@hunter-harness/workflow-harness 0.2.80 → 0.2.82
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 +18 -4
- package/harness/bundles/general/claude-code/harness-codebase-map/SKILL.md +13 -3
- package/harness/bundles/general/claude-code/harness-knowledge-ingest/SKILL.md +13 -3
- package/harness/bundles/general/claude-code/harness-knowledge-query/SKILL.md +13 -3
- package/harness/bundles/general/claude-code/harness-plan/SKILL.md +13 -3
- 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 +13 -3
- package/harness/bundles/general/claude-code/harness-run/SKILL.md +13 -3
- package/harness/bundles/general/claude-code/harness-submit/SKILL.md +13 -3
- package/harness/bundles/general/claude-code/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-test/SKILL.md +13 -3
- package/harness/bundles/general/claude-code/scripts/harness_archive.py +173 -20
- package/harness/bundles/general/claude-code/scripts/harness_context.py +77 -3
- package/harness/bundles/general/claude-code/scripts/harness_events.py +35 -2
- package/harness/bundles/general/claude-code/scripts/harness_plan_finalize.py +6 -1
- package/harness/bundles/general/codebuddy/.harness-build.json +1 -1
- package/harness/bundles/general/codebuddy/harness-archive/SKILL.md +18 -4
- package/harness/bundles/general/codebuddy/harness-codebase-map/SKILL.md +13 -3
- package/harness/bundles/general/codebuddy/harness-knowledge-ingest/SKILL.md +13 -3
- package/harness/bundles/general/codebuddy/harness-knowledge-query/SKILL.md +13 -3
- package/harness/bundles/general/codebuddy/harness-plan/SKILL.md +13 -3
- 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 +13 -3
- package/harness/bundles/general/codebuddy/harness-run/SKILL.md +13 -3
- package/harness/bundles/general/codebuddy/harness-submit/SKILL.md +13 -3
- package/harness/bundles/general/codebuddy/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-test/SKILL.md +13 -3
- package/harness/bundles/general/codebuddy/scripts/harness_archive.py +173 -20
- package/harness/bundles/general/codebuddy/scripts/harness_context.py +77 -3
- package/harness/bundles/general/codebuddy/scripts/harness_events.py +35 -2
- package/harness/bundles/general/codebuddy/scripts/harness_plan_finalize.py +6 -1
- package/harness/bundles/general/codex/.harness-build.json +1 -1
- package/harness/bundles/general/codex/harness-archive/SKILL.md +18 -4
- package/harness/bundles/general/codex/harness-codebase-map/SKILL.md +13 -3
- package/harness/bundles/general/codex/harness-knowledge-ingest/SKILL.md +13 -3
- package/harness/bundles/general/codex/harness-knowledge-query/SKILL.md +13 -3
- package/harness/bundles/general/codex/harness-plan/SKILL.md +13 -3
- 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 +13 -3
- package/harness/bundles/general/codex/harness-run/SKILL.md +13 -3
- package/harness/bundles/general/codex/harness-submit/SKILL.md +13 -3
- package/harness/bundles/general/codex/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-test/SKILL.md +13 -3
- package/harness/bundles/general/codex/scripts/harness_archive.py +173 -20
- package/harness/bundles/general/codex/scripts/harness_context.py +77 -3
- package/harness/bundles/general/codex/scripts/harness_events.py +35 -2
- package/harness/bundles/general/codex/scripts/harness_plan_finalize.py +6 -1
- package/harness/bundles/general/cursor/.harness-build.json +1 -1
- package/harness/bundles/general/cursor/harness-archive/SKILL.md +18 -4
- package/harness/bundles/general/cursor/harness-codebase-map/SKILL.md +13 -3
- package/harness/bundles/general/cursor/harness-knowledge-ingest/SKILL.md +13 -3
- package/harness/bundles/general/cursor/harness-knowledge-query/SKILL.md +13 -3
- package/harness/bundles/general/cursor/harness-plan/SKILL.md +13 -3
- 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 +13 -3
- package/harness/bundles/general/cursor/harness-run/SKILL.md +13 -3
- package/harness/bundles/general/cursor/harness-submit/SKILL.md +13 -3
- package/harness/bundles/general/cursor/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-test/SKILL.md +13 -3
- package/harness/bundles/general/cursor/scripts/harness_archive.py +173 -20
- package/harness/bundles/general/cursor/scripts/harness_context.py +77 -3
- package/harness/bundles/general/cursor/scripts/harness_events.py +35 -2
- package/harness/bundles/general/cursor/scripts/harness_plan_finalize.py +6 -1
- package/harness/bundles/java/claude-code/.harness-build.json +1 -1
- package/harness/bundles/java/claude-code/harness-apidoc/SKILL.md +13 -3
- package/harness/bundles/java/claude-code/harness-archive/SKILL.md +18 -4
- package/harness/bundles/java/claude-code/harness-codebase-map/SKILL.md +13 -3
- package/harness/bundles/java/claude-code/harness-knowledge-ingest/SKILL.md +13 -3
- package/harness/bundles/java/claude-code/harness-knowledge-query/SKILL.md +13 -3
- package/harness/bundles/java/claude-code/harness-package/SKILL.md +13 -3
- package/harness/bundles/java/claude-code/harness-plan/SKILL.md +13 -3
- 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 +13 -3
- package/harness/bundles/java/claude-code/harness-run/SKILL.md +13 -3
- package/harness/bundles/java/claude-code/harness-submit/SKILL.md +13 -3
- package/harness/bundles/java/claude-code/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-test/SKILL.md +13 -3
- package/harness/bundles/java/claude-code/scripts/harness_archive.py +173 -20
- package/harness/bundles/java/claude-code/scripts/harness_context.py +77 -3
- package/harness/bundles/java/claude-code/scripts/harness_events.py +35 -2
- package/harness/bundles/java/claude-code/scripts/harness_plan_finalize.py +6 -1
- package/harness/bundles/java/codebuddy/.harness-build.json +1 -1
- package/harness/bundles/java/codebuddy/harness-apidoc/SKILL.md +13 -3
- package/harness/bundles/java/codebuddy/harness-archive/SKILL.md +18 -4
- package/harness/bundles/java/codebuddy/harness-codebase-map/SKILL.md +13 -3
- package/harness/bundles/java/codebuddy/harness-knowledge-ingest/SKILL.md +13 -3
- package/harness/bundles/java/codebuddy/harness-knowledge-query/SKILL.md +13 -3
- package/harness/bundles/java/codebuddy/harness-package/SKILL.md +13 -3
- package/harness/bundles/java/codebuddy/harness-plan/SKILL.md +13 -3
- 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 +13 -3
- package/harness/bundles/java/codebuddy/harness-run/SKILL.md +13 -3
- package/harness/bundles/java/codebuddy/harness-submit/SKILL.md +13 -3
- package/harness/bundles/java/codebuddy/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-test/SKILL.md +13 -3
- package/harness/bundles/java/codebuddy/scripts/harness_archive.py +173 -20
- package/harness/bundles/java/codebuddy/scripts/harness_context.py +77 -3
- package/harness/bundles/java/codebuddy/scripts/harness_events.py +35 -2
- package/harness/bundles/java/codebuddy/scripts/harness_plan_finalize.py +6 -1
- package/harness/bundles/java/codex/.harness-build.json +1 -1
- package/harness/bundles/java/codex/harness-apidoc/SKILL.md +13 -3
- package/harness/bundles/java/codex/harness-archive/SKILL.md +18 -4
- package/harness/bundles/java/codex/harness-codebase-map/SKILL.md +13 -3
- package/harness/bundles/java/codex/harness-knowledge-ingest/SKILL.md +13 -3
- package/harness/bundles/java/codex/harness-knowledge-query/SKILL.md +13 -3
- package/harness/bundles/java/codex/harness-package/SKILL.md +13 -3
- package/harness/bundles/java/codex/harness-plan/SKILL.md +13 -3
- 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 +13 -3
- package/harness/bundles/java/codex/harness-run/SKILL.md +13 -3
- package/harness/bundles/java/codex/harness-submit/SKILL.md +13 -3
- package/harness/bundles/java/codex/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-test/SKILL.md +13 -3
- package/harness/bundles/java/codex/scripts/harness_archive.py +173 -20
- package/harness/bundles/java/codex/scripts/harness_context.py +77 -3
- package/harness/bundles/java/codex/scripts/harness_events.py +35 -2
- package/harness/bundles/java/codex/scripts/harness_plan_finalize.py +6 -1
- package/harness/bundles/java/cursor/.harness-build.json +1 -1
- package/harness/bundles/java/cursor/harness-apidoc/SKILL.md +13 -3
- package/harness/bundles/java/cursor/harness-archive/SKILL.md +18 -4
- package/harness/bundles/java/cursor/harness-codebase-map/SKILL.md +13 -3
- package/harness/bundles/java/cursor/harness-knowledge-ingest/SKILL.md +13 -3
- package/harness/bundles/java/cursor/harness-knowledge-query/SKILL.md +13 -3
- package/harness/bundles/java/cursor/harness-package/SKILL.md +13 -3
- package/harness/bundles/java/cursor/harness-plan/SKILL.md +13 -3
- 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 +13 -3
- package/harness/bundles/java/cursor/harness-run/SKILL.md +13 -3
- package/harness/bundles/java/cursor/harness-submit/SKILL.md +13 -3
- package/harness/bundles/java/cursor/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-test/SKILL.md +13 -3
- package/harness/bundles/java/cursor/scripts/harness_archive.py +173 -20
- package/harness/bundles/java/cursor/scripts/harness_context.py +77 -3
- package/harness/bundles/java/cursor/scripts/harness_events.py +35 -2
- package/harness/bundles/java/cursor/scripts/harness_plan_finalize.py +6 -1
- package/harness/manifests/general/claude-code.json +19 -19
- package/harness/manifests/general/codebuddy.json +19 -19
- package/harness/manifests/general/codex.json +19 -19
- package/harness/manifests/general/cursor.json +19 -19
- package/harness/manifests/java/claude-code.json +21 -21
- package/harness/manifests/java/codebuddy.json +21 -21
- package/harness/manifests/java/codex.json +21 -21
- package/harness/manifests/java/cursor.json +21 -21
- package/hunter-workflow-family.json +5 -5
- package/package.json +1 -1
|
@@ -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=611f91f44adcd1d1; overlay=none; agent=codebuddy; do not edit -->
|
|
6
6
|
# harness-knowledge-query
|
|
7
7
|
|
|
8
8
|
项目知识以 Hunter Platform 的服务端索引为唯一真源。客户端只提交查询并消费结果:
|
|
@@ -75,13 +75,23 @@ powershell.exe -Command "npx hunter-harness knowledge query '<用户需求原文
|
|
|
75
75
|
|
|
76
76
|
`events.ndjson` 为唯一事实源(schema_version 3,兼容读取 v1/v2;`note` 承载人类可读摘要);`logs/execution-log.md` 由 `harness_events.py` 渲染,**禁止用 Write/Edit 直接维护**。直接修改的内容会在 `phase.end` 或 finalize 时被完整重建覆盖,属于数据丢失;需要保留的详情必须进入事件 `note`。结构 → [[../protocols/report-pipeline-protocol.md|report-pipeline-protocol]]
|
|
77
77
|
|
|
78
|
+
**`phase.start` 由 `harness_gate.py begin` 写,不要再手工追加一次。** 两条同 `run-id` 的
|
|
79
|
+
`phase.start` 会让 `plan finalize` 以 `PHASE_START_DUPLICATE` 卡死,而且手工那次会先触发
|
|
80
|
+
auto-seal、把正在开始的 attempt 封成 `RECOVERED`。要补触发指令说明就带 `--note` 跑 `gate begin`。
|
|
81
|
+
(重复追加现已按 `(phase, run-id)` 判为幂等 no-op,但依赖它不如不写。)
|
|
82
|
+
|
|
78
83
|
```powershell
|
|
79
|
-
|
|
84
|
+
# 阶段开始:gate begin 负责,note 在这里给
|
|
85
|
+
python <skills-root>/scripts/harness_gate.py begin --change-dir ".harness/changes/<change-name>" --phase <phase> --note "<触发指令>"
|
|
86
|
+
# 阶段中的其他事件才用 append
|
|
87
|
+
python <skills-root>/scripts/harness_events.py append --change-dir ".harness/changes/<change-name>" --phase <phase> --type <command|issue|verification> --run-id <phase-run-id> --note "<摘要>"
|
|
80
88
|
```
|
|
81
89
|
|
|
82
90
|
> **脚本接线**:`harness_events.py append`;`harness_archive.py finalize`;`harness_preflight.py check`;`harness_ledger.py can-reuse`;`harness_service.py ensure/stop`(须 `--files`/`serviceStart.inputFiles`)。JSON 输出按 D13 护栏解读。
|
|
83
91
|
|
|
84
92
|
> **Task 4 §6.1 写入契约**:普通 `append` = 加锁 -> 追加一行 -> fsync -> 解锁,**不 load 历史、不渲染**(O(1),跨进程锁 `events.ndjson.lock`,UUID 用完整 `uuid4().hex` 无需去重扫描)。仅 `--type phase.end` append 在追加成功后渲染一次 `execution-log.md`;显式 `harness_events.py render` 随时从完整 events 重建;`harness_archive.py finalize` 在 collect 前强制 render 一次。高频 command append 期间 log 可能滞后,phase 边界保持最新。
|
|
85
93
|
|
|
86
|
-
每个阶段的 `phase.start` 与对应 `phase.end` 必须复用同一 `--run-id` / `--attempt`;阶段结束必须写 `--status OK|WARN|FAIL|BLOCKED`。重试同一阶段时生成新的 run-id 并增加 `--attempt <n
|
|
94
|
+
每个阶段的 `phase.start` 与对应 `phase.end` 必须复用同一 `--run-id` / `--attempt`;阶段结束必须写 `--status OK|WARN|FAIL|BLOCKED`。重试同一阶段时生成新的 run-id 并增加 `--attempt <n>`,不得覆盖或伪装成一次执行。**`attempt` 按 phase 全局递增,不是按 run-id**:一个 run-id 只绑定一个 attempt,重试必须「新 run-id + 下一个 attempt」两者同时换,只换其一会撞 `EVENT_ATTEMPT_CONFLICT` 或 `PHASE_ALREADY_CLOSED`。已发布的 plan 用 `harness_plan_finalize.py republish` 自动分配两者。
|
|
95
|
+
|
|
96
|
+
阶段跑得久(plan/run 常见)时用 `harness_context.py renew --project . --change <cn> --executor <tool>` 续租;租约到期本身不再阻断 `close`(同一 owner 的过期租约不构成冲突,收据里记 `leaseLapsed`),但续租能让 `view` 的状态如实反映在跑。跨工具继续执行时写 `--executor-tool <codex|claude-code|codebuddy|cursor>`,并在接棒事件写 `--handoff-from-tool` / `--handoff-reason`;也可由 `HUNTER_HARNESS_TOOL/AGENT/MODEL/RUN_ID` 环境变量统一注入。
|
|
87
97
|
> 片段:logging · phase=`knowledge-query` · 成功记录命中摘要,失败记录远端错误码且不做本地回退
|
|
@@ -3,7 +3,7 @@ name: harness-plan
|
|
|
3
3
|
description: 将需求转化为设计文档+实施计划+完整测试场景表,必须在编码前完成。仅当用户显式调用 /harness-plan 或明确要求进入
|
|
4
4
|
Harness 规划阶段时使用;不得因用户描述需求就自动触发。
|
|
5
5
|
---
|
|
6
|
-
<!-- generated by harness_deploy.py; core=
|
|
6
|
+
<!-- generated by harness_deploy.py; core=611f91f44adcd1d1; overlay=none; agent=codebuddy; do not edit -->
|
|
7
7
|
# harness-plan — 需求规划
|
|
8
8
|
|
|
9
9
|
## Purpose
|
|
@@ -135,13 +135,23 @@ change-name 范围变更 → 提示重命名或记 🟡WARN(→ `reference.md`
|
|
|
135
135
|
|
|
136
136
|
`events.ndjson` 为唯一事实源(schema_version 3,兼容读取 v1/v2;`note` 承载人类可读摘要);`logs/execution-log.md` 由 `harness_events.py` 渲染,**禁止用 Write/Edit 直接维护**。直接修改的内容会在 `phase.end` 或 finalize 时被完整重建覆盖,属于数据丢失;需要保留的详情必须进入事件 `note`。结构 → [[../protocols/report-pipeline-protocol.md|report-pipeline-protocol]]
|
|
137
137
|
|
|
138
|
+
**`phase.start` 由 `harness_gate.py begin` 写,不要再手工追加一次。** 两条同 `run-id` 的
|
|
139
|
+
`phase.start` 会让 `plan finalize` 以 `PHASE_START_DUPLICATE` 卡死,而且手工那次会先触发
|
|
140
|
+
auto-seal、把正在开始的 attempt 封成 `RECOVERED`。要补触发指令说明就带 `--note` 跑 `gate begin`。
|
|
141
|
+
(重复追加现已按 `(phase, run-id)` 判为幂等 no-op,但依赖它不如不写。)
|
|
142
|
+
|
|
138
143
|
```powershell
|
|
139
|
-
|
|
144
|
+
# 阶段开始:gate begin 负责,note 在这里给
|
|
145
|
+
python <skills-root>/scripts/harness_gate.py begin --change-dir ".harness/changes/<change-name>" --phase <phase> --note "<触发指令>"
|
|
146
|
+
# 阶段中的其他事件才用 append
|
|
147
|
+
python <skills-root>/scripts/harness_events.py append --change-dir ".harness/changes/<change-name>" --phase <phase> --type <command|issue|verification> --run-id <phase-run-id> --note "<摘要>"
|
|
140
148
|
```
|
|
141
149
|
|
|
142
150
|
> **脚本接线**:`harness_events.py append`;`harness_archive.py finalize`;`harness_preflight.py check`;`harness_ledger.py can-reuse`;`harness_service.py ensure/stop`(须 `--files`/`serviceStart.inputFiles`)。JSON 输出按 D13 护栏解读。
|
|
143
151
|
|
|
144
152
|
> **Task 4 §6.1 写入契约**:普通 `append` = 加锁 -> 追加一行 -> fsync -> 解锁,**不 load 历史、不渲染**(O(1),跨进程锁 `events.ndjson.lock`,UUID 用完整 `uuid4().hex` 无需去重扫描)。仅 `--type phase.end` append 在追加成功后渲染一次 `execution-log.md`;显式 `harness_events.py render` 随时从完整 events 重建;`harness_archive.py finalize` 在 collect 前强制 render 一次。高频 command append 期间 log 可能滞后,phase 边界保持最新。
|
|
145
153
|
|
|
146
|
-
每个阶段的 `phase.start` 与对应 `phase.end` 必须复用同一 `--run-id` / `--attempt`;阶段结束必须写 `--status OK|WARN|FAIL|BLOCKED`。重试同一阶段时生成新的 run-id 并增加 `--attempt <n
|
|
154
|
+
每个阶段的 `phase.start` 与对应 `phase.end` 必须复用同一 `--run-id` / `--attempt`;阶段结束必须写 `--status OK|WARN|FAIL|BLOCKED`。重试同一阶段时生成新的 run-id 并增加 `--attempt <n>`,不得覆盖或伪装成一次执行。**`attempt` 按 phase 全局递增,不是按 run-id**:一个 run-id 只绑定一个 attempt,重试必须「新 run-id + 下一个 attempt」两者同时换,只换其一会撞 `EVENT_ATTEMPT_CONFLICT` 或 `PHASE_ALREADY_CLOSED`。已发布的 plan 用 `harness_plan_finalize.py republish` 自动分配两者。
|
|
155
|
+
|
|
156
|
+
阶段跑得久(plan/run 常见)时用 `harness_context.py renew --project . --change <cn> --executor <tool>` 续租;租约到期本身不再阻断 `close`(同一 owner 的过期租约不构成冲突,收据里记 `leaseLapsed`),但续租能让 `view` 的状态如实反映在跑。跨工具继续执行时写 `--executor-tool <codex|claude-code|codebuddy|cursor>`,并在接棒事件写 `--handoff-from-tool` / `--handoff-reason`;也可由 `HUNTER_HARNESS_TOOL/AGENT/MODEL/RUN_ID` 环境变量统一注入。
|
|
147
157
|
> 片段:logging · phase=`plan`
|
|
@@ -3,7 +3,7 @@ name: harness-pull
|
|
|
3
3
|
description: 从 Hunter Platform 下拉配置/规则/架构/指令(及显式来源分支的分支文件恢复)。仅当用户显式调用
|
|
4
4
|
/harness-pull 或明确说'从平台拉取/恢复'时使用;不得自动触发。
|
|
5
5
|
---
|
|
6
|
-
<!-- generated by harness_deploy.py; core=
|
|
6
|
+
<!-- generated by harness_deploy.py; core=611f91f44adcd1d1; 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=611f91f44adcd1d1; overlay=none; agent=codebuddy; do not edit -->
|
|
7
7
|
# harness-push — 上传到 Hunter Platform
|
|
8
8
|
|
|
9
9
|
## Purpose
|
|
@@ -54,7 +54,11 @@ stderr 里直接给出上面的 republish 命令;`--dry-run` 会列出本地
|
|
|
54
54
|
`ARCHIVE_REMOTE_IMMUTABLE_CONFLICT`(exit 1,**不发起上传**)。
|
|
55
55
|
**已发布的归档无法从客户端补上知识条目**——注入 `candidates/knowledge.json` 必然改变字节,
|
|
56
56
|
服务端拒绝替换。要让旧归档产生知识条目,需要平台侧提供重新索引或归档版本化能力。
|
|
57
|
-
|
|
57
|
+
只想重试**同一个包**(例如知识索引失败)时用 `--retry-retained`:它上传盘上留存的原包字节,
|
|
58
|
+
不重建。**重建得不到已发布的字节**——包 manifest 绑定归档自己的提交,封存目录与 harness 本身
|
|
59
|
+
也都会前进;`--no-knowledge-injection` 只是不注入候选,不等于能复现旧包。
|
|
60
|
+
留存包与远端字节不同(即那是一次失败尝试的残留)时,`--retry-retained` 同样在本地判出
|
|
61
|
+
`ARCHIVE_REMOTE_IMMUTABLE_CONFLICT` 并拒绝上传。
|
|
58
62
|
|
|
59
63
|
## 关键规则
|
|
60
64
|
|
|
@@ -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=611f91f44adcd1d1; overlay=none; agent=codebuddy; do not edit -->
|
|
8
8
|
# harness-review — 代码审查
|
|
9
9
|
|
|
10
10
|
## Purpose
|
|
@@ -153,13 +153,23 @@ review 读取 `state-snapshot.json`(`harness_state.py` / state-layout-protocol
|
|
|
153
153
|
|
|
154
154
|
`events.ndjson` 为唯一事实源(schema_version 3,兼容读取 v1/v2;`note` 承载人类可读摘要);`logs/execution-log.md` 由 `harness_events.py` 渲染,**禁止用 Write/Edit 直接维护**。直接修改的内容会在 `phase.end` 或 finalize 时被完整重建覆盖,属于数据丢失;需要保留的详情必须进入事件 `note`。结构 → [[../protocols/report-pipeline-protocol.md|report-pipeline-protocol]]
|
|
155
155
|
|
|
156
|
+
**`phase.start` 由 `harness_gate.py begin` 写,不要再手工追加一次。** 两条同 `run-id` 的
|
|
157
|
+
`phase.start` 会让 `plan finalize` 以 `PHASE_START_DUPLICATE` 卡死,而且手工那次会先触发
|
|
158
|
+
auto-seal、把正在开始的 attempt 封成 `RECOVERED`。要补触发指令说明就带 `--note` 跑 `gate begin`。
|
|
159
|
+
(重复追加现已按 `(phase, run-id)` 判为幂等 no-op,但依赖它不如不写。)
|
|
160
|
+
|
|
156
161
|
```powershell
|
|
157
|
-
|
|
162
|
+
# 阶段开始:gate begin 负责,note 在这里给
|
|
163
|
+
python <skills-root>/scripts/harness_gate.py begin --change-dir ".harness/changes/<change-name>" --phase <phase> --note "<触发指令>"
|
|
164
|
+
# 阶段中的其他事件才用 append
|
|
165
|
+
python <skills-root>/scripts/harness_events.py append --change-dir ".harness/changes/<change-name>" --phase <phase> --type <command|issue|verification> --run-id <phase-run-id> --note "<摘要>"
|
|
158
166
|
```
|
|
159
167
|
|
|
160
168
|
> **脚本接线**:`harness_events.py append`;`harness_archive.py finalize`;`harness_preflight.py check`;`harness_ledger.py can-reuse`;`harness_service.py ensure/stop`(须 `--files`/`serviceStart.inputFiles`)。JSON 输出按 D13 护栏解读。
|
|
161
169
|
|
|
162
170
|
> **Task 4 §6.1 写入契约**:普通 `append` = 加锁 -> 追加一行 -> fsync -> 解锁,**不 load 历史、不渲染**(O(1),跨进程锁 `events.ndjson.lock`,UUID 用完整 `uuid4().hex` 无需去重扫描)。仅 `--type phase.end` append 在追加成功后渲染一次 `execution-log.md`;显式 `harness_events.py render` 随时从完整 events 重建;`harness_archive.py finalize` 在 collect 前强制 render 一次。高频 command append 期间 log 可能滞后,phase 边界保持最新。
|
|
163
171
|
|
|
164
|
-
每个阶段的 `phase.start` 与对应 `phase.end` 必须复用同一 `--run-id` / `--attempt`;阶段结束必须写 `--status OK|WARN|FAIL|BLOCKED`。重试同一阶段时生成新的 run-id 并增加 `--attempt <n
|
|
172
|
+
每个阶段的 `phase.start` 与对应 `phase.end` 必须复用同一 `--run-id` / `--attempt`;阶段结束必须写 `--status OK|WARN|FAIL|BLOCKED`。重试同一阶段时生成新的 run-id 并增加 `--attempt <n>`,不得覆盖或伪装成一次执行。**`attempt` 按 phase 全局递增,不是按 run-id**:一个 run-id 只绑定一个 attempt,重试必须「新 run-id + 下一个 attempt」两者同时换,只换其一会撞 `EVENT_ATTEMPT_CONFLICT` 或 `PHASE_ALREADY_CLOSED`。已发布的 plan 用 `harness_plan_finalize.py republish` 自动分配两者。
|
|
173
|
+
|
|
174
|
+
阶段跑得久(plan/run 常见)时用 `harness_context.py renew --project . --change <cn> --executor <tool>` 续租;租约到期本身不再阻断 `close`(同一 owner 的过期租约不构成冲突,收据里记 `leaseLapsed`),但续租能让 `view` 的状态如实反映在跑。跨工具继续执行时写 `--executor-tool <codex|claude-code|codebuddy|cursor>`,并在接棒事件写 `--handoff-from-tool` / `--handoff-reason`;也可由 `HUNTER_HARNESS_TOOL/AGENT/MODEL/RUN_ID` 环境变量统一注入。
|
|
165
175
|
> 片段:logging · phase=`review` · 事件:phase/decision/verification/issue/artifact
|
|
@@ -3,7 +3,7 @@ name: harness-run
|
|
|
3
3
|
description: 按变更簇执行 TDD 编码循环(RED→GREEN→REFACTOR→编译验证),逐变更簇实现计划中的任务。仅当用户显式调用
|
|
4
4
|
/harness-run 时使用;不得因用户提到编码/实现就自动触发,也不得被其他阶段 skill 自动接续。
|
|
5
5
|
---
|
|
6
|
-
<!-- generated by harness_deploy.py; core=
|
|
6
|
+
<!-- generated by harness_deploy.py; core=611f91f44adcd1d1; overlay=none; agent=codebuddy; do not edit -->
|
|
7
7
|
# harness-run — 需求编码
|
|
8
8
|
|
|
9
9
|
## Purpose
|
|
@@ -138,13 +138,23 @@ python <skills-root>/scripts/harness_test_guard.py record --project . --change-d
|
|
|
138
138
|
|
|
139
139
|
`events.ndjson` 为唯一事实源(schema_version 3,兼容读取 v1/v2;`note` 承载人类可读摘要);`logs/execution-log.md` 由 `harness_events.py` 渲染,**禁止用 Write/Edit 直接维护**。直接修改的内容会在 `phase.end` 或 finalize 时被完整重建覆盖,属于数据丢失;需要保留的详情必须进入事件 `note`。结构 → [[../protocols/report-pipeline-protocol.md|report-pipeline-protocol]]
|
|
140
140
|
|
|
141
|
+
**`phase.start` 由 `harness_gate.py begin` 写,不要再手工追加一次。** 两条同 `run-id` 的
|
|
142
|
+
`phase.start` 会让 `plan finalize` 以 `PHASE_START_DUPLICATE` 卡死,而且手工那次会先触发
|
|
143
|
+
auto-seal、把正在开始的 attempt 封成 `RECOVERED`。要补触发指令说明就带 `--note` 跑 `gate begin`。
|
|
144
|
+
(重复追加现已按 `(phase, run-id)` 判为幂等 no-op,但依赖它不如不写。)
|
|
145
|
+
|
|
141
146
|
```powershell
|
|
142
|
-
|
|
147
|
+
# 阶段开始:gate begin 负责,note 在这里给
|
|
148
|
+
python <skills-root>/scripts/harness_gate.py begin --change-dir ".harness/changes/<change-name>" --phase <phase> --note "<触发指令>"
|
|
149
|
+
# 阶段中的其他事件才用 append
|
|
150
|
+
python <skills-root>/scripts/harness_events.py append --change-dir ".harness/changes/<change-name>" --phase <phase> --type <command|issue|verification> --run-id <phase-run-id> --note "<摘要>"
|
|
143
151
|
```
|
|
144
152
|
|
|
145
153
|
> **脚本接线**:`harness_events.py append`;`harness_archive.py finalize`;`harness_preflight.py check`;`harness_ledger.py can-reuse`;`harness_service.py ensure/stop`(须 `--files`/`serviceStart.inputFiles`)。JSON 输出按 D13 护栏解读。
|
|
146
154
|
|
|
147
155
|
> **Task 4 §6.1 写入契约**:普通 `append` = 加锁 -> 追加一行 -> fsync -> 解锁,**不 load 历史、不渲染**(O(1),跨进程锁 `events.ndjson.lock`,UUID 用完整 `uuid4().hex` 无需去重扫描)。仅 `--type phase.end` append 在追加成功后渲染一次 `execution-log.md`;显式 `harness_events.py render` 随时从完整 events 重建;`harness_archive.py finalize` 在 collect 前强制 render 一次。高频 command append 期间 log 可能滞后,phase 边界保持最新。
|
|
148
156
|
|
|
149
|
-
每个阶段的 `phase.start` 与对应 `phase.end` 必须复用同一 `--run-id` / `--attempt`;阶段结束必须写 `--status OK|WARN|FAIL|BLOCKED`。重试同一阶段时生成新的 run-id 并增加 `--attempt <n
|
|
157
|
+
每个阶段的 `phase.start` 与对应 `phase.end` 必须复用同一 `--run-id` / `--attempt`;阶段结束必须写 `--status OK|WARN|FAIL|BLOCKED`。重试同一阶段时生成新的 run-id 并增加 `--attempt <n>`,不得覆盖或伪装成一次执行。**`attempt` 按 phase 全局递增,不是按 run-id**:一个 run-id 只绑定一个 attempt,重试必须「新 run-id + 下一个 attempt」两者同时换,只换其一会撞 `EVENT_ATTEMPT_CONFLICT` 或 `PHASE_ALREADY_CLOSED`。已发布的 plan 用 `harness_plan_finalize.py republish` 自动分配两者。
|
|
158
|
+
|
|
159
|
+
阶段跑得久(plan/run 常见)时用 `harness_context.py renew --project . --change <cn> --executor <tool>` 续租;租约到期本身不再阻断 `close`(同一 owner 的过期租约不构成冲突,收据里记 `leaseLapsed`),但续租能让 `view` 的状态如实反映在跑。跨工具继续执行时写 `--executor-tool <codex|claude-code|codebuddy|cursor>`,并在接棒事件写 `--handoff-from-tool` / `--handoff-reason`;也可由 `HUNTER_HARNESS_TOOL/AGENT/MODEL/RUN_ID` 环境变量统一注入。
|
|
150
160
|
> 片段:logging · phase=`run`
|
|
@@ -3,7 +3,7 @@ name: harness-submit
|
|
|
3
3
|
description: 最终提交封装:验证→中文 commit→提交/推送;worktree 模式含 --no-ff 合并回主分支。仅当用户显式调用
|
|
4
4
|
/harness-submit(或 /harness-merge 重入合并段)时使用;用户口头说'提交/commit/push'时必须先确认,不得自动触发。
|
|
5
5
|
---
|
|
6
|
-
<!-- generated by harness_deploy.py; core=
|
|
6
|
+
<!-- generated by harness_deploy.py; core=611f91f44adcd1d1; overlay=none; agent=codebuddy; do not edit -->
|
|
7
7
|
# harness-submit — 最终提交(含 worktree 合并)
|
|
8
8
|
|
|
9
9
|
## Purpose
|
|
@@ -160,13 +160,23 @@ git 经 PowerShell;commit/报告不得含明文密钥。遵循 `../protocols/s
|
|
|
160
160
|
|
|
161
161
|
`events.ndjson` 为唯一事实源(schema_version 3,兼容读取 v1/v2;`note` 承载人类可读摘要);`logs/execution-log.md` 由 `harness_events.py` 渲染,**禁止用 Write/Edit 直接维护**。直接修改的内容会在 `phase.end` 或 finalize 时被完整重建覆盖,属于数据丢失;需要保留的详情必须进入事件 `note`。结构 → [[../protocols/report-pipeline-protocol.md|report-pipeline-protocol]]
|
|
162
162
|
|
|
163
|
+
**`phase.start` 由 `harness_gate.py begin` 写,不要再手工追加一次。** 两条同 `run-id` 的
|
|
164
|
+
`phase.start` 会让 `plan finalize` 以 `PHASE_START_DUPLICATE` 卡死,而且手工那次会先触发
|
|
165
|
+
auto-seal、把正在开始的 attempt 封成 `RECOVERED`。要补触发指令说明就带 `--note` 跑 `gate begin`。
|
|
166
|
+
(重复追加现已按 `(phase, run-id)` 判为幂等 no-op,但依赖它不如不写。)
|
|
167
|
+
|
|
163
168
|
```powershell
|
|
164
|
-
|
|
169
|
+
# 阶段开始:gate begin 负责,note 在这里给
|
|
170
|
+
python <skills-root>/scripts/harness_gate.py begin --change-dir ".harness/changes/<change-name>" --phase <phase> --note "<触发指令>"
|
|
171
|
+
# 阶段中的其他事件才用 append
|
|
172
|
+
python <skills-root>/scripts/harness_events.py append --change-dir ".harness/changes/<change-name>" --phase <phase> --type <command|issue|verification> --run-id <phase-run-id> --note "<摘要>"
|
|
165
173
|
```
|
|
166
174
|
|
|
167
175
|
> **脚本接线**:`harness_events.py append`;`harness_archive.py finalize`;`harness_preflight.py check`;`harness_ledger.py can-reuse`;`harness_service.py ensure/stop`(须 `--files`/`serviceStart.inputFiles`)。JSON 输出按 D13 护栏解读。
|
|
168
176
|
|
|
169
177
|
> **Task 4 §6.1 写入契约**:普通 `append` = 加锁 -> 追加一行 -> fsync -> 解锁,**不 load 历史、不渲染**(O(1),跨进程锁 `events.ndjson.lock`,UUID 用完整 `uuid4().hex` 无需去重扫描)。仅 `--type phase.end` append 在追加成功后渲染一次 `execution-log.md`;显式 `harness_events.py render` 随时从完整 events 重建;`harness_archive.py finalize` 在 collect 前强制 render 一次。高频 command append 期间 log 可能滞后,phase 边界保持最新。
|
|
170
178
|
|
|
171
|
-
每个阶段的 `phase.start` 与对应 `phase.end` 必须复用同一 `--run-id` / `--attempt`;阶段结束必须写 `--status OK|WARN|FAIL|BLOCKED`。重试同一阶段时生成新的 run-id 并增加 `--attempt <n
|
|
179
|
+
每个阶段的 `phase.start` 与对应 `phase.end` 必须复用同一 `--run-id` / `--attempt`;阶段结束必须写 `--status OK|WARN|FAIL|BLOCKED`。重试同一阶段时生成新的 run-id 并增加 `--attempt <n>`,不得覆盖或伪装成一次执行。**`attempt` 按 phase 全局递增,不是按 run-id**:一个 run-id 只绑定一个 attempt,重试必须「新 run-id + 下一个 attempt」两者同时换,只换其一会撞 `EVENT_ATTEMPT_CONFLICT` 或 `PHASE_ALREADY_CLOSED`。已发布的 plan 用 `harness_plan_finalize.py republish` 自动分配两者。
|
|
180
|
+
|
|
181
|
+
阶段跑得久(plan/run 常见)时用 `harness_context.py renew --project . --change <cn> --executor <tool>` 续租;租约到期本身不再阻断 `close`(同一 owner 的过期租约不构成冲突,收据里记 `leaseLapsed`),但续租能让 `view` 的状态如实反映在跑。跨工具继续执行时写 `--executor-tool <codex|claude-code|codebuddy|cursor>`,并在接棒事件写 `--handoff-from-tool` / `--handoff-reason`;也可由 `HUNTER_HARNESS_TOOL/AGENT/MODEL/RUN_ID` 环境变量统一注入。
|
|
172
182
|
> 片段:logging · phase=`submit`/`merge`
|
|
@@ -4,7 +4,7 @@ description: Use when the user asks to synchronize, refresh, or validate Harness
|
|
|
4
4
|
metadata, adapters, remote knowledge ownership, instruction entrypoints,
|
|
5
5
|
config origins, or CodeGraph status.
|
|
6
6
|
---
|
|
7
|
-
<!-- generated by harness_deploy.py; core=
|
|
7
|
+
<!-- generated by harness_deploy.py; core=611f91f44adcd1d1; 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=611f91f44adcd1d1; overlay=none; agent=codebuddy; do not edit -->
|
|
7
7
|
# harness-test — 测试执行
|
|
8
8
|
|
|
9
9
|
## Purpose
|
|
@@ -219,13 +219,23 @@ python <skills-root>/scripts/harness_test_guard.py record --project . --change-d
|
|
|
219
219
|
|
|
220
220
|
`events.ndjson` 为唯一事实源(schema_version 3,兼容读取 v1/v2;`note` 承载人类可读摘要);`logs/execution-log.md` 由 `harness_events.py` 渲染,**禁止用 Write/Edit 直接维护**。直接修改的内容会在 `phase.end` 或 finalize 时被完整重建覆盖,属于数据丢失;需要保留的详情必须进入事件 `note`。结构 → [[../protocols/report-pipeline-protocol.md|report-pipeline-protocol]]
|
|
221
221
|
|
|
222
|
+
**`phase.start` 由 `harness_gate.py begin` 写,不要再手工追加一次。** 两条同 `run-id` 的
|
|
223
|
+
`phase.start` 会让 `plan finalize` 以 `PHASE_START_DUPLICATE` 卡死,而且手工那次会先触发
|
|
224
|
+
auto-seal、把正在开始的 attempt 封成 `RECOVERED`。要补触发指令说明就带 `--note` 跑 `gate begin`。
|
|
225
|
+
(重复追加现已按 `(phase, run-id)` 判为幂等 no-op,但依赖它不如不写。)
|
|
226
|
+
|
|
222
227
|
```powershell
|
|
223
|
-
|
|
228
|
+
# 阶段开始:gate begin 负责,note 在这里给
|
|
229
|
+
python <skills-root>/scripts/harness_gate.py begin --change-dir ".harness/changes/<change-name>" --phase <phase> --note "<触发指令>"
|
|
230
|
+
# 阶段中的其他事件才用 append
|
|
231
|
+
python <skills-root>/scripts/harness_events.py append --change-dir ".harness/changes/<change-name>" --phase <phase> --type <command|issue|verification> --run-id <phase-run-id> --note "<摘要>"
|
|
224
232
|
```
|
|
225
233
|
|
|
226
234
|
> **脚本接线**:`harness_events.py append`;`harness_archive.py finalize`;`harness_preflight.py check`;`harness_ledger.py can-reuse`;`harness_service.py ensure/stop`(须 `--files`/`serviceStart.inputFiles`)。JSON 输出按 D13 护栏解读。
|
|
227
235
|
|
|
228
236
|
> **Task 4 §6.1 写入契约**:普通 `append` = 加锁 -> 追加一行 -> fsync -> 解锁,**不 load 历史、不渲染**(O(1),跨进程锁 `events.ndjson.lock`,UUID 用完整 `uuid4().hex` 无需去重扫描)。仅 `--type phase.end` append 在追加成功后渲染一次 `execution-log.md`;显式 `harness_events.py render` 随时从完整 events 重建;`harness_archive.py finalize` 在 collect 前强制 render 一次。高频 command append 期间 log 可能滞后,phase 边界保持最新。
|
|
229
237
|
|
|
230
|
-
每个阶段的 `phase.start` 与对应 `phase.end` 必须复用同一 `--run-id` / `--attempt`;阶段结束必须写 `--status OK|WARN|FAIL|BLOCKED`。重试同一阶段时生成新的 run-id 并增加 `--attempt <n
|
|
238
|
+
每个阶段的 `phase.start` 与对应 `phase.end` 必须复用同一 `--run-id` / `--attempt`;阶段结束必须写 `--status OK|WARN|FAIL|BLOCKED`。重试同一阶段时生成新的 run-id 并增加 `--attempt <n>`,不得覆盖或伪装成一次执行。**`attempt` 按 phase 全局递增,不是按 run-id**:一个 run-id 只绑定一个 attempt,重试必须「新 run-id + 下一个 attempt」两者同时换,只换其一会撞 `EVENT_ATTEMPT_CONFLICT` 或 `PHASE_ALREADY_CLOSED`。已发布的 plan 用 `harness_plan_finalize.py republish` 自动分配两者。
|
|
239
|
+
|
|
240
|
+
阶段跑得久(plan/run 常见)时用 `harness_context.py renew --project . --change <cn> --executor <tool>` 续租;租约到期本身不再阻断 `close`(同一 owner 的过期租约不构成冲突,收据里记 `leaseLapsed`),但续租能让 `view` 的状态如实反映在跑。跨工具继续执行时写 `--executor-tool <codex|claude-code|codebuddy|cursor>`,并在接棒事件写 `--handoff-from-tool` / `--handoff-reason`;也可由 `HUNTER_HARNESS_TOOL/AGENT/MODEL/RUN_ID` 环境变量统一注入。
|
|
231
241
|
> 片段:logging · phase=`test` · 事件:phase/command/verification/decision/issue/artifact
|
|
@@ -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)
|