@hunter-harness/workflow-harness 0.4.9 → 0.4.11
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 +1 -1
- package/harness/bundles/general/claude-code/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-execute/SKILL.md +5 -3
- package/harness/bundles/general/claude-code/harness-execute/testing-pitfalls-java.md +7 -0
- package/harness/bundles/general/claude-code/harness-knowledge-ingest/SKILL.md +7 -1
- package/harness/bundles/general/claude-code/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-plan/SKILL.md +3 -3
- package/harness/bundles/general/claude-code/harness-plan/checklist.md +1 -0
- package/harness/bundles/general/claude-code/harness-plan/reference.md +8 -0
- package/harness/bundles/general/claude-code/harness-pull/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-push/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-review/SKILL.md +12 -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/scripts/harness_context.py +71 -8
- package/harness/bundles/general/claude-code/scripts/harness_events.py +29 -19
- package/harness/bundles/general/claude-code/scripts/harness_fixback.py +37 -1
- package/harness/bundles/general/claude-code/scripts/harness_gate.py +124 -10
- package/harness/bundles/general/claude-code/scripts/harness_ledger.py +84 -4
- package/harness/bundles/general/claude-code/scripts/harness_process.py +18 -0
- package/harness/bundles/general/claude-code/scripts/harness_runtime.py +47 -1
- package/harness/bundles/general/codebuddy/.harness-build.json +1 -1
- package/harness/bundles/general/codebuddy/harness-archive/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-execute/SKILL.md +5 -3
- package/harness/bundles/general/codebuddy/harness-execute/testing-pitfalls-java.md +7 -0
- package/harness/bundles/general/codebuddy/harness-knowledge-ingest/SKILL.md +7 -1
- package/harness/bundles/general/codebuddy/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-plan/SKILL.md +3 -3
- package/harness/bundles/general/codebuddy/harness-plan/checklist.md +1 -0
- package/harness/bundles/general/codebuddy/harness-plan/reference.md +8 -0
- package/harness/bundles/general/codebuddy/harness-pull/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-push/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-review/SKILL.md +12 -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/scripts/harness_context.py +71 -8
- package/harness/bundles/general/codebuddy/scripts/harness_events.py +29 -19
- package/harness/bundles/general/codebuddy/scripts/harness_fixback.py +37 -1
- package/harness/bundles/general/codebuddy/scripts/harness_gate.py +124 -10
- package/harness/bundles/general/codebuddy/scripts/harness_ledger.py +84 -4
- package/harness/bundles/general/codebuddy/scripts/harness_process.py +18 -0
- package/harness/bundles/general/codebuddy/scripts/harness_runtime.py +47 -1
- package/harness/bundles/general/codex/.harness-build.json +1 -1
- package/harness/bundles/general/codex/harness-archive/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-execute/SKILL.md +5 -3
- package/harness/bundles/general/codex/harness-execute/testing-pitfalls-java.md +7 -0
- package/harness/bundles/general/codex/harness-knowledge-ingest/SKILL.md +7 -1
- package/harness/bundles/general/codex/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-plan/SKILL.md +3 -3
- package/harness/bundles/general/codex/harness-plan/checklist.md +1 -0
- package/harness/bundles/general/codex/harness-plan/reference.md +8 -0
- package/harness/bundles/general/codex/harness-pull/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-push/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-review/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/scripts/harness_context.py +71 -8
- package/harness/bundles/general/codex/scripts/harness_events.py +29 -19
- package/harness/bundles/general/codex/scripts/harness_fixback.py +37 -1
- package/harness/bundles/general/codex/scripts/harness_gate.py +124 -10
- package/harness/bundles/general/codex/scripts/harness_ledger.py +84 -4
- package/harness/bundles/general/codex/scripts/harness_process.py +18 -0
- package/harness/bundles/general/codex/scripts/harness_runtime.py +47 -1
- package/harness/bundles/general/cursor/.harness-build.json +1 -1
- package/harness/bundles/general/cursor/harness-archive/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-execute/SKILL.md +5 -3
- package/harness/bundles/general/cursor/harness-execute/testing-pitfalls-java.md +7 -0
- package/harness/bundles/general/cursor/harness-knowledge-ingest/SKILL.md +7 -1
- package/harness/bundles/general/cursor/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-plan/SKILL.md +3 -3
- package/harness/bundles/general/cursor/harness-plan/checklist.md +1 -0
- package/harness/bundles/general/cursor/harness-plan/reference.md +8 -0
- package/harness/bundles/general/cursor/harness-pull/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-push/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-review/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/scripts/harness_context.py +71 -8
- package/harness/bundles/general/cursor/scripts/harness_events.py +29 -19
- package/harness/bundles/general/cursor/scripts/harness_fixback.py +37 -1
- package/harness/bundles/general/cursor/scripts/harness_gate.py +124 -10
- package/harness/bundles/general/cursor/scripts/harness_ledger.py +84 -4
- package/harness/bundles/general/cursor/scripts/harness_process.py +18 -0
- package/harness/bundles/general/cursor/scripts/harness_runtime.py +47 -1
- package/harness/bundles/general/pi/.harness-build.json +1 -1
- package/harness/bundles/general/pi/harness-archive/SKILL.md +1 -1
- package/harness/bundles/general/pi/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/general/pi/harness-execute/SKILL.md +5 -3
- package/harness/bundles/general/pi/harness-execute/testing-pitfalls-java.md +7 -0
- package/harness/bundles/general/pi/harness-knowledge-ingest/SKILL.md +7 -1
- package/harness/bundles/general/pi/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/pi/harness-plan/SKILL.md +3 -3
- package/harness/bundles/general/pi/harness-plan/checklist.md +1 -0
- package/harness/bundles/general/pi/harness-plan/reference.md +8 -0
- package/harness/bundles/general/pi/harness-pull/SKILL.md +1 -1
- package/harness/bundles/general/pi/harness-push/SKILL.md +1 -1
- package/harness/bundles/general/pi/harness-review/SKILL.md +1 -1
- package/harness/bundles/general/pi/harness-submit/SKILL.md +1 -1
- package/harness/bundles/general/pi/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/pi/scripts/harness_context.py +71 -8
- package/harness/bundles/general/pi/scripts/harness_events.py +29 -19
- package/harness/bundles/general/pi/scripts/harness_fixback.py +37 -1
- package/harness/bundles/general/pi/scripts/harness_gate.py +124 -10
- package/harness/bundles/general/pi/scripts/harness_ledger.py +84 -4
- package/harness/bundles/general/pi/scripts/harness_process.py +18 -0
- package/harness/bundles/general/pi/scripts/harness_runtime.py +47 -1
- 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 +1 -1
- package/harness/bundles/java/claude-code/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-execute/SKILL.md +5 -3
- package/harness/bundles/java/claude-code/harness-execute/testing-pitfalls-java.md +1 -0
- package/harness/bundles/java/claude-code/harness-knowledge-ingest/SKILL.md +7 -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 +3 -3
- package/harness/bundles/java/claude-code/harness-plan/checklist.md +1 -0
- package/harness/bundles/java/claude-code/harness-plan/reference.md +8 -0
- package/harness/bundles/java/claude-code/harness-pull/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-push/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-review/SKILL.md +12 -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/scripts/harness_context.py +71 -8
- package/harness/bundles/java/claude-code/scripts/harness_events.py +29 -19
- package/harness/bundles/java/claude-code/scripts/harness_fixback.py +37 -1
- package/harness/bundles/java/claude-code/scripts/harness_gate.py +124 -10
- package/harness/bundles/java/claude-code/scripts/harness_ledger.py +84 -4
- package/harness/bundles/java/claude-code/scripts/harness_process.py +18 -0
- package/harness/bundles/java/claude-code/scripts/harness_runtime.py +47 -1
- 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 +1 -1
- package/harness/bundles/java/codebuddy/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-execute/SKILL.md +5 -3
- package/harness/bundles/java/codebuddy/harness-execute/testing-pitfalls-java.md +1 -0
- package/harness/bundles/java/codebuddy/harness-knowledge-ingest/SKILL.md +7 -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 +3 -3
- package/harness/bundles/java/codebuddy/harness-plan/checklist.md +1 -0
- package/harness/bundles/java/codebuddy/harness-plan/reference.md +8 -0
- package/harness/bundles/java/codebuddy/harness-pull/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-push/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-review/SKILL.md +12 -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/scripts/harness_context.py +71 -8
- package/harness/bundles/java/codebuddy/scripts/harness_events.py +29 -19
- package/harness/bundles/java/codebuddy/scripts/harness_fixback.py +37 -1
- package/harness/bundles/java/codebuddy/scripts/harness_gate.py +124 -10
- package/harness/bundles/java/codebuddy/scripts/harness_ledger.py +84 -4
- package/harness/bundles/java/codebuddy/scripts/harness_process.py +18 -0
- package/harness/bundles/java/codebuddy/scripts/harness_runtime.py +47 -1
- 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 +1 -1
- package/harness/bundles/java/codex/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-execute/SKILL.md +5 -3
- package/harness/bundles/java/codex/harness-execute/testing-pitfalls-java.md +1 -0
- package/harness/bundles/java/codex/harness-knowledge-ingest/SKILL.md +7 -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 +3 -3
- package/harness/bundles/java/codex/harness-plan/checklist.md +1 -0
- package/harness/bundles/java/codex/harness-plan/reference.md +8 -0
- package/harness/bundles/java/codex/harness-pull/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-push/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-review/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/scripts/harness_context.py +71 -8
- package/harness/bundles/java/codex/scripts/harness_events.py +29 -19
- package/harness/bundles/java/codex/scripts/harness_fixback.py +37 -1
- package/harness/bundles/java/codex/scripts/harness_gate.py +124 -10
- package/harness/bundles/java/codex/scripts/harness_ledger.py +84 -4
- package/harness/bundles/java/codex/scripts/harness_process.py +18 -0
- package/harness/bundles/java/codex/scripts/harness_runtime.py +47 -1
- 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 +1 -1
- package/harness/bundles/java/cursor/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-execute/SKILL.md +5 -3
- package/harness/bundles/java/cursor/harness-execute/testing-pitfalls-java.md +1 -0
- package/harness/bundles/java/cursor/harness-knowledge-ingest/SKILL.md +7 -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 +3 -3
- package/harness/bundles/java/cursor/harness-plan/checklist.md +1 -0
- package/harness/bundles/java/cursor/harness-plan/reference.md +8 -0
- package/harness/bundles/java/cursor/harness-pull/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-push/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-review/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/scripts/harness_context.py +71 -8
- package/harness/bundles/java/cursor/scripts/harness_events.py +29 -19
- package/harness/bundles/java/cursor/scripts/harness_fixback.py +37 -1
- package/harness/bundles/java/cursor/scripts/harness_gate.py +124 -10
- package/harness/bundles/java/cursor/scripts/harness_ledger.py +84 -4
- package/harness/bundles/java/cursor/scripts/harness_process.py +18 -0
- package/harness/bundles/java/cursor/scripts/harness_runtime.py +47 -1
- package/harness/bundles/java/pi/.harness-build.json +1 -1
- package/harness/bundles/java/pi/harness-apidoc/SKILL.md +1 -1
- package/harness/bundles/java/pi/harness-archive/SKILL.md +1 -1
- package/harness/bundles/java/pi/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/java/pi/harness-execute/SKILL.md +5 -3
- package/harness/bundles/java/pi/harness-execute/testing-pitfalls-java.md +1 -0
- package/harness/bundles/java/pi/harness-knowledge-ingest/SKILL.md +7 -1
- package/harness/bundles/java/pi/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/java/pi/harness-package/SKILL.md +1 -1
- package/harness/bundles/java/pi/harness-plan/SKILL.md +3 -3
- package/harness/bundles/java/pi/harness-plan/checklist.md +1 -0
- package/harness/bundles/java/pi/harness-plan/reference.md +8 -0
- package/harness/bundles/java/pi/harness-pull/SKILL.md +1 -1
- package/harness/bundles/java/pi/harness-push/SKILL.md +1 -1
- package/harness/bundles/java/pi/harness-review/SKILL.md +1 -1
- package/harness/bundles/java/pi/harness-submit/SKILL.md +1 -1
- package/harness/bundles/java/pi/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/pi/scripts/harness_context.py +71 -8
- package/harness/bundles/java/pi/scripts/harness_events.py +29 -19
- package/harness/bundles/java/pi/scripts/harness_fixback.py +37 -1
- package/harness/bundles/java/pi/scripts/harness_gate.py +124 -10
- package/harness/bundles/java/pi/scripts/harness_ledger.py +84 -4
- package/harness/bundles/java/pi/scripts/harness_process.py +18 -0
- package/harness/bundles/java/pi/scripts/harness_runtime.py +47 -1
- package/harness/manifests/general/claude-code.json +22 -22
- package/harness/manifests/general/codebuddy.json +22 -22
- package/harness/manifests/general/codex.json +22 -22
- package/harness/manifests/general/cursor.json +22 -22
- package/harness/manifests/general/pi.json +22 -22
- package/harness/manifests/java/claude-code.json +24 -24
- package/harness/manifests/java/codebuddy.json +24 -24
- package/harness/manifests/java/codex.json +24 -24
- package/harness/manifests/java/cursor.json +24 -24
- package/harness/manifests/java/pi.json +24 -24
- package/hunter-workflow-family.json +2 -2
- package/package.json +1 -1
|
@@ -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=cdc01b6329bacd46; overlay=none; agent=codebuddy; do not edit -->
|
|
8
8
|
# harness-review — 代码审查
|
|
9
9
|
|
|
10
10
|
## Purpose
|
|
@@ -57,6 +57,17 @@ review 默认优先在独立上下文执行,避免主会话已形成的实现
|
|
|
57
57
|
|
|
58
58
|
无隔离能力、定义损坏、spawn 失败、空返回或只有元数据时,不重试,立即由主会话按同一 6 维度检查清单完成审查。无论是否委派,都必须追加一条结构化 `decision` 事件:`--execution-mode delegated|inline` 标明执行方式;委派时写 `--executor-agent harness-reviewer --decision-reason-code REVIEW_DELEGATED`;回退时只写一个 `--fallback-reason-code REVIEW_INLINE_UNAVAILABLE|REVIEW_INLINE_SPAWN_FAILED|REVIEW_INLINE_INVALID_RESULT`。`decision` 和 `reason` 必须用中文完整说明,禁止把原因码拼进正文。正常主会话评审是可接受结果,不显示成流程故障;`fallbackPolicy=inline-no-retry`。
|
|
59
59
|
|
|
60
|
+
完整示例(委派路径,可直接复制改参):
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
python <skills-root>/scripts/harness_events.py append --change-dir ".harness/changes/<cn>" \
|
|
64
|
+
--phase review --type decision --execution-mode delegated \
|
|
65
|
+
--executor-agent harness-reviewer --decision-reason-code REVIEW_DELEGATED \
|
|
66
|
+
--decision "已使用独立评审完成 6 维度审查" --reason "固定 reviewer 可用,避免主会话实现结论影响审查" --json
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
注意:`decision` 事件**不接受** `--name`/`--status`(那是 verification 的字段);`issue` 事件必须带 `--severity`。
|
|
70
|
+
|
|
60
71
|
项目规模小、风险低或没有 CodeGraph 都不是跳过独立评审的理由。原因码是稳定机器字段,仅用于协议与技术详情;面向用户的事件摘要必须写成「已使用独立评审」或「当前环境没有可用的隔离评审能力,已由主会话完成评审」等中文说明。
|
|
61
72
|
|
|
62
73
|
3. **持久化报告与事实 sidecar(强制,主会话)** — Agent 返回后主会话 Write 到 `reports/review/review-report-*.md`,并调用 `python <skills-root>/scripts/harness_review.py scaffold --change-dir <change-dir> [--run-id <id>]` 生成 findings 写入骨架(runId 缺省从 events.ndjson 最新 review phase.start 推断),补齐每一条发现后经 `harness_review.py write-findings --change-dir <change-dir> --stdin` 落地。每条发现必须填写 `fixbackAction=code|manual|workflow`:只有确实需要修改产品代码的项使用 `code`;人工验收用 `manual`;流程、暂存或工具使用建议用 `workflow`。权威计数来自 `reports/review/review-findings.json`,不是 Markdown。任一写入缺失 → 评审未完整结束,不得关门。
|
|
@@ -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=cdc01b6329bacd46; 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=cdc01b6329bacd46; overlay=none; agent=codebuddy; do not edit -->
|
|
8
8
|
# harness-sync
|
|
9
9
|
|
|
10
10
|
## Purpose
|
|
@@ -478,6 +478,55 @@ def _phase_has_ended(event_paths: list[Path], phase: str) -> bool:
|
|
|
478
478
|
)
|
|
479
479
|
|
|
480
480
|
|
|
481
|
+
def _ensure_phase_end_event(
|
|
482
|
+
contract_root: Path,
|
|
483
|
+
*,
|
|
484
|
+
phase: str,
|
|
485
|
+
status: str,
|
|
486
|
+
executor: str,
|
|
487
|
+
) -> dict[str, Any] | None:
|
|
488
|
+
"""补齐 phase.start/end 事件对(P0-2)。
|
|
489
|
+
|
|
490
|
+
平台 Run 计时由 events-sync 上报的 phase.start/end 事件对驱动。不经
|
|
491
|
+
gate close 收尾的阶段(典型:plan 走 finalize + context close)只写
|
|
492
|
+
transitions 收据,phase.end 永远缺席,平台计时永不停——连续两个 change
|
|
493
|
+
复现,属结构性缺口(2026-08-30 sales-insight-agent demo-datasource 实测)。
|
|
494
|
+
close_transition 是所有跨阶段推进的共同通道,在此按事件配对补齐:
|
|
495
|
+
有 start 无 end → 复用 start 的 run_id/attempt 代写 end;已有 end(gate
|
|
496
|
+
close 先行写入)或连 start 都没有 → 不动,返回 None。
|
|
497
|
+
"""
|
|
498
|
+
import harness_events as he
|
|
499
|
+
|
|
500
|
+
events_file = contract_root / "events.ndjson"
|
|
501
|
+
events = _read_ndjson(events_file)
|
|
502
|
+
starts = [
|
|
503
|
+
item
|
|
504
|
+
for item in events
|
|
505
|
+
if hpaths.resolve_phase_name(item.get("phase")) == phase
|
|
506
|
+
and item.get("type") == "phase.start"
|
|
507
|
+
]
|
|
508
|
+
if not starts or any(
|
|
509
|
+
hpaths.resolve_phase_name(item.get("phase")) == phase
|
|
510
|
+
and item.get("type") == "phase.end"
|
|
511
|
+
for item in events
|
|
512
|
+
):
|
|
513
|
+
return None
|
|
514
|
+
start = starts[-1]
|
|
515
|
+
result = he.append_event(
|
|
516
|
+
contract_root,
|
|
517
|
+
phase=phase,
|
|
518
|
+
type_="phase.end",
|
|
519
|
+
run_id=str(start.get("run_id") or f"{phase}_unknown"),
|
|
520
|
+
attempt=int(start.get("attempt") or 1),
|
|
521
|
+
status=status,
|
|
522
|
+
executor_tool=executor,
|
|
523
|
+
note="context close 自动补齐 phase.end(平台计时事件对配对)",
|
|
524
|
+
)
|
|
525
|
+
if not result.get("ok"):
|
|
526
|
+
return {"code": "PHASE_END_PAIR_FAILED", "detail": result}
|
|
527
|
+
return {"code": "PHASE_END_AUTO_PAIRED", "event": result.get("event")}
|
|
528
|
+
|
|
529
|
+
|
|
481
530
|
def _reselect_review_fixback(
|
|
482
531
|
project: Path,
|
|
483
532
|
change: str,
|
|
@@ -1334,6 +1383,12 @@ def close_transition(
|
|
|
1334
1383
|
and latest.get("artifacts") == artifact_entries
|
|
1335
1384
|
and (executor is None or latest.get("executor") == executor)
|
|
1336
1385
|
):
|
|
1386
|
+
paired = _ensure_phase_end_event(
|
|
1387
|
+
contract_root,
|
|
1388
|
+
phase=from_phase,
|
|
1389
|
+
status=status,
|
|
1390
|
+
executor=executor or str(latest.get("executor") or "unknown"),
|
|
1391
|
+
)
|
|
1337
1392
|
return {
|
|
1338
1393
|
"ok": True,
|
|
1339
1394
|
"code": "TRANSITION_ALREADY_CLOSED",
|
|
@@ -1341,6 +1396,7 @@ def close_transition(
|
|
|
1341
1396
|
"receipt": latest,
|
|
1342
1397
|
"path": str(paths["transitions"]),
|
|
1343
1398
|
"invalidation": None,
|
|
1399
|
+
"phaseEndPair": paired,
|
|
1344
1400
|
}
|
|
1345
1401
|
return {"ok": False, "code": "CONTEXT_LEASE_REQUIRED"}
|
|
1346
1402
|
try:
|
|
@@ -1397,6 +1453,12 @@ def close_transition(
|
|
|
1397
1453
|
receipt["receiptHash"] = _payload_hash(receipt)
|
|
1398
1454
|
_append_ndjson(paths["transitions"], receipt)
|
|
1399
1455
|
paths["lease"].unlink(missing_ok=True)
|
|
1456
|
+
paired = _ensure_phase_end_event(
|
|
1457
|
+
contract_root,
|
|
1458
|
+
phase=from_phase,
|
|
1459
|
+
status=status,
|
|
1460
|
+
executor=effective_executor,
|
|
1461
|
+
)
|
|
1400
1462
|
invalidation = None
|
|
1401
1463
|
if to_phase == "execute" and from_phase in {"execute", "review"}:
|
|
1402
1464
|
invalidation = _invalidate_for_fixback(
|
|
@@ -1411,6 +1473,7 @@ def close_transition(
|
|
|
1411
1473
|
"receipt": receipt,
|
|
1412
1474
|
"path": str(paths["transitions"]),
|
|
1413
1475
|
"invalidation": invalidation,
|
|
1476
|
+
"phaseEndPair": paired,
|
|
1414
1477
|
}
|
|
1415
1478
|
|
|
1416
1479
|
|
|
@@ -1898,7 +1961,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
1898
1961
|
prepare = sub.add_parser("prepare")
|
|
1899
1962
|
prepare.add_argument("--json", action="store_true")
|
|
1900
1963
|
prepare.add_argument("--project", required=True, type=Path)
|
|
1901
|
-
prepare.add_argument("--change")
|
|
1964
|
+
prepare.add_argument("--change", "--change-dir", dest="change")
|
|
1902
1965
|
prepare.add_argument("--phase", required=True)
|
|
1903
1966
|
prepare.add_argument("--executor", required=True)
|
|
1904
1967
|
prepare.add_argument("--title")
|
|
@@ -1907,7 +1970,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
1907
1970
|
bootstrap = sub.add_parser("bootstrap-plan")
|
|
1908
1971
|
bootstrap.add_argument("--json", action="store_true")
|
|
1909
1972
|
bootstrap.add_argument("--project", required=True, type=Path)
|
|
1910
|
-
bootstrap.add_argument("--change", required=True)
|
|
1973
|
+
bootstrap.add_argument("--change", "--change-dir", dest="change", required=True)
|
|
1911
1974
|
bootstrap.add_argument("--executor", required=True)
|
|
1912
1975
|
bootstrap.add_argument("--title")
|
|
1913
1976
|
bootstrap.add_argument("--stage", default="plan", choices=["plan", "post-run"])
|
|
@@ -1915,7 +1978,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
1915
1978
|
close = sub.add_parser("close")
|
|
1916
1979
|
close.add_argument("--json", action="store_true")
|
|
1917
1980
|
close.add_argument("--project", required=True, type=Path)
|
|
1918
|
-
close.add_argument("--change", required=True)
|
|
1981
|
+
close.add_argument("--change", "--change-dir", dest="change", required=True)
|
|
1919
1982
|
close.add_argument("--from-phase", required=True)
|
|
1920
1983
|
close.add_argument("--to-phase", required=True)
|
|
1921
1984
|
close.add_argument("--executor", required=True)
|
|
@@ -1924,7 +1987,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
1924
1987
|
begin = sub.add_parser("begin")
|
|
1925
1988
|
begin.add_argument("--json", action="store_true")
|
|
1926
1989
|
begin.add_argument("--project", required=True, type=Path)
|
|
1927
|
-
begin.add_argument("--change", required=True)
|
|
1990
|
+
begin.add_argument("--change", "--change-dir", dest="change", required=True)
|
|
1928
1991
|
begin.add_argument("--phase", required=True)
|
|
1929
1992
|
begin.add_argument("--executor", required=True)
|
|
1930
1993
|
handoff = sub.add_parser(
|
|
@@ -1933,7 +1996,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
1933
1996
|
)
|
|
1934
1997
|
handoff.add_argument("--json", action="store_true")
|
|
1935
1998
|
handoff.add_argument("--project", required=True, type=Path)
|
|
1936
|
-
handoff.add_argument("--change", required=True)
|
|
1999
|
+
handoff.add_argument("--change", "--change-dir", dest="change", required=True)
|
|
1937
2000
|
handoff.add_argument("--to-phase", required=True)
|
|
1938
2001
|
handoff.add_argument(
|
|
1939
2002
|
"--from-phase",
|
|
@@ -1946,20 +2009,20 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
1946
2009
|
)
|
|
1947
2010
|
renew.add_argument("--json", action="store_true")
|
|
1948
2011
|
renew.add_argument("--project", required=True, type=Path)
|
|
1949
|
-
renew.add_argument("--change", required=True)
|
|
2012
|
+
renew.add_argument("--change", "--change-dir", dest="change", required=True)
|
|
1950
2013
|
renew.add_argument("--executor", required=True)
|
|
1951
2014
|
renew.add_argument("--ttl-seconds", type=int, default=3600)
|
|
1952
2015
|
configure = sub.add_parser("configure-plan")
|
|
1953
2016
|
configure.add_argument("--json", action="store_true")
|
|
1954
2017
|
configure.add_argument("--project", required=True, type=Path)
|
|
1955
|
-
configure.add_argument("--change", required=True)
|
|
2018
|
+
configure.add_argument("--change", "--change-dir", dest="change", required=True)
|
|
1956
2019
|
configure.add_argument("--phases", required=True)
|
|
1957
2020
|
configure.add_argument("--operator", required=True)
|
|
1958
2021
|
configure.add_argument("--reason", required=True)
|
|
1959
2022
|
view = sub.add_parser("view")
|
|
1960
2023
|
view.add_argument("--json", action="store_true")
|
|
1961
2024
|
view.add_argument("--project", required=True, type=Path)
|
|
1962
|
-
view.add_argument("--change", required=True)
|
|
2025
|
+
view.add_argument("--change", "--change-dir", dest="change", required=True)
|
|
1963
2026
|
return parser
|
|
1964
2027
|
|
|
1965
2028
|
|
|
@@ -884,22 +884,32 @@ def build_event(args: argparse.Namespace, existing: list[dict[str, Any]]) -> dic
|
|
|
884
884
|
def validate_append_event(args: argparse.Namespace) -> tuple[str, str] | None:
|
|
885
885
|
"""Validate type-specific append fields before any file is mutated."""
|
|
886
886
|
event_type = str(args.type)
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
887
|
+
missing = [
|
|
888
|
+
f"--{field.replace('_', '-')}"
|
|
889
|
+
for field in _EVENT_REQUIRED_FIELDS.get(event_type, ())
|
|
890
|
+
if getattr(args, field, None) is None
|
|
891
|
+
or (isinstance(getattr(args, field, None), str)
|
|
892
|
+
and not getattr(args, field).strip())
|
|
893
|
+
]
|
|
894
|
+
if missing:
|
|
895
|
+
# 一次性列出全部缺失字段——逐个报错会让调用方每个字段往返一轮(E-3)
|
|
896
|
+
return (
|
|
897
|
+
"EVENT_REQUIRED_FIELD",
|
|
898
|
+
f"EVENT_REQUIRED_FIELD: {event_type} requires " + ", ".join(missing),
|
|
899
|
+
)
|
|
894
900
|
allowed = _EVENT_ALLOWED_FIELDS[event_type]
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
901
|
+
disallowed = [
|
|
902
|
+
f"--{field.replace('_', '-')}"
|
|
903
|
+
for field in OPTIONAL_FIELDS
|
|
904
|
+
if getattr(args, field, None) is not None and field not in allowed
|
|
905
|
+
]
|
|
906
|
+
if disallowed:
|
|
907
|
+
# 一次列出全部不被接受的字段——逐个报错会让调用方每字段往返一轮(R-4)
|
|
908
|
+
return (
|
|
909
|
+
"EVENT_FIELD_NOT_ALLOWED",
|
|
910
|
+
f"EVENT_FIELD_NOT_ALLOWED: {event_type} does not accept "
|
|
911
|
+
+ ", ".join(disallowed),
|
|
912
|
+
)
|
|
903
913
|
for field, length in (("trace_id", 32), ("span_id", 16), ("parent_span_id", 16)):
|
|
904
914
|
value = getattr(args, field, None)
|
|
905
915
|
if value is not None and not re.fullmatch(rf"[0-9a-f]{{{length}}}", str(value)):
|
|
@@ -2146,7 +2156,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
2146
2156
|
parents=[common],
|
|
2147
2157
|
help="append one event and auto-render execution-log",
|
|
2148
2158
|
)
|
|
2149
|
-
p_append.add_argument("--change-dir", required=True)
|
|
2159
|
+
p_append.add_argument("--change-dir", "--change", dest="change_dir", required=True)
|
|
2150
2160
|
p_append.add_argument("--phase", required=True)
|
|
2151
2161
|
p_append.add_argument("--type", required=True)
|
|
2152
2162
|
p_append.add_argument("--command", default=None)
|
|
@@ -2200,7 +2210,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
2200
2210
|
parents=[common],
|
|
2201
2211
|
help="validate and append many events under one lock (H-15)",
|
|
2202
2212
|
)
|
|
2203
|
-
p_batch.add_argument("--change-dir", required=True)
|
|
2213
|
+
p_batch.add_argument("--change-dir", "--change", dest="change_dir", required=True)
|
|
2204
2214
|
p_batch.add_argument(
|
|
2205
2215
|
"--file",
|
|
2206
2216
|
required=True,
|
|
@@ -2213,7 +2223,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
2213
2223
|
parents=[common],
|
|
2214
2224
|
help="re-render execution-log.md from events.ndjson",
|
|
2215
2225
|
)
|
|
2216
|
-
p_render.add_argument("--change-dir", required=True)
|
|
2226
|
+
p_render.add_argument("--change-dir", "--change", dest="change_dir", required=True)
|
|
2217
2227
|
p_render.set_defaults(func=cmd_render)
|
|
2218
2228
|
|
|
2219
2229
|
p_summary = sub.add_parser(
|
|
@@ -2221,7 +2231,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
2221
2231
|
parents=[common],
|
|
2222
2232
|
help="summarize phases/issues from events.ndjson",
|
|
2223
2233
|
)
|
|
2224
|
-
p_summary.add_argument("--change-dir", required=True)
|
|
2234
|
+
p_summary.add_argument("--change-dir", "--change", dest="change_dir", required=True)
|
|
2225
2235
|
p_summary.set_defaults(func=cmd_summary)
|
|
2226
2236
|
|
|
2227
2237
|
return parser
|
|
@@ -75,6 +75,14 @@ def _write_json(path: Path, value: dict[str, Any]) -> None:
|
|
|
75
75
|
temporary.unlink(missing_ok=True)
|
|
76
76
|
|
|
77
77
|
|
|
78
|
+
def _read_json(path: Path) -> Any:
|
|
79
|
+
try:
|
|
80
|
+
value = json.loads(path.read_text(encoding="utf-8-sig"))
|
|
81
|
+
except (OSError, json.JSONDecodeError):
|
|
82
|
+
return None
|
|
83
|
+
return value
|
|
84
|
+
|
|
85
|
+
|
|
78
86
|
def _load(change_dir: Path, batch_id: str) -> dict[str, Any]:
|
|
79
87
|
path = _batch_path(change_dir, batch_id)
|
|
80
88
|
try:
|
|
@@ -1186,7 +1194,21 @@ def evidence_template(
|
|
|
1186
1194
|
"provenance": provenance,
|
|
1187
1195
|
}
|
|
1188
1196
|
target = Path(out) if out else Path("runtime") / f"fixback-{resolved_id}.json"
|
|
1189
|
-
|
|
1197
|
+
if target.is_absolute():
|
|
1198
|
+
path = target
|
|
1199
|
+
else:
|
|
1200
|
+
# F-3:与 ledger --out 同一类坑——相对路径已含 change-dir 前缀时直接拼
|
|
1201
|
+
# change_dir 会产生嵌套幽灵目录(2026-08-31 实测)。前缀命中按 cwd 解析。
|
|
1202
|
+
cwd = Path.cwd().resolve()
|
|
1203
|
+
change_resolved = change_dir.resolve()
|
|
1204
|
+
if _is_within(change_resolved, [cwd]):
|
|
1205
|
+
change_rel = change_resolved.relative_to(cwd)
|
|
1206
|
+
if target.parts[: len(change_rel.parts)] == change_rel.parts:
|
|
1207
|
+
path = cwd / target
|
|
1208
|
+
else:
|
|
1209
|
+
path = change_dir / target
|
|
1210
|
+
else:
|
|
1211
|
+
path = change_dir / target
|
|
1190
1212
|
path = path.resolve()
|
|
1191
1213
|
if not _is_within(path, [change_dir.resolve(), state_root, _project_root(change_dir)]):
|
|
1192
1214
|
return {
|
|
@@ -1708,6 +1730,20 @@ def close_batch(
|
|
|
1708
1730
|
}
|
|
1709
1731
|
)
|
|
1710
1732
|
_write_json(_batch_path(change_dir, batch_id), batch)
|
|
1733
|
+
# F-5:批次关闭会同步把托管会话置为 CLOSED——否则 session 永远 ACTIVE,
|
|
1734
|
+
# 后续 launch-review 被 CONTEXT_PREPARATION_ACTIVE 幂等保护误拦(2026-08-31
|
|
1735
|
+
# demo-datasource 实测)。
|
|
1736
|
+
session_file = _session_path(change_dir)
|
|
1737
|
+
session = _read_json(session_file)
|
|
1738
|
+
if (
|
|
1739
|
+
isinstance(session, dict)
|
|
1740
|
+
and session.get("batchId") == batch_id
|
|
1741
|
+
and session.get("status") == "ACTIVE"
|
|
1742
|
+
):
|
|
1743
|
+
session["status"] = "CLOSED"
|
|
1744
|
+
session["nextStep"] = "done"
|
|
1745
|
+
session["updatedAt"] = closed_at
|
|
1746
|
+
_write_json(session_file, session)
|
|
1711
1747
|
return batch
|
|
1712
1748
|
|
|
1713
1749
|
|
|
@@ -2594,6 +2594,64 @@ def _normalize_gate_phase(args: argparse.Namespace, as_json: bool) -> int | None
|
|
|
2594
2594
|
)
|
|
2595
2595
|
|
|
2596
2596
|
|
|
2597
|
+
def _verification_target_coverage_warning(
|
|
2598
|
+
change_dir: Path, phase: str
|
|
2599
|
+
) -> dict[str, Any] | None:
|
|
2600
|
+
"""E-5:场景表引用未声明的 verification target 时提前 WARN。
|
|
2601
|
+
|
|
2602
|
+
场景带 execution_level(unit/integration/…),但 build-profile 的
|
|
2603
|
+
verificationGraph.targets 未声明对应 target 时,问题要等到 ledger record
|
|
2604
|
+
才以 unsupported verification 报错——太迟。gate begin 提前列出待补清单。
|
|
2605
|
+
映射约定:unit → unitTest;其他 level → 任一以其名开头的 target。
|
|
2606
|
+
"""
|
|
2607
|
+
if phase != "execute":
|
|
2608
|
+
return None
|
|
2609
|
+
manifest_path = change_dir / "meta" / "scenario-manifest.json"
|
|
2610
|
+
profile_path = change_dir / "meta" / "build-profile.json"
|
|
2611
|
+
if not profile_path.is_file():
|
|
2612
|
+
# build-profile 常规位置在项目 .harness/config/;change meta 下优先
|
|
2613
|
+
profile_path = change_dir.parents[2] / ".harness" / "config" / "build-profile.json"
|
|
2614
|
+
if not manifest_path.is_file() or not profile_path.is_file():
|
|
2615
|
+
return None
|
|
2616
|
+
try:
|
|
2617
|
+
manifest = json.loads(manifest_path.read_text(encoding="utf-8-sig"))
|
|
2618
|
+
profile = json.loads(profile_path.read_text(encoding="utf-8-sig"))
|
|
2619
|
+
except (OSError, json.JSONDecodeError):
|
|
2620
|
+
return None
|
|
2621
|
+
content = manifest.get("content") if isinstance(manifest, dict) else None
|
|
2622
|
+
scenarios = content.get("scenarios") if isinstance(content, dict) else None
|
|
2623
|
+
if not isinstance(scenarios, list):
|
|
2624
|
+
return None
|
|
2625
|
+
levels = sorted({
|
|
2626
|
+
str(s.get("execution_level")).strip()
|
|
2627
|
+
for s in scenarios
|
|
2628
|
+
if isinstance(s, dict) and str(s.get("execution_level") or "").strip()
|
|
2629
|
+
})
|
|
2630
|
+
graph = profile.get("verificationGraph")
|
|
2631
|
+
targets = graph.get("targets") if isinstance(graph, dict) else None
|
|
2632
|
+
target_names = set(targets.keys()) if isinstance(targets, dict) else set()
|
|
2633
|
+
uncovered = [
|
|
2634
|
+
level for level in levels
|
|
2635
|
+
if level != "unit"
|
|
2636
|
+
and not any(name.lower().startswith(level.lower()) for name in target_names)
|
|
2637
|
+
]
|
|
2638
|
+
# unit 场景由 unitTest 覆盖;只在完全没有 unitTest 时才算缺口
|
|
2639
|
+
if "unit" in levels and "unitTest" not in target_names:
|
|
2640
|
+
uncovered.append("unit(→unitTest)")
|
|
2641
|
+
if not uncovered:
|
|
2642
|
+
return None
|
|
2643
|
+
return {
|
|
2644
|
+
"code": "VERIFICATION_TARGETS_UNDECLARED",
|
|
2645
|
+
"message": (
|
|
2646
|
+
"场景表涉及未声明的验证级别: " + ", ".join(uncovered) +
|
|
2647
|
+
";ledger record 将报 unsupported verification。请在 "
|
|
2648
|
+
".harness/config/build-profile.json 的 verificationGraph.targets "
|
|
2649
|
+
"声明对应 target(如 integrationTest)"
|
|
2650
|
+
),
|
|
2651
|
+
"uncoveredLevels": uncovered,
|
|
2652
|
+
}
|
|
2653
|
+
|
|
2654
|
+
|
|
2597
2655
|
def cmd_begin(args: argparse.Namespace) -> int:
|
|
2598
2656
|
as_json = bool(args.json)
|
|
2599
2657
|
normalized = _normalize_gate_phase(args, as_json)
|
|
@@ -2911,6 +2969,14 @@ def cmd_begin(args: argparse.Namespace) -> int:
|
|
|
2911
2969
|
)
|
|
2912
2970
|
raise
|
|
2913
2971
|
|
|
2972
|
+
coverage_warning = _verification_target_coverage_warning(change_dir, args.phase)
|
|
2973
|
+
if coverage_warning is not None:
|
|
2974
|
+
gate_warnings.append(coverage_warning)
|
|
2975
|
+
print(
|
|
2976
|
+
f"[harness-gate] WARNING {coverage_warning['code']}: "
|
|
2977
|
+
f"{coverage_warning['message']}",
|
|
2978
|
+
file=sys.stderr,
|
|
2979
|
+
)
|
|
2914
2980
|
payload = {
|
|
2915
2981
|
"ok": True,
|
|
2916
2982
|
"code": "PHASE_BEGUN",
|
|
@@ -3039,6 +3105,20 @@ def _resume_closed_phase(
|
|
|
3039
3105
|
for candidate in hctx.allowed_next_phases(project, change_id, args.phase)
|
|
3040
3106
|
if hp.resolve_phase_name(candidate) != args.phase
|
|
3041
3107
|
]
|
|
3108
|
+
# F-4:续跑路径同样——fixback 回环的 execute 关门应回 review 的后继
|
|
3109
|
+
if args.phase == "execute" and isinstance(transitions, list) and transitions:
|
|
3110
|
+
latest_t = transitions[-1]
|
|
3111
|
+
if (
|
|
3112
|
+
isinstance(latest_t, dict)
|
|
3113
|
+
and hp.resolve_phase_name(latest_t.get("fromPhase")) == "review"
|
|
3114
|
+
and hp.resolve_phase_name(latest_t.get("toPhase")) == "execute"
|
|
3115
|
+
and latest_t.get("trigger") == "review-fixback"
|
|
3116
|
+
):
|
|
3117
|
+
candidates = [
|
|
3118
|
+
candidate
|
|
3119
|
+
for candidate in hctx.allowed_next_phases(project, change_id, "review")
|
|
3120
|
+
if hp.resolve_phase_name(candidate) != args.phase
|
|
3121
|
+
]
|
|
3042
3122
|
if to_phase is None and len(candidates) == 1:
|
|
3043
3123
|
# 计划后继唯一(排除 fixback 自环):close 的意图没有歧义,直接补跑 handoff
|
|
3044
3124
|
to_phase = candidates[0]
|
|
@@ -3625,10 +3705,25 @@ def cmd_close(args: argparse.Namespace) -> int:
|
|
|
3625
3705
|
and context_view.get("ok")
|
|
3626
3706
|
and isinstance(context_view.get("current"), dict)
|
|
3627
3707
|
):
|
|
3708
|
+
# F-4:fixback 回环(最新交接是 review→execute 且 trigger=
|
|
3709
|
+
# review-fixback)关门时,后继不是再来一轮 review,而是 review
|
|
3710
|
+
# 当时的计划后继(submit)。不按此处理会把 transitions 链写成
|
|
3711
|
+
# …→review→execute→review 的语义错乱(2026-08-31 实测)。
|
|
3712
|
+
source_phase = args.phase
|
|
3713
|
+
transitions = context_view.get("transitions") or []
|
|
3714
|
+
if args.phase == "execute" and transitions:
|
|
3715
|
+
latest = transitions[-1]
|
|
3716
|
+
if (
|
|
3717
|
+
isinstance(latest, dict)
|
|
3718
|
+
and hp.resolve_phase_name(latest.get("fromPhase")) == "review"
|
|
3719
|
+
and hp.resolve_phase_name(latest.get("toPhase")) == "execute"
|
|
3720
|
+
and latest.get("trigger") == "review-fixback"
|
|
3721
|
+
):
|
|
3722
|
+
source_phase = "review"
|
|
3628
3723
|
forward = [
|
|
3629
3724
|
candidate
|
|
3630
3725
|
for candidate in hctx.allowed_next_phases(
|
|
3631
|
-
project, str(resolved["changeId"]),
|
|
3726
|
+
project, str(resolved["changeId"]), source_phase
|
|
3632
3727
|
)
|
|
3633
3728
|
if hp.resolve_phase_name(candidate) != args.phase
|
|
3634
3729
|
]
|
|
@@ -3800,11 +3895,25 @@ def cmd_classify(args: argparse.Namespace) -> int:
|
|
|
3800
3895
|
payload["classifiedAt"] = classified_at
|
|
3801
3896
|
|
|
3802
3897
|
if change_dir is not None and change_dir.is_dir():
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3898
|
+
finalized = (change_dir / "meta" / "plan-profile.json").is_file()
|
|
3899
|
+
if finalized and not getattr(args, "force", False):
|
|
3900
|
+
# P1-1:classify 形态像只读查询,实际是写操作。已发布的 change
|
|
3901
|
+
# 被误跑 classify 会覆盖 configure-plan 写入的工作副本(2026-08-30
|
|
3902
|
+
# demo-datasource 实测 plannedPhases 被清)。默认拒写并显式提示。
|
|
3903
|
+
payload["policyPersisted"] = False
|
|
3904
|
+
payload["warning"] = (
|
|
3905
|
+
"change 已发布(meta/plan-profile.json 存在),classify 不再重写 "
|
|
3906
|
+
"meta/gate-policy.json 工作副本;确需重算请显式传 --force"
|
|
3907
|
+
)
|
|
3908
|
+
payload["writeGuarded"] = True
|
|
3909
|
+
else:
|
|
3910
|
+
policy_doc = gate_policy_document(payload)
|
|
3911
|
+
policy_path = change_dir / "meta" / "gate-policy.json"
|
|
3912
|
+
_write_json(policy_path, policy_doc)
|
|
3913
|
+
payload["policyPersisted"] = True
|
|
3914
|
+
payload["policyPath"] = str(policy_path)
|
|
3915
|
+
if finalized:
|
|
3916
|
+
payload["warning"] = "--force:已发布 change 的工作副本已被重写"
|
|
3808
3917
|
else:
|
|
3809
3918
|
payload["policyPersisted"] = False
|
|
3810
3919
|
payload["warning"] = (
|
|
@@ -3930,7 +4039,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
3930
4039
|
|
|
3931
4040
|
p_begin = sub.add_parser("begin", parents=[shared])
|
|
3932
4041
|
p_begin.add_argument("--phase", required=True)
|
|
3933
|
-
p_begin.add_argument("--change", default=None)
|
|
4042
|
+
p_begin.add_argument("--change", "--change-dir", dest="change", default=None)
|
|
3934
4043
|
p_begin.add_argument(
|
|
3935
4044
|
"--project",
|
|
3936
4045
|
default=None,
|
|
@@ -3960,7 +4069,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
3960
4069
|
help="关闭阶段门禁(关门只调本命令;上下文交接由 --to-phase 内联完成,不要再单独调 harness_context.py close)",
|
|
3961
4070
|
)
|
|
3962
4071
|
p_close.add_argument("--phase", required=True)
|
|
3963
|
-
p_close.add_argument("--change", default=None)
|
|
4072
|
+
p_close.add_argument("--change", "--change-dir", dest="change", default=None)
|
|
3964
4073
|
p_close.add_argument(
|
|
3965
4074
|
"--project",
|
|
3966
4075
|
default=None,
|
|
@@ -3985,7 +4094,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
3985
4094
|
|
|
3986
4095
|
p_classify = sub.add_parser("classify", parents=[shared])
|
|
3987
4096
|
p_classify.add_argument("--project", default=None, type=Path, help=_PROJECT_ROOT_HELP)
|
|
3988
|
-
p_classify.add_argument("--change", default=None)
|
|
4097
|
+
p_classify.add_argument("--change", "--change-dir", dest="change", default=None)
|
|
3989
4098
|
p_classify.add_argument("--stage", required=True, choices=["plan", "post-run"])
|
|
3990
4099
|
p_classify.add_argument(
|
|
3991
4100
|
"--tier-override",
|
|
@@ -3993,13 +4102,18 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
3993
4102
|
choices=["fast", "standard", "full"],
|
|
3994
4103
|
)
|
|
3995
4104
|
p_classify.add_argument("--override-by", default="user")
|
|
4105
|
+
p_classify.add_argument(
|
|
4106
|
+
"--force",
|
|
4107
|
+
action="store_true",
|
|
4108
|
+
help="已发布 change 也强制重写 gate-policy.json 工作副本",
|
|
4109
|
+
)
|
|
3996
4110
|
p_classify.set_defaults(func=cmd_classify)
|
|
3997
4111
|
|
|
3998
4112
|
p_checkpoint = sub.add_parser("checkpoint", parents=[shared])
|
|
3999
4113
|
p_checkpoint.add_argument("--project", default=None, type=Path, help=_PROJECT_ROOT_HELP)
|
|
4000
4114
|
p_checkpoint.add_argument("checkpoint_action", choices=["status", "approve"])
|
|
4001
4115
|
p_checkpoint.add_argument("--id", required=True)
|
|
4002
|
-
p_checkpoint.add_argument("--change", default=None)
|
|
4116
|
+
p_checkpoint.add_argument("--change", "--change-dir", dest="change", default=None)
|
|
4003
4117
|
p_checkpoint.add_argument("--reviewer", default=None)
|
|
4004
4118
|
p_checkpoint.set_defaults(func=cmd_checkpoint)
|
|
4005
4119
|
|