@hunter-harness/workflow-harness 0.2.79 → 0.2.80
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 +12 -1
- 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 +15 -1
- package/harness/bundles/general/claude-code/harness-review/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-run/SKILL.md +1 -1
- 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 +158 -44
- package/harness/bundles/general/codebuddy/.harness-build.json +1 -1
- package/harness/bundles/general/codebuddy/harness-archive/SKILL.md +12 -1
- 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 +15 -1
- package/harness/bundles/general/codebuddy/harness-review/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-run/SKILL.md +1 -1
- 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 +158 -44
- package/harness/bundles/general/codex/.harness-build.json +1 -1
- package/harness/bundles/general/codex/harness-archive/SKILL.md +12 -1
- 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 +15 -1
- package/harness/bundles/general/codex/harness-review/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-run/SKILL.md +1 -1
- 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 +158 -44
- package/harness/bundles/general/cursor/.harness-build.json +1 -1
- package/harness/bundles/general/cursor/harness-archive/SKILL.md +12 -1
- 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 +15 -1
- package/harness/bundles/general/cursor/harness-review/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-run/SKILL.md +1 -1
- 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 +158 -44
- 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 +12 -1
- 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 +15 -1
- package/harness/bundles/java/claude-code/harness-review/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-run/SKILL.md +1 -1
- 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 +158 -44
- 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 +12 -1
- 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 +15 -1
- package/harness/bundles/java/codebuddy/harness-review/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-run/SKILL.md +1 -1
- 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 +158 -44
- 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 +12 -1
- 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 +15 -1
- package/harness/bundles/java/codex/harness-review/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-run/SKILL.md +1 -1
- 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 +158 -44
- 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 +12 -1
- 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 +15 -1
- package/harness/bundles/java/cursor/harness-review/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-run/SKILL.md +1 -1
- 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 +158 -44
- package/harness/manifests/general/claude-code.json +16 -16
- package/harness/manifests/general/codebuddy.json +16 -16
- package/harness/manifests/general/codex.json +16 -16
- package/harness/manifests/general/cursor.json +16 -16
- package/harness/manifests/java/claude-code.json +18 -18
- package/harness/manifests/java/codebuddy.json +18 -18
- package/harness/manifests/java/codex.json +18 -18
- package/harness/manifests/java/cursor.json +18 -18
- 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=5fadb6520fbfa2c3; overlay=none; agent=claude-code; do not edit -->
|
|
25
25
|
# harness-archive — 变更归档
|
|
26
26
|
|
|
27
27
|
## Purpose
|
|
@@ -162,10 +162,21 @@ python <skills-root>/scripts/harness_archive.py republish --change <change-key>
|
|
|
162
162
|
python <skills-root>/scripts/harness_archive.py republish --change <change-key> --json # 重建并上传
|
|
163
163
|
```
|
|
164
164
|
|
|
165
|
+
脚本在 `<skills-root>/scripts/` 共享,**不在 `harness-archive/scripts/`**(见 read-protocol 第 0 条)。
|
|
166
|
+
|
|
167
|
+
在项目根内任意目录直接跑即可,**不需要** `--project`。
|
|
168
|
+
|
|
165
169
|
归档目录是封存的(after-manifest 覆盖其每个字节),因此 `republish` **不写回**归档目录:
|
|
166
170
|
缺失的 `candidates/knowledge.json` 只在内存中按已归档 summary 生成并放进包里。平台的知识
|
|
167
171
|
条目只来自这个包;`--scope all` 走的是 branch_file 通道,不进知识管道。
|
|
168
172
|
|
|
173
|
+
**服务端一个 change key 只存一个不可变包**,所以补传只适用于「从未成功上传」与「字节完全一致的
|
|
174
|
+
重试」。已 durable 的 change 会在本地就判出结果:字节一致 → `ARCHIVE_ALREADY_PUBLISHED`
|
|
175
|
+
(exit 0,不重传);字节不同 → `ARCHIVE_REMOTE_IMMUTABLE_CONFLICT`(exit 1,**不发起上传**)。
|
|
176
|
+
**已发布归档无法从客户端补知识条目**——注入候选必然改变字节,服务端拒绝替换;要补需要平台侧
|
|
177
|
+
提供重新索引或归档版本化能力。
|
|
178
|
+
只想重试**同一个包**(例如知识索引失败)时加 `--no-knowledge-injection`,它按封存目录原样重建,字节与已存包一致。
|
|
179
|
+
|
|
169
180
|
### 三、文件移动只用内置工具或 PowerShell
|
|
170
181
|
|
|
171
182
|
移动用 Read+Write+验证 或 PowerShell;**禁止 Bash mv/cp/rm**。移动失败时不删除原目录,报错退出让用户手动处理。
|
|
@@ -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=5fadb6520fbfa2c3; 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=5fadb6520fbfa2c3; 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=5fadb6520fbfa2c3; 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=5fadb6520fbfa2c3; 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=5fadb6520fbfa2c3; 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=5fadb6520fbfa2c3; overlay=none; agent=claude-code; do not edit -->
|
|
25
25
|
# harness-push — 上传到 Hunter Platform
|
|
26
26
|
|
|
27
27
|
## Purpose
|
|
@@ -57,8 +57,22 @@ npx hunter-harness harness-push --scope archive --change <change-key> --json
|
|
|
57
57
|
python <skills-root>/scripts/harness_archive.py republish --change <change-key> --json
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
+
`<skills-root>` 是已部署 skills 的根目录,实际形态是 `.codebuddy/skills/`(`.claude`、`.cursor`、
|
|
61
|
+
`.codex` 同理)。**脚本在它下面的 `scripts/` 里共享,不在 `harness-push/scripts/`** —— 已有四份
|
|
62
|
+
执行日志先猜成后者、报 `No such file` 再回头找。
|
|
63
|
+
|
|
60
64
|
`--scope archive` 没有待发布 claim 时返回 `PUSH_PULL_ARCHIVE_NO_PENDING_CLAIM`(exit 5),
|
|
61
65
|
stderr 里直接给出上面的 republish 命令;`--dry-run` 会列出本地可补传的归档目录,不取租约。
|
|
66
|
+
`republish` 在项目根内任意目录直接跑即可,**不需要** `--project`。
|
|
67
|
+
|
|
68
|
+
**补传能做什么、不能做什么**:服务端对同一 change key 只保存**一个不可变包**。所以补传只适用于
|
|
69
|
+
「从未成功上传」与「字节完全一致的重试」两种情况。若该 change 已是 durable,`republish` 会读
|
|
70
|
+
`.harness/state/local/archive-packages/<key>.remote.json` 在**本地就判定**并返回:
|
|
71
|
+
字节一致 → `ARCHIVE_ALREADY_PUBLISHED`(exit 0,不重传);字节不同 →
|
|
72
|
+
`ARCHIVE_REMOTE_IMMUTABLE_CONFLICT`(exit 1,**不发起上传**)。
|
|
73
|
+
**已发布的归档无法从客户端补上知识条目**——注入 `candidates/knowledge.json` 必然改变字节,
|
|
74
|
+
服务端拒绝替换。要让旧归档产生知识条目,需要平台侧提供重新索引或归档版本化能力。
|
|
75
|
+
只想重试**同一个包**(例如知识索引失败)时加 `--no-knowledge-injection`,它按封存目录原样重建,字节与已存包一致。
|
|
62
76
|
|
|
63
77
|
## 关键规则
|
|
64
78
|
|
|
@@ -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=5fadb6520fbfa2c3; 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=5fadb6520fbfa2c3; overlay=none; agent=claude-code; do not edit -->
|
|
26
26
|
# harness-run — 需求编码
|
|
27
27
|
|
|
28
28
|
## Purpose
|
|
@@ -21,7 +21,7 @@ disallowed-tools:
|
|
|
21
21
|
- Bash(awk *)
|
|
22
22
|
- Bash(curl *)
|
|
23
23
|
---
|
|
24
|
-
<!-- generated by harness_deploy.py; core=
|
|
24
|
+
<!-- generated by harness_deploy.py; core=5fadb6520fbfa2c3; 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=5fadb6520fbfa2c3; 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=5fadb6520fbfa2c3; overlay=none; agent=claude-code; do not edit -->
|
|
27
27
|
# harness-test — 测试执行
|
|
28
28
|
|
|
29
29
|
## Purpose
|
|
@@ -10232,6 +10232,42 @@ def cmd_repair_cli(args: argparse.Namespace) -> int:
|
|
|
10232
10232
|
return code
|
|
10233
10233
|
|
|
10234
10234
|
|
|
10235
|
+
def resolve_republish_project_root(explicit: Path | None, archive_dir: Path | None) -> Path:
|
|
10236
|
+
"""Locate the project root for a republish without requiring --project.
|
|
10237
|
+
|
|
10238
|
+
``find_project_root`` walks ``p.parents``, which excludes ``p`` itself; it is
|
|
10239
|
+
built for a change/archive subdirectory. Handing it the cwd therefore skips
|
|
10240
|
+
the very directory the operator is standing in and keeps climbing — in a real
|
|
10241
|
+
run that resolved ``E:/WorkProject/kb-sdd`` to its parent and made the command
|
|
10242
|
+
look for archives that were never there.
|
|
10243
|
+
|
|
10244
|
+
Order: explicit flag, then the archive's own location, then cwd and its
|
|
10245
|
+
parents (cwd included), then the deployed script's location — the script
|
|
10246
|
+
lives at ``<project>/.claude|.codebuddy/skills/scripts/`` after install.
|
|
10247
|
+
"""
|
|
10248
|
+
if explicit is not None:
|
|
10249
|
+
return explicit.resolve()
|
|
10250
|
+
if archive_dir is not None:
|
|
10251
|
+
return find_project_root(archive_dir)
|
|
10252
|
+
|
|
10253
|
+
def first_with_archive(start: Path) -> Path | None:
|
|
10254
|
+
start = start.resolve()
|
|
10255
|
+
for candidate in (start, *start.parents):
|
|
10256
|
+
if (candidate / ".harness" / "archive").is_dir():
|
|
10257
|
+
return candidate
|
|
10258
|
+
return None
|
|
10259
|
+
|
|
10260
|
+
found = first_with_archive(Path.cwd())
|
|
10261
|
+
if found is not None:
|
|
10262
|
+
return found
|
|
10263
|
+
found = first_with_archive(Path(__file__).resolve().parent)
|
|
10264
|
+
if found is not None:
|
|
10265
|
+
return found
|
|
10266
|
+
# Nothing carries an archive directory; report against cwd so the error names
|
|
10267
|
+
# a path the operator recognises instead of some ancestor.
|
|
10268
|
+
return Path.cwd().resolve()
|
|
10269
|
+
|
|
10270
|
+
|
|
10235
10271
|
def resolve_archive_dir_for_change(
|
|
10236
10272
|
project_root: Path,
|
|
10237
10273
|
change_key: str,
|
|
@@ -10272,7 +10308,10 @@ def _republish_knowledge_entry(archive_dir: Path) -> dict[str, tuple[str, str, b
|
|
|
10272
10308
|
change_key=str(summary.get("changeName") or archive_dir.name),
|
|
10273
10309
|
archive_id=archive_dir.name,
|
|
10274
10310
|
producer_version=SCHEMA_VERSION,
|
|
10275
|
-
|
|
10311
|
+
# Derived from the archive, never wall-clock: `now_iso()` here made every
|
|
10312
|
+
# rebuild produce different bytes, so a package could never be compared
|
|
10313
|
+
# against a stored one and the "deterministic package" claim was false.
|
|
10314
|
+
created_at=_archive_created_at(summary_path),
|
|
10276
10315
|
)
|
|
10277
10316
|
if not candidates:
|
|
10278
10317
|
return {}
|
|
@@ -10285,12 +10324,28 @@ def _republish_knowledge_entry(archive_dir: Path) -> dict[str, tuple[str, str, b
|
|
|
10285
10324
|
}
|
|
10286
10325
|
|
|
10287
10326
|
|
|
10327
|
+
def read_durable_archive_receipt(project_root: Path, change_key: str) -> dict[str, Any] | None:
|
|
10328
|
+
"""The `<key>.remote.json` written after a confirmed durable upload, if any."""
|
|
10329
|
+
receipt_path = (
|
|
10330
|
+
project_root / ".harness" / "state" / "local" / "archive-packages"
|
|
10331
|
+
/ f"{change_key}.remote.json"
|
|
10332
|
+
)
|
|
10333
|
+
if not receipt_path.is_file():
|
|
10334
|
+
return None
|
|
10335
|
+
try:
|
|
10336
|
+
receipt = read_json(receipt_path)
|
|
10337
|
+
except (OSError, json.JSONDecodeError, TypeError, ValueError):
|
|
10338
|
+
return None
|
|
10339
|
+
return receipt if isinstance(receipt, dict) else None
|
|
10340
|
+
|
|
10341
|
+
|
|
10288
10342
|
def cmd_republish(
|
|
10289
10343
|
*,
|
|
10290
10344
|
change_key: str,
|
|
10291
10345
|
archive_dir: Path | None,
|
|
10292
10346
|
project_root: Path | None,
|
|
10293
10347
|
dry_run: bool,
|
|
10348
|
+
inject_knowledge: bool = True,
|
|
10294
10349
|
) -> tuple[int, dict[str, Any]]:
|
|
10295
10350
|
"""Rebuild and re-upload the core ZIP for an already sealed archive.
|
|
10296
10351
|
|
|
@@ -10311,7 +10366,7 @@ def cmd_republish(
|
|
|
10311
10366
|
payload["reasonCode"] = "ARCHIVE_CHANGE_KEY_INVALID"
|
|
10312
10367
|
return 2, payload
|
|
10313
10368
|
|
|
10314
|
-
root = (project_root
|
|
10369
|
+
root = resolve_republish_project_root(project_root, archive_dir)
|
|
10315
10370
|
payload["projectRoot"] = str(root)
|
|
10316
10371
|
matches: list[str] = []
|
|
10317
10372
|
if archive_dir is None:
|
|
@@ -10338,53 +10393,103 @@ def cmd_republish(
|
|
|
10338
10393
|
payload["reasonCode"] = "ARCHIVE_SUMMARY_MISSING"
|
|
10339
10394
|
return 1, payload
|
|
10340
10395
|
|
|
10341
|
-
|
|
10342
|
-
|
|
10343
|
-
|
|
10344
|
-
|
|
10345
|
-
|
|
10346
|
-
|
|
10347
|
-
|
|
10348
|
-
|
|
10396
|
+
extra_entries: dict[str, tuple[str, str, bytes]] = {}
|
|
10397
|
+
if inject_knowledge:
|
|
10398
|
+
try:
|
|
10399
|
+
extra_entries = _republish_knowledge_entry(archive_dir)
|
|
10400
|
+
except (OSError, ValueError, json.JSONDecodeError) as exc:
|
|
10401
|
+
# Knowledge is additive: never fail a re-delivery over it.
|
|
10402
|
+
payload.setdefault("warnings", []).append(
|
|
10403
|
+
f"knowledge candidate regeneration skipped: {exc}"
|
|
10404
|
+
)
|
|
10405
|
+
extra_entries = {}
|
|
10349
10406
|
payload["knowledgeCandidatesInjected"] = "candidates/knowledge.json" in extra_entries
|
|
10350
10407
|
|
|
10351
|
-
|
|
10352
|
-
|
|
10353
|
-
|
|
10354
|
-
|
|
10355
|
-
|
|
10356
|
-
|
|
10357
|
-
|
|
10408
|
+
# Build once, into a preview path, so the bytes can be compared against any
|
|
10409
|
+
# durable receipt before an upload is attempted. The builder refuses to write
|
|
10410
|
+
# outside the project, so the preview lands beside the real packages under a
|
|
10411
|
+
# distinct name; it must never be mistaken for a pending upload.
|
|
10412
|
+
preview_path = (
|
|
10413
|
+
root / ".harness" / "state" / "local" / "archive-packages"
|
|
10414
|
+
/ f"{change_key}.preview.zip"
|
|
10415
|
+
)
|
|
10416
|
+
try:
|
|
10417
|
+
package = build_archive_package(
|
|
10418
|
+
root,
|
|
10419
|
+
archive_dir,
|
|
10420
|
+
change_key,
|
|
10421
|
+
output_path=preview_path,
|
|
10422
|
+
extra_entries=extra_entries,
|
|
10358
10423
|
)
|
|
10424
|
+
except (OSError, ValueError, UnicodeDecodeError, json.JSONDecodeError) as exc:
|
|
10425
|
+
payload["error"] = f"archive package build failed: {exc}"
|
|
10426
|
+
payload["reasonCode"] = "ARCHIVE_PACKAGE_BUILD_FAILED"
|
|
10427
|
+
return 1, payload
|
|
10428
|
+
finally:
|
|
10359
10429
|
try:
|
|
10360
|
-
|
|
10361
|
-
|
|
10362
|
-
|
|
10363
|
-
|
|
10364
|
-
|
|
10365
|
-
|
|
10366
|
-
|
|
10367
|
-
|
|
10368
|
-
|
|
10369
|
-
|
|
10370
|
-
|
|
10371
|
-
|
|
10372
|
-
|
|
10373
|
-
|
|
10374
|
-
|
|
10375
|
-
|
|
10376
|
-
|
|
10377
|
-
|
|
10430
|
+
preview_path.unlink(missing_ok=True)
|
|
10431
|
+
except OSError:
|
|
10432
|
+
pass
|
|
10433
|
+
|
|
10434
|
+
payload.update(
|
|
10435
|
+
{
|
|
10436
|
+
"fileCount": package["fileCount"],
|
|
10437
|
+
"sizeBytes": package["sizeBytes"],
|
|
10438
|
+
"knowledgeCandidateCount": package["knowledgeCandidateCount"],
|
|
10439
|
+
"packageSha256": package["packageSha256"],
|
|
10440
|
+
"manifestSha256": package["manifestSha256"],
|
|
10441
|
+
"files": package["paths"],
|
|
10442
|
+
}
|
|
10443
|
+
)
|
|
10444
|
+
|
|
10445
|
+
# The platform stores exactly one immutable package per change key. A rebuild
|
|
10446
|
+
# that differs by even one byte is refused server-side, so decide here rather
|
|
10447
|
+
# than spending an upload to learn it.
|
|
10448
|
+
durable = read_durable_archive_receipt(root, change_key)
|
|
10449
|
+
stored_sha = str((durable or {}).get("packageSha256") or "")
|
|
10450
|
+
if durable is not None and str(durable.get("archiveStatus") or "") == "durable":
|
|
10451
|
+
payload["remoteReceipt"] = {
|
|
10452
|
+
"archiveId": durable.get("archiveId"),
|
|
10453
|
+
"archiveStatus": durable.get("archiveStatus"),
|
|
10454
|
+
"knowledgeStatus": durable.get("knowledgeStatus"),
|
|
10455
|
+
"packageSha256": stored_sha,
|
|
10456
|
+
"recordedAt": durable.get("recordedAt"),
|
|
10457
|
+
}
|
|
10458
|
+
if stored_sha and stored_sha == package["packageSha256"]:
|
|
10459
|
+
payload.update({
|
|
10378
10460
|
"ok": True,
|
|
10379
|
-
"reasonCode": "
|
|
10380
|
-
"
|
|
10381
|
-
|
|
10382
|
-
|
|
10383
|
-
|
|
10384
|
-
|
|
10385
|
-
|
|
10386
|
-
|
|
10387
|
-
|
|
10461
|
+
"reasonCode": "ARCHIVE_ALREADY_PUBLISHED",
|
|
10462
|
+
"message": (
|
|
10463
|
+
"远端已存有字节一致的同一个包,无需重传"
|
|
10464
|
+
f"(archiveId={durable.get('archiveId')})。"
|
|
10465
|
+
),
|
|
10466
|
+
})
|
|
10467
|
+
return 0, payload
|
|
10468
|
+
payload.update({
|
|
10469
|
+
"ok": False,
|
|
10470
|
+
"reasonCode": "ARCHIVE_REMOTE_IMMUTABLE_CONFLICT",
|
|
10471
|
+
"error": (
|
|
10472
|
+
"该 change 在远端已是 durable 归档,服务端对同一 change key 只保存一个"
|
|
10473
|
+
"不可变包,不接受不同字节的替换。"
|
|
10474
|
+
),
|
|
10475
|
+
"nextAction": (
|
|
10476
|
+
"补传只适用于『从未成功上传』或『字节完全一致的重试』。"
|
|
10477
|
+
+ (
|
|
10478
|
+
"本次重建注入了 "
|
|
10479
|
+
f"{package['knowledgeCandidateCount']} 条知识候选"
|
|
10480
|
+
"(原包上传时还没有 candidates/knowledge.json),因此字节必然不同——"
|
|
10481
|
+
"已发布归档无法从客户端追加知识条目,需要平台侧提供重新索引或归档版本化能力。"
|
|
10482
|
+
"若只是想重试同一个包(例如知识索引失败),加 --no-knowledge-injection "
|
|
10483
|
+
"可重建与封存目录完全一致的字节。"
|
|
10484
|
+
if payload.get("knowledgeCandidatesInjected")
|
|
10485
|
+
else "本地归档内容与已上传的包不一致,请先确认哪一份才是应保留的事实。"
|
|
10486
|
+
)
|
|
10487
|
+
),
|
|
10488
|
+
})
|
|
10489
|
+
return 1, payload
|
|
10490
|
+
|
|
10491
|
+
if dry_run:
|
|
10492
|
+
payload.update({"ok": True, "reasonCode": "ARCHIVE_REPUBLISH_PREVIEW"})
|
|
10388
10493
|
return 0, payload
|
|
10389
10494
|
|
|
10390
10495
|
result = auto_push_archive_core(
|
|
@@ -10407,6 +10512,7 @@ def cmd_republish_cli(args: argparse.Namespace) -> int:
|
|
|
10407
10512
|
archive_dir=resolve_path(args.archive_dir) if args.archive_dir else None,
|
|
10408
10513
|
project_root=resolve_path(args.project) if args.project else None,
|
|
10409
10514
|
dry_run=bool(args.dry_run),
|
|
10515
|
+
inject_knowledge=not bool(args.no_knowledge_injection),
|
|
10410
10516
|
)
|
|
10411
10517
|
emit_json(payload)
|
|
10412
10518
|
return code
|
|
@@ -10590,6 +10696,14 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
10590
10696
|
action="store_true",
|
|
10591
10697
|
help="build the package and report its contents without uploading",
|
|
10592
10698
|
)
|
|
10699
|
+
p_republish.add_argument(
|
|
10700
|
+
"--no-knowledge-injection",
|
|
10701
|
+
action="store_true",
|
|
10702
|
+
help=(
|
|
10703
|
+
"rebuild exactly what the sealed archive holds; needed to retry the "
|
|
10704
|
+
"same bytes the server already stored"
|
|
10705
|
+
),
|
|
10706
|
+
)
|
|
10593
10707
|
p_republish.add_argument("--json", action="store_true", default=True)
|
|
10594
10708
|
p_republish.set_defaults(func=cmd_republish_cli)
|
|
10595
10709
|
|
|
@@ -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=5fadb6520fbfa2c3; overlay=none; agent=codebuddy; do not edit -->
|
|
7
7
|
# harness-archive — 变更归档
|
|
8
8
|
|
|
9
9
|
## Purpose
|
|
@@ -144,10 +144,21 @@ python <skills-root>/scripts/harness_archive.py republish --change <change-key>
|
|
|
144
144
|
python <skills-root>/scripts/harness_archive.py republish --change <change-key> --json # 重建并上传
|
|
145
145
|
```
|
|
146
146
|
|
|
147
|
+
脚本在 `<skills-root>/scripts/` 共享,**不在 `harness-archive/scripts/`**(见 read-protocol 第 0 条)。
|
|
148
|
+
|
|
149
|
+
在项目根内任意目录直接跑即可,**不需要** `--project`。
|
|
150
|
+
|
|
147
151
|
归档目录是封存的(after-manifest 覆盖其每个字节),因此 `republish` **不写回**归档目录:
|
|
148
152
|
缺失的 `candidates/knowledge.json` 只在内存中按已归档 summary 生成并放进包里。平台的知识
|
|
149
153
|
条目只来自这个包;`--scope all` 走的是 branch_file 通道,不进知识管道。
|
|
150
154
|
|
|
155
|
+
**服务端一个 change key 只存一个不可变包**,所以补传只适用于「从未成功上传」与「字节完全一致的
|
|
156
|
+
重试」。已 durable 的 change 会在本地就判出结果:字节一致 → `ARCHIVE_ALREADY_PUBLISHED`
|
|
157
|
+
(exit 0,不重传);字节不同 → `ARCHIVE_REMOTE_IMMUTABLE_CONFLICT`(exit 1,**不发起上传**)。
|
|
158
|
+
**已发布归档无法从客户端补知识条目**——注入候选必然改变字节,服务端拒绝替换;要补需要平台侧
|
|
159
|
+
提供重新索引或归档版本化能力。
|
|
160
|
+
只想重试**同一个包**(例如知识索引失败)时加 `--no-knowledge-injection`,它按封存目录原样重建,字节与已存包一致。
|
|
161
|
+
|
|
151
162
|
### 三、文件移动只用内置工具或 PowerShell
|
|
152
163
|
|
|
153
164
|
移动用 Read+Write+验证 或 PowerShell;**禁止 Bash mv/cp/rm**。移动失败时不删除原目录,报错退出让用户手动处理。
|
|
@@ -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=5fadb6520fbfa2c3; 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=5fadb6520fbfa2c3; 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=5fadb6520fbfa2c3; 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=5fadb6520fbfa2c3; overlay=none; agent=codebuddy; do not edit -->
|
|
7
7
|
# harness-plan — 需求规划
|
|
8
8
|
|
|
9
9
|
## Purpose
|
|
@@ -3,7 +3,7 @@ name: harness-pull
|
|
|
3
3
|
description: 从 Hunter Platform 下拉配置/规则/架构/指令(及显式来源分支的分支文件恢复)。仅当用户显式调用
|
|
4
4
|
/harness-pull 或明确说'从平台拉取/恢复'时使用;不得自动触发。
|
|
5
5
|
---
|
|
6
|
-
<!-- generated by harness_deploy.py; core=
|
|
6
|
+
<!-- generated by harness_deploy.py; core=5fadb6520fbfa2c3; overlay=none; agent=codebuddy; do not edit -->
|
|
7
7
|
# harness-pull — 从 Hunter Platform 下拉与恢复
|
|
8
8
|
|
|
9
9
|
## Purpose
|
|
@@ -3,7 +3,7 @@ name: harness-push
|
|
|
3
3
|
description: 上传本地配置/规则/架构/指令(及显式归档)到 Hunter Platform。仅当用户显式调用 /harness-push
|
|
4
4
|
或明确说'上传到平台'时使用;不得因存在本地修改就自动触发。
|
|
5
5
|
---
|
|
6
|
-
<!-- generated by harness_deploy.py; core=
|
|
6
|
+
<!-- generated by harness_deploy.py; core=5fadb6520fbfa2c3; overlay=none; agent=codebuddy; do not edit -->
|
|
7
7
|
# harness-push — 上传到 Hunter Platform
|
|
8
8
|
|
|
9
9
|
## Purpose
|
|
@@ -39,8 +39,22 @@ npx hunter-harness harness-push --scope archive --change <change-key> --json
|
|
|
39
39
|
python <skills-root>/scripts/harness_archive.py republish --change <change-key> --json
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
+
`<skills-root>` 是已部署 skills 的根目录,实际形态是 `.codebuddy/skills/`(`.claude`、`.cursor`、
|
|
43
|
+
`.codex` 同理)。**脚本在它下面的 `scripts/` 里共享,不在 `harness-push/scripts/`** —— 已有四份
|
|
44
|
+
执行日志先猜成后者、报 `No such file` 再回头找。
|
|
45
|
+
|
|
42
46
|
`--scope archive` 没有待发布 claim 时返回 `PUSH_PULL_ARCHIVE_NO_PENDING_CLAIM`(exit 5),
|
|
43
47
|
stderr 里直接给出上面的 republish 命令;`--dry-run` 会列出本地可补传的归档目录,不取租约。
|
|
48
|
+
`republish` 在项目根内任意目录直接跑即可,**不需要** `--project`。
|
|
49
|
+
|
|
50
|
+
**补传能做什么、不能做什么**:服务端对同一 change key 只保存**一个不可变包**。所以补传只适用于
|
|
51
|
+
「从未成功上传」与「字节完全一致的重试」两种情况。若该 change 已是 durable,`republish` 会读
|
|
52
|
+
`.harness/state/local/archive-packages/<key>.remote.json` 在**本地就判定**并返回:
|
|
53
|
+
字节一致 → `ARCHIVE_ALREADY_PUBLISHED`(exit 0,不重传);字节不同 →
|
|
54
|
+
`ARCHIVE_REMOTE_IMMUTABLE_CONFLICT`(exit 1,**不发起上传**)。
|
|
55
|
+
**已发布的归档无法从客户端补上知识条目**——注入 `candidates/knowledge.json` 必然改变字节,
|
|
56
|
+
服务端拒绝替换。要让旧归档产生知识条目,需要平台侧提供重新索引或归档版本化能力。
|
|
57
|
+
只想重试**同一个包**(例如知识索引失败)时加 `--no-knowledge-injection`,它按封存目录原样重建,字节与已存包一致。
|
|
44
58
|
|
|
45
59
|
## 关键规则
|
|
46
60
|
|
|
@@ -4,7 +4,7 @@ description: 6维度代码审查(架构/安全/规范/兼容/测试/性能)
|
|
|
4
4
|
.harness/context-index.json)和测试场景表,在隔离上下文运行。仅当用户显式调用 /harness-review 时使用;不得在
|
|
5
5
|
test 结束后自动接续执行。
|
|
6
6
|
---
|
|
7
|
-
<!-- generated by harness_deploy.py; core=
|
|
7
|
+
<!-- generated by harness_deploy.py; core=5fadb6520fbfa2c3; overlay=none; agent=codebuddy; do not edit -->
|
|
8
8
|
# harness-review — 代码审查
|
|
9
9
|
|
|
10
10
|
## Purpose
|
|
@@ -3,7 +3,7 @@ name: harness-run
|
|
|
3
3
|
description: 按变更簇执行 TDD 编码循环(RED→GREEN→REFACTOR→编译验证),逐变更簇实现计划中的任务。仅当用户显式调用
|
|
4
4
|
/harness-run 时使用;不得因用户提到编码/实现就自动触发,也不得被其他阶段 skill 自动接续。
|
|
5
5
|
---
|
|
6
|
-
<!-- generated by harness_deploy.py; core=
|
|
6
|
+
<!-- generated by harness_deploy.py; core=5fadb6520fbfa2c3; overlay=none; agent=codebuddy; do not edit -->
|
|
7
7
|
# harness-run — 需求编码
|
|
8
8
|
|
|
9
9
|
## Purpose
|
|
@@ -3,7 +3,7 @@ name: harness-submit
|
|
|
3
3
|
description: 最终提交封装:验证→中文 commit→提交/推送;worktree 模式含 --no-ff 合并回主分支。仅当用户显式调用
|
|
4
4
|
/harness-submit(或 /harness-merge 重入合并段)时使用;用户口头说'提交/commit/push'时必须先确认,不得自动触发。
|
|
5
5
|
---
|
|
6
|
-
<!-- generated by harness_deploy.py; core=
|
|
6
|
+
<!-- generated by harness_deploy.py; core=5fadb6520fbfa2c3; overlay=none; agent=codebuddy; do not edit -->
|
|
7
7
|
# harness-submit — 最终提交(含 worktree 合并)
|
|
8
8
|
|
|
9
9
|
## Purpose
|
|
@@ -4,7 +4,7 @@ description: Use when the user asks to synchronize, refresh, or validate Harness
|
|
|
4
4
|
metadata, adapters, remote knowledge ownership, instruction entrypoints,
|
|
5
5
|
config origins, or CodeGraph status.
|
|
6
6
|
---
|
|
7
|
-
<!-- generated by harness_deploy.py; core=
|
|
7
|
+
<!-- generated by harness_deploy.py; core=5fadb6520fbfa2c3; overlay=none; agent=codebuddy; do not edit -->
|
|
8
8
|
# harness-sync
|
|
9
9
|
|
|
10
10
|
## Purpose
|
|
@@ -3,7 +3,7 @@ name: harness-test
|
|
|
3
3
|
description: 测试执行:读取场景表,执行单元测试+API接口测试+数据兼容验证,输出测试报告。仅当用户显式调用 /harness-test
|
|
4
4
|
时使用;不得在 run 结束后自动接续执行。
|
|
5
5
|
---
|
|
6
|
-
<!-- generated by harness_deploy.py; core=
|
|
6
|
+
<!-- generated by harness_deploy.py; core=5fadb6520fbfa2c3; overlay=none; agent=codebuddy; do not edit -->
|
|
7
7
|
# harness-test — 测试执行
|
|
8
8
|
|
|
9
9
|
## Purpose
|