@hunter-harness/workflow-harness 0.2.86 → 0.3.0
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/contracts/workflow-policy.json +46 -17
- package/harness/bundles/general/claude-code/harness-archive/SKILL.md +2 -2
- package/harness/bundles/general/claude-code/harness-codebase-map/SKILL.md +2 -2
- package/harness/bundles/general/claude-code/harness-execute/SKILL.md +189 -0
- package/harness/bundles/general/claude-code/harness-knowledge-ingest/SKILL.md +2 -2
- package/harness/bundles/general/claude-code/harness-knowledge-query/SKILL.md +2 -2
- package/harness/bundles/general/claude-code/harness-plan/SKILL.md +10 -10
- package/harness/bundles/general/claude-code/harness-plan/checklist.md +2 -13
- package/harness/bundles/general/claude-code/harness-plan/reference.md +43 -81
- 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 +3 -3
- package/harness/bundles/general/claude-code/harness-review/protocols.md +1 -1
- package/harness/bundles/general/claude-code/harness-run/SKILL.md +8 -2
- package/harness/bundles/general/claude-code/harness-submit/SKILL.md +2 -2
- package/harness/bundles/general/claude-code/harness-submit/checklist.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 +11 -2
- package/harness/bundles/general/claude-code/scripts/harness_adoption_metrics.py +391 -0
- package/harness/bundles/general/claude-code/scripts/harness_archive.py +23 -14
- package/harness/bundles/general/claude-code/scripts/harness_context.py +94 -43
- package/harness/bundles/general/claude-code/scripts/harness_events.py +1 -1
- package/harness/bundles/general/claude-code/scripts/harness_events_sync.py +1 -0
- package/harness/bundles/general/claude-code/scripts/harness_fixback.py +10 -10
- package/harness/bundles/general/claude-code/scripts/harness_gate.py +92 -25
- package/harness/bundles/general/claude-code/scripts/harness_ledger.py +4 -2
- package/harness/bundles/general/claude-code/scripts/harness_paths.py +120 -5
- package/harness/bundles/general/claude-code/scripts/harness_phase.py +41 -17
- package/harness/bundles/general/claude-code/scripts/harness_plan_finalize.py +16 -873
- package/harness/bundles/general/codebuddy/.harness-build.json +1 -1
- package/harness/bundles/general/codebuddy/contracts/workflow-policy.json +46 -17
- package/harness/bundles/general/codebuddy/harness-archive/SKILL.md +2 -2
- package/harness/bundles/general/codebuddy/harness-codebase-map/SKILL.md +2 -2
- package/harness/bundles/general/codebuddy/harness-execute/SKILL.md +170 -0
- package/harness/bundles/general/codebuddy/harness-knowledge-ingest/SKILL.md +2 -2
- package/harness/bundles/general/codebuddy/harness-knowledge-query/SKILL.md +2 -2
- package/harness/bundles/general/codebuddy/harness-plan/SKILL.md +10 -10
- package/harness/bundles/general/codebuddy/harness-plan/checklist.md +2 -13
- package/harness/bundles/general/codebuddy/harness-plan/reference.md +43 -81
- 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 +3 -3
- package/harness/bundles/general/codebuddy/harness-review/protocols.md +1 -1
- package/harness/bundles/general/codebuddy/harness-run/SKILL.md +8 -2
- package/harness/bundles/general/codebuddy/harness-submit/SKILL.md +2 -2
- package/harness/bundles/general/codebuddy/harness-submit/checklist.md +1 -1
- package/harness/bundles/general/codebuddy/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-test/SKILL.md +11 -2
- package/harness/bundles/general/codebuddy/scripts/harness_adoption_metrics.py +391 -0
- package/harness/bundles/general/codebuddy/scripts/harness_archive.py +23 -14
- package/harness/bundles/general/codebuddy/scripts/harness_context.py +94 -43
- package/harness/bundles/general/codebuddy/scripts/harness_events.py +1 -1
- package/harness/bundles/general/codebuddy/scripts/harness_events_sync.py +1 -0
- package/harness/bundles/general/codebuddy/scripts/harness_fixback.py +10 -10
- package/harness/bundles/general/codebuddy/scripts/harness_gate.py +92 -25
- package/harness/bundles/general/codebuddy/scripts/harness_ledger.py +4 -2
- package/harness/bundles/general/codebuddy/scripts/harness_paths.py +120 -5
- package/harness/bundles/general/codebuddy/scripts/harness_phase.py +41 -17
- package/harness/bundles/general/codebuddy/scripts/harness_plan_finalize.py +16 -873
- package/harness/bundles/general/codex/.harness-build.json +1 -1
- package/harness/bundles/general/codex/contracts/workflow-policy.json +46 -17
- package/harness/bundles/general/codex/harness-archive/SKILL.md +2 -2
- package/harness/bundles/general/codex/harness-codebase-map/SKILL.md +2 -2
- package/harness/bundles/general/codex/harness-execute/SKILL.md +170 -0
- package/harness/bundles/general/codex/harness-knowledge-ingest/SKILL.md +2 -2
- package/harness/bundles/general/codex/harness-knowledge-query/SKILL.md +2 -2
- package/harness/bundles/general/codex/harness-plan/SKILL.md +10 -10
- package/harness/bundles/general/codex/harness-plan/checklist.md +2 -13
- package/harness/bundles/general/codex/harness-plan/reference.md +43 -81
- 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 +3 -3
- package/harness/bundles/general/codex/harness-review/protocols.md +1 -1
- package/harness/bundles/general/codex/harness-run/SKILL.md +8 -2
- package/harness/bundles/general/codex/harness-submit/SKILL.md +2 -2
- package/harness/bundles/general/codex/harness-submit/checklist.md +1 -1
- package/harness/bundles/general/codex/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-test/SKILL.md +11 -2
- package/harness/bundles/general/codex/scripts/harness_adoption_metrics.py +391 -0
- package/harness/bundles/general/codex/scripts/harness_archive.py +23 -14
- package/harness/bundles/general/codex/scripts/harness_context.py +94 -43
- package/harness/bundles/general/codex/scripts/harness_events.py +1 -1
- package/harness/bundles/general/codex/scripts/harness_events_sync.py +1 -0
- package/harness/bundles/general/codex/scripts/harness_fixback.py +10 -10
- package/harness/bundles/general/codex/scripts/harness_gate.py +92 -25
- package/harness/bundles/general/codex/scripts/harness_ledger.py +4 -2
- package/harness/bundles/general/codex/scripts/harness_paths.py +120 -5
- package/harness/bundles/general/codex/scripts/harness_phase.py +41 -17
- package/harness/bundles/general/codex/scripts/harness_plan_finalize.py +16 -873
- package/harness/bundles/general/cursor/.harness-build.json +1 -1
- package/harness/bundles/general/cursor/contracts/workflow-policy.json +46 -17
- package/harness/bundles/general/cursor/harness-archive/SKILL.md +2 -2
- package/harness/bundles/general/cursor/harness-codebase-map/SKILL.md +2 -2
- package/harness/bundles/general/cursor/harness-execute/SKILL.md +170 -0
- package/harness/bundles/general/cursor/harness-knowledge-ingest/SKILL.md +2 -2
- package/harness/bundles/general/cursor/harness-knowledge-query/SKILL.md +2 -2
- package/harness/bundles/general/cursor/harness-plan/SKILL.md +10 -10
- package/harness/bundles/general/cursor/harness-plan/checklist.md +2 -13
- package/harness/bundles/general/cursor/harness-plan/reference.md +43 -81
- 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 +3 -3
- package/harness/bundles/general/cursor/harness-review/protocols.md +1 -1
- package/harness/bundles/general/cursor/harness-run/SKILL.md +8 -2
- package/harness/bundles/general/cursor/harness-submit/SKILL.md +2 -2
- package/harness/bundles/general/cursor/harness-submit/checklist.md +1 -1
- package/harness/bundles/general/cursor/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-test/SKILL.md +11 -2
- package/harness/bundles/general/cursor/scripts/harness_adoption_metrics.py +391 -0
- package/harness/bundles/general/cursor/scripts/harness_archive.py +23 -14
- package/harness/bundles/general/cursor/scripts/harness_context.py +94 -43
- package/harness/bundles/general/cursor/scripts/harness_events.py +1 -1
- package/harness/bundles/general/cursor/scripts/harness_events_sync.py +1 -0
- package/harness/bundles/general/cursor/scripts/harness_fixback.py +10 -10
- package/harness/bundles/general/cursor/scripts/harness_gate.py +92 -25
- package/harness/bundles/general/cursor/scripts/harness_ledger.py +4 -2
- package/harness/bundles/general/cursor/scripts/harness_paths.py +120 -5
- package/harness/bundles/general/cursor/scripts/harness_phase.py +41 -17
- package/harness/bundles/general/cursor/scripts/harness_plan_finalize.py +16 -873
- package/harness/bundles/java/claude-code/.harness-build.json +1 -1
- package/harness/bundles/java/claude-code/contracts/workflow-policy.json +46 -17
- package/harness/bundles/java/claude-code/harness-apidoc/SKILL.md +2 -2
- package/harness/bundles/java/claude-code/harness-archive/SKILL.md +2 -2
- package/harness/bundles/java/claude-code/harness-codebase-map/SKILL.md +2 -2
- package/harness/bundles/java/claude-code/harness-execute/SKILL.md +189 -0
- package/harness/bundles/java/claude-code/harness-knowledge-ingest/SKILL.md +2 -2
- package/harness/bundles/java/claude-code/harness-knowledge-query/SKILL.md +2 -2
- package/harness/bundles/java/claude-code/harness-package/SKILL.md +2 -2
- package/harness/bundles/java/claude-code/harness-plan/SKILL.md +10 -10
- package/harness/bundles/java/claude-code/harness-plan/checklist.md +2 -13
- package/harness/bundles/java/claude-code/harness-plan/reference.md +43 -81
- 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 +3 -3
- package/harness/bundles/java/claude-code/harness-review/protocols.md +1 -1
- package/harness/bundles/java/claude-code/harness-run/SKILL.md +8 -2
- package/harness/bundles/java/claude-code/harness-submit/SKILL.md +2 -2
- package/harness/bundles/java/claude-code/harness-submit/checklist.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 +11 -2
- package/harness/bundles/java/claude-code/scripts/harness_adoption_metrics.py +391 -0
- package/harness/bundles/java/claude-code/scripts/harness_archive.py +23 -14
- package/harness/bundles/java/claude-code/scripts/harness_context.py +94 -43
- package/harness/bundles/java/claude-code/scripts/harness_events.py +1 -1
- package/harness/bundles/java/claude-code/scripts/harness_events_sync.py +1 -0
- package/harness/bundles/java/claude-code/scripts/harness_fixback.py +10 -10
- package/harness/bundles/java/claude-code/scripts/harness_gate.py +92 -25
- package/harness/bundles/java/claude-code/scripts/harness_ledger.py +4 -2
- package/harness/bundles/java/claude-code/scripts/harness_paths.py +120 -5
- package/harness/bundles/java/claude-code/scripts/harness_phase.py +41 -17
- package/harness/bundles/java/claude-code/scripts/harness_plan_finalize.py +16 -873
- package/harness/bundles/java/codebuddy/.harness-build.json +1 -1
- package/harness/bundles/java/codebuddy/contracts/workflow-policy.json +46 -17
- package/harness/bundles/java/codebuddy/harness-apidoc/SKILL.md +2 -2
- package/harness/bundles/java/codebuddy/harness-archive/SKILL.md +2 -2
- package/harness/bundles/java/codebuddy/harness-codebase-map/SKILL.md +2 -2
- package/harness/bundles/java/codebuddy/harness-execute/SKILL.md +170 -0
- package/harness/bundles/java/codebuddy/harness-knowledge-ingest/SKILL.md +2 -2
- package/harness/bundles/java/codebuddy/harness-knowledge-query/SKILL.md +2 -2
- package/harness/bundles/java/codebuddy/harness-package/SKILL.md +2 -2
- package/harness/bundles/java/codebuddy/harness-plan/SKILL.md +10 -10
- package/harness/bundles/java/codebuddy/harness-plan/checklist.md +2 -13
- package/harness/bundles/java/codebuddy/harness-plan/reference.md +43 -81
- 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 +3 -3
- package/harness/bundles/java/codebuddy/harness-review/protocols.md +1 -1
- package/harness/bundles/java/codebuddy/harness-run/SKILL.md +8 -2
- package/harness/bundles/java/codebuddy/harness-submit/SKILL.md +2 -2
- package/harness/bundles/java/codebuddy/harness-submit/checklist.md +1 -1
- package/harness/bundles/java/codebuddy/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-test/SKILL.md +11 -2
- package/harness/bundles/java/codebuddy/scripts/harness_adoption_metrics.py +391 -0
- package/harness/bundles/java/codebuddy/scripts/harness_archive.py +23 -14
- package/harness/bundles/java/codebuddy/scripts/harness_context.py +94 -43
- package/harness/bundles/java/codebuddy/scripts/harness_events.py +1 -1
- package/harness/bundles/java/codebuddy/scripts/harness_events_sync.py +1 -0
- package/harness/bundles/java/codebuddy/scripts/harness_fixback.py +10 -10
- package/harness/bundles/java/codebuddy/scripts/harness_gate.py +92 -25
- package/harness/bundles/java/codebuddy/scripts/harness_ledger.py +4 -2
- package/harness/bundles/java/codebuddy/scripts/harness_paths.py +120 -5
- package/harness/bundles/java/codebuddy/scripts/harness_phase.py +41 -17
- package/harness/bundles/java/codebuddy/scripts/harness_plan_finalize.py +16 -873
- package/harness/bundles/java/codex/.harness-build.json +1 -1
- package/harness/bundles/java/codex/contracts/workflow-policy.json +46 -17
- package/harness/bundles/java/codex/harness-apidoc/SKILL.md +2 -2
- package/harness/bundles/java/codex/harness-archive/SKILL.md +2 -2
- package/harness/bundles/java/codex/harness-codebase-map/SKILL.md +2 -2
- package/harness/bundles/java/codex/harness-execute/SKILL.md +170 -0
- package/harness/bundles/java/codex/harness-knowledge-ingest/SKILL.md +2 -2
- package/harness/bundles/java/codex/harness-knowledge-query/SKILL.md +2 -2
- package/harness/bundles/java/codex/harness-package/SKILL.md +2 -2
- package/harness/bundles/java/codex/harness-plan/SKILL.md +10 -10
- package/harness/bundles/java/codex/harness-plan/checklist.md +2 -13
- package/harness/bundles/java/codex/harness-plan/reference.md +43 -81
- 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 +3 -3
- package/harness/bundles/java/codex/harness-review/protocols.md +1 -1
- package/harness/bundles/java/codex/harness-run/SKILL.md +8 -2
- package/harness/bundles/java/codex/harness-submit/SKILL.md +2 -2
- package/harness/bundles/java/codex/harness-submit/checklist.md +1 -1
- package/harness/bundles/java/codex/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-test/SKILL.md +11 -2
- package/harness/bundles/java/codex/scripts/harness_adoption_metrics.py +391 -0
- package/harness/bundles/java/codex/scripts/harness_archive.py +23 -14
- package/harness/bundles/java/codex/scripts/harness_context.py +94 -43
- package/harness/bundles/java/codex/scripts/harness_events.py +1 -1
- package/harness/bundles/java/codex/scripts/harness_events_sync.py +1 -0
- package/harness/bundles/java/codex/scripts/harness_fixback.py +10 -10
- package/harness/bundles/java/codex/scripts/harness_gate.py +92 -25
- package/harness/bundles/java/codex/scripts/harness_ledger.py +4 -2
- package/harness/bundles/java/codex/scripts/harness_paths.py +120 -5
- package/harness/bundles/java/codex/scripts/harness_phase.py +41 -17
- package/harness/bundles/java/codex/scripts/harness_plan_finalize.py +16 -873
- package/harness/bundles/java/cursor/.harness-build.json +1 -1
- package/harness/bundles/java/cursor/contracts/workflow-policy.json +46 -17
- package/harness/bundles/java/cursor/harness-apidoc/SKILL.md +2 -2
- package/harness/bundles/java/cursor/harness-archive/SKILL.md +2 -2
- package/harness/bundles/java/cursor/harness-codebase-map/SKILL.md +2 -2
- package/harness/bundles/java/cursor/harness-execute/SKILL.md +170 -0
- package/harness/bundles/java/cursor/harness-knowledge-ingest/SKILL.md +2 -2
- package/harness/bundles/java/cursor/harness-knowledge-query/SKILL.md +2 -2
- package/harness/bundles/java/cursor/harness-package/SKILL.md +2 -2
- package/harness/bundles/java/cursor/harness-plan/SKILL.md +10 -10
- package/harness/bundles/java/cursor/harness-plan/checklist.md +2 -13
- package/harness/bundles/java/cursor/harness-plan/reference.md +43 -81
- 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 +3 -3
- package/harness/bundles/java/cursor/harness-review/protocols.md +1 -1
- package/harness/bundles/java/cursor/harness-run/SKILL.md +8 -2
- package/harness/bundles/java/cursor/harness-submit/SKILL.md +2 -2
- package/harness/bundles/java/cursor/harness-submit/checklist.md +1 -1
- package/harness/bundles/java/cursor/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-test/SKILL.md +11 -2
- package/harness/bundles/java/cursor/scripts/harness_adoption_metrics.py +391 -0
- package/harness/bundles/java/cursor/scripts/harness_archive.py +23 -14
- package/harness/bundles/java/cursor/scripts/harness_context.py +94 -43
- package/harness/bundles/java/cursor/scripts/harness_events.py +1 -1
- package/harness/bundles/java/cursor/scripts/harness_events_sync.py +1 -0
- package/harness/bundles/java/cursor/scripts/harness_fixback.py +10 -10
- package/harness/bundles/java/cursor/scripts/harness_gate.py +92 -25
- package/harness/bundles/java/cursor/scripts/harness_ledger.py +4 -2
- package/harness/bundles/java/cursor/scripts/harness_paths.py +120 -5
- package/harness/bundles/java/cursor/scripts/harness_phase.py +41 -17
- package/harness/bundles/java/cursor/scripts/harness_plan_finalize.py +16 -873
- package/harness/manifests/general/claude-code.json +36 -28
- package/harness/manifests/general/codebuddy.json +36 -28
- package/harness/manifests/general/codex.json +36 -28
- package/harness/manifests/general/cursor.json +36 -28
- package/harness/manifests/java/claude-code.json +38 -30
- package/harness/manifests/java/codebuddy.json +38 -30
- package/harness/manifests/java/codex.json +38 -30
- package/harness/manifests/java/cursor.json +38 -30
- package/hunter-workflow-family.json +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Legacy plan finalize 退役前的采纳度度量(roadmap 14,只读)。
|
|
3
|
+
|
|
4
|
+
对一组项目根聚合三条判据的读数,输出 ``pass`` / ``fail`` / ``indeterminate``。
|
|
5
|
+
本脚本只读:不写任何文件、不改判据阈值、不宣告"达标可删"。样本不足时输出
|
|
6
|
+
``indeterminate``,由人决定是否继续积累 dogfood 样本。
|
|
7
|
+
|
|
8
|
+
三条判据(docs/harness-improvement-roadmap/14-migration-rollout-and-acceptance.md):
|
|
9
|
+
|
|
10
|
+
1. ``finalize_first_try``:v2 finalize 首次成功率。连续 10 次 finalize 中
|
|
11
|
+
首次 attempt 即 ``publication_committed_event_complete`` 的比例 >= 8/10。
|
|
12
|
+
操作化定义:磁盘布局是一 operation 一文件
|
|
13
|
+
(``meta/plan-finalization-transactions/<operation_id>.json``),同 operation
|
|
14
|
+
的重试覆盖同一文件,可观测的重试信号 = 同一 change 的后续 operation 文件数。
|
|
15
|
+
"首次成功" = 该 change 最早创建的 operation 文件 status 为
|
|
16
|
+
``publication_committed_event_complete``。
|
|
17
|
+
2. ``evidence_closure``:证据闭环。连续 5 个 v2 change 的
|
|
18
|
+
``evidence/verification-ledger.json`` 覆盖全部 ``requiredEvidenceKind=ledger``
|
|
19
|
+
场景,且 events 未出现 ``SCENARIO_MANIFEST_`` 阻断码。
|
|
20
|
+
覆盖判定:场景声明的 ``executableTestId`` 出现在 ledger receipt 的
|
|
21
|
+
``declared`` 或 ``selected`` 列表,或其三元 identity(testId/file/title)
|
|
22
|
+
出现在 ``collected``。任何一项判不出都记 uncovered 并带 reason——
|
|
23
|
+
准入门槛宁缺毋滥。
|
|
24
|
+
3. ``legacy_fallback``:legacy 回退率。最近 10 个新 change 中写出
|
|
25
|
+
``meta/plan-finalization.json``(legacy finalize 收据)的比例 <= 1/10。
|
|
26
|
+
|
|
27
|
+
多项目样本合并成单一证据池(dogfood 车队是一个总体)。窗口内排序键:
|
|
28
|
+
payload ``created_at``(ISO 字典序)→ 缺则文件 mtime → 再并列按 change_key。
|
|
29
|
+
|
|
30
|
+
用法::
|
|
31
|
+
|
|
32
|
+
python harness/scripts/harness_adoption_metrics.py \
|
|
33
|
+
--project <root> [--project <root2> ...] [--json] [--strict]
|
|
34
|
+
|
|
35
|
+
默认 exit 0(度量工具不卡 CI);``--strict`` 时任何 fail 或 indeterminate → exit 1。
|
|
36
|
+
|
|
37
|
+
Python 3.10+,stdlib only,不 import 任何 harness_*(零耦合)。
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
from __future__ import annotations
|
|
41
|
+
|
|
42
|
+
import argparse
|
|
43
|
+
import json
|
|
44
|
+
import sys
|
|
45
|
+
from pathlib import Path
|
|
46
|
+
from typing import Any
|
|
47
|
+
|
|
48
|
+
SCRIPTS_DIR = Path(__file__).resolve().parent
|
|
49
|
+
if str(SCRIPTS_DIR) not in sys.path:
|
|
50
|
+
sys.path.insert(0, str(SCRIPTS_DIR))
|
|
51
|
+
|
|
52
|
+
if hasattr(sys.stdout, "reconfigure"):
|
|
53
|
+
sys.stdout.reconfigure(encoding="utf-8")
|
|
54
|
+
if hasattr(sys.stderr, "reconfigure"):
|
|
55
|
+
sys.stderr.reconfigure(encoding="utf-8")
|
|
56
|
+
|
|
57
|
+
FIRST_TRY_WINDOW = 10
|
|
58
|
+
FIRST_TRY_THRESHOLD = 8
|
|
59
|
+
EVIDENCE_WINDOW = 5
|
|
60
|
+
LEGACY_WINDOW = 10
|
|
61
|
+
LEGACY_THRESHOLD = 1
|
|
62
|
+
|
|
63
|
+
FINAL_STATUS = "publication_committed_event_complete"
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _read_json(path: Path) -> dict[str, Any] | None:
|
|
67
|
+
try:
|
|
68
|
+
payload = json.loads(path.read_text(encoding="utf-8-sig"))
|
|
69
|
+
except (OSError, json.JSONDecodeError):
|
|
70
|
+
return None
|
|
71
|
+
return payload if isinstance(payload, dict) else None
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _change_dirs(project: Path) -> list[Path]:
|
|
75
|
+
root = project / ".harness" / "changes"
|
|
76
|
+
if not root.is_dir():
|
|
77
|
+
return []
|
|
78
|
+
return sorted((p for p in root.iterdir() if p.is_dir()), key=lambda p: p.name)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _sort_key(created_at: str, mtime: float, change_key: str) -> tuple[str, float, str]:
|
|
82
|
+
# created_at 是 ISO 字符串,字典序即时间序;缺失时退到 mtime。
|
|
83
|
+
return (created_at, mtime, change_key)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _operation_sort_key(record: dict[str, Any], path: Path) -> tuple[str, float, str]:
|
|
87
|
+
return _sort_key(
|
|
88
|
+
str(record.get("created_at") or ""),
|
|
89
|
+
path.stat().st_mtime,
|
|
90
|
+
str(record.get("operation_id") or path.name),
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
# ---------------------------------------------------------------------------
|
|
95
|
+
# 判据 1:v2 finalize 首次成功率
|
|
96
|
+
# ---------------------------------------------------------------------------
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _collect_first_try(projects: list[Path]) -> dict[str, Any]:
|
|
100
|
+
per_change: list[dict[str, Any]] = []
|
|
101
|
+
for project in projects:
|
|
102
|
+
for change in _change_dirs(project):
|
|
103
|
+
transactions_dir = change / "meta" / "plan-finalization-transactions"
|
|
104
|
+
if not transactions_dir.is_dir():
|
|
105
|
+
continue
|
|
106
|
+
records: list[tuple[dict[str, Any], Path]] = []
|
|
107
|
+
for path in transactions_dir.glob("*.json"):
|
|
108
|
+
payload = _read_json(path)
|
|
109
|
+
if payload is not None:
|
|
110
|
+
records.append((payload, path))
|
|
111
|
+
if not records:
|
|
112
|
+
continue
|
|
113
|
+
records.sort(key=lambda item: _operation_sort_key(item[0], item[1]))
|
|
114
|
+
first_payload, _first_path = records[0]
|
|
115
|
+
per_change.append(
|
|
116
|
+
{
|
|
117
|
+
"project": str(project),
|
|
118
|
+
"change": change.name,
|
|
119
|
+
"first_status": str(first_payload.get("status") or ""),
|
|
120
|
+
"first_try_ok": first_payload.get("status") == FINAL_STATUS,
|
|
121
|
+
"retry_count": len(records) - 1,
|
|
122
|
+
"sort": _operation_sort_key(first_payload, records[0][1]),
|
|
123
|
+
}
|
|
124
|
+
)
|
|
125
|
+
per_change.sort(key=lambda item: item["sort"])
|
|
126
|
+
window = per_change[-FIRST_TRY_WINDOW:]
|
|
127
|
+
sample = len(window)
|
|
128
|
+
ok_count = sum(1 for item in window if item["first_try_ok"])
|
|
129
|
+
if sample < FIRST_TRY_WINDOW:
|
|
130
|
+
status = "indeterminate"
|
|
131
|
+
detail = f"样本 {sample}/{FIRST_TRY_WINDOW},判据不可判定"
|
|
132
|
+
else:
|
|
133
|
+
status = "pass" if ok_count >= FIRST_TRY_THRESHOLD else "fail"
|
|
134
|
+
detail = (
|
|
135
|
+
f"最近 {sample} 次 finalize 中首次即 {FINAL_STATUS} 的有 "
|
|
136
|
+
f"{ok_count} 次(阈值 >= {FIRST_TRY_THRESHOLD})"
|
|
137
|
+
)
|
|
138
|
+
return {
|
|
139
|
+
"id": "finalize_first_try",
|
|
140
|
+
"reading": f"{ok_count}/{sample}",
|
|
141
|
+
"threshold": f">= {FIRST_TRY_THRESHOLD}/{FIRST_TRY_WINDOW}",
|
|
142
|
+
"sample": sample,
|
|
143
|
+
"status": status,
|
|
144
|
+
"detail": detail,
|
|
145
|
+
"changes": [
|
|
146
|
+
{key: value for key, value in item.items() if key != "sort"}
|
|
147
|
+
for item in window
|
|
148
|
+
],
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
# ---------------------------------------------------------------------------
|
|
153
|
+
# 判据 2:证据闭环
|
|
154
|
+
# ---------------------------------------------------------------------------
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _evidence_closure_for_change(change: Path) -> dict[str, Any]:
|
|
158
|
+
manifest = _read_json(change / "meta" / "scenario-manifest.json")
|
|
159
|
+
if manifest is None:
|
|
160
|
+
return {"change": change.name, "clean": False,
|
|
161
|
+
"reason": "scenario-manifest.json 缺失或不可解析"}
|
|
162
|
+
scenarios = manifest.get("scenarios")
|
|
163
|
+
if not isinstance(scenarios, list):
|
|
164
|
+
return {"change": change.name, "clean": False,
|
|
165
|
+
"reason": "scenario-manifest.json 无 scenarios 数组"}
|
|
166
|
+
ledger_scenarios = [
|
|
167
|
+
s for s in scenarios
|
|
168
|
+
if isinstance(s, dict) and s.get("requiredEvidenceKind") == "ledger"
|
|
169
|
+
]
|
|
170
|
+
|
|
171
|
+
uncovered: list[str] = []
|
|
172
|
+
if ledger_scenarios:
|
|
173
|
+
ledger_payload = None
|
|
174
|
+
for candidate in (
|
|
175
|
+
change / ".harness" / "state" / "changes" / change.name
|
|
176
|
+
/ "evidence" / "verification-ledger.json",
|
|
177
|
+
change / "evidence" / "verification-ledger.json",
|
|
178
|
+
):
|
|
179
|
+
ledger_payload = _read_json(candidate)
|
|
180
|
+
if ledger_payload is not None:
|
|
181
|
+
break
|
|
182
|
+
if ledger_payload is None:
|
|
183
|
+
uncovered = [
|
|
184
|
+
f"{s.get('id')}:verification-ledger.json 缺失或不可解析"
|
|
185
|
+
for s in ledger_scenarios
|
|
186
|
+
]
|
|
187
|
+
else:
|
|
188
|
+
declared = set(ledger_payload.get("declared") or [])
|
|
189
|
+
selected = set(ledger_payload.get("selected") or [])
|
|
190
|
+
collected_keys = {
|
|
191
|
+
(
|
|
192
|
+
str(item.get("testId") or ""),
|
|
193
|
+
str(item.get("file") or ""),
|
|
194
|
+
str(item.get("title") or ""),
|
|
195
|
+
)
|
|
196
|
+
for item in (ledger_payload.get("collected") or [])
|
|
197
|
+
if isinstance(item, dict)
|
|
198
|
+
}
|
|
199
|
+
for scenario in ledger_scenarios:
|
|
200
|
+
scenario_id = str(scenario.get("id") or "?")
|
|
201
|
+
test_id = str(scenario.get("executableTestId") or "").strip()
|
|
202
|
+
identity = (
|
|
203
|
+
test_id,
|
|
204
|
+
str(scenario.get("testFile") or "").strip(),
|
|
205
|
+
str(scenario.get("testTitle") or "").strip(),
|
|
206
|
+
)
|
|
207
|
+
if not test_id:
|
|
208
|
+
uncovered.append(f"{scenario_id}:缺 executableTestId,无法判定")
|
|
209
|
+
continue
|
|
210
|
+
if (
|
|
211
|
+
test_id not in declared
|
|
212
|
+
and test_id not in selected
|
|
213
|
+
and identity not in collected_keys
|
|
214
|
+
):
|
|
215
|
+
uncovered.append(f"{scenario_id}:ledger 未覆盖")
|
|
216
|
+
|
|
217
|
+
blocked = False
|
|
218
|
+
for events_path in (
|
|
219
|
+
change / "events.ndjson",
|
|
220
|
+
change / ".harness" / "state" / "changes" / change.name / "events.ndjson",
|
|
221
|
+
change / "meta" / "events.ndjson",
|
|
222
|
+
):
|
|
223
|
+
try:
|
|
224
|
+
text = events_path.read_text(encoding="utf-8-sig", errors="replace")
|
|
225
|
+
except OSError:
|
|
226
|
+
continue
|
|
227
|
+
if "SCENARIO_MANIFEST_" in text:
|
|
228
|
+
blocked = True
|
|
229
|
+
break
|
|
230
|
+
|
|
231
|
+
clean = not uncovered and not blocked
|
|
232
|
+
result: dict[str, Any] = {
|
|
233
|
+
"change": change.name,
|
|
234
|
+
"clean": clean,
|
|
235
|
+
"ledger_scenarios": len(ledger_scenarios),
|
|
236
|
+
}
|
|
237
|
+
if uncovered:
|
|
238
|
+
result["uncovered"] = uncovered
|
|
239
|
+
if blocked:
|
|
240
|
+
result["blocked_by"] = "SCENARIO_MANIFEST_*"
|
|
241
|
+
if clean:
|
|
242
|
+
result["reason"] = "ok"
|
|
243
|
+
return result
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def _collect_evidence_closure(projects: list[Path]) -> dict[str, Any]:
|
|
247
|
+
v2_changes: list[tuple[tuple[str, float, str], Path]] = []
|
|
248
|
+
for project in projects:
|
|
249
|
+
for change in _change_dirs(project):
|
|
250
|
+
transactions_dir = change / "meta" / "plan-finalization-transactions"
|
|
251
|
+
if not transactions_dir.is_dir() or not any(
|
|
252
|
+
transactions_dir.glob("*.json")
|
|
253
|
+
):
|
|
254
|
+
continue
|
|
255
|
+
# 窗口排序键与判据 1 一致:最早事务的 created_at。
|
|
256
|
+
earliest: tuple[str, float, str] | None = None
|
|
257
|
+
for path in transactions_dir.glob("*.json"):
|
|
258
|
+
payload = _read_json(path)
|
|
259
|
+
if payload is None:
|
|
260
|
+
continue
|
|
261
|
+
key = _operation_sort_key(payload, path)
|
|
262
|
+
if earliest is None or key < earliest:
|
|
263
|
+
earliest = key
|
|
264
|
+
v2_changes.append((earliest or _sort_key("", change.stat().st_mtime, change.name), change))
|
|
265
|
+
v2_changes.sort(key=lambda item: item[0])
|
|
266
|
+
window = [change for _key, change in v2_changes[-EVIDENCE_WINDOW:]]
|
|
267
|
+
sample = len(window)
|
|
268
|
+
results = [_evidence_closure_for_change(change) for change in window]
|
|
269
|
+
clean_count = sum(1 for item in results if item["clean"])
|
|
270
|
+
if sample < EVIDENCE_WINDOW:
|
|
271
|
+
status = "indeterminate"
|
|
272
|
+
detail = f"样本 {sample}/{EVIDENCE_WINDOW},判据不可判定"
|
|
273
|
+
else:
|
|
274
|
+
status = "pass" if clean_count == sample else "fail"
|
|
275
|
+
detail = (
|
|
276
|
+
f"最近 {sample} 个 v2 change 中证据闭环干净的有 {clean_count} 个"
|
|
277
|
+
f"(阈值 = {EVIDENCE_WINDOW}/{EVIDENCE_WINDOW})"
|
|
278
|
+
)
|
|
279
|
+
return {
|
|
280
|
+
"id": "evidence_closure",
|
|
281
|
+
"reading": f"{clean_count}/{sample}",
|
|
282
|
+
"threshold": f"{EVIDENCE_WINDOW}/{EVIDENCE_WINDOW} 干净",
|
|
283
|
+
"sample": sample,
|
|
284
|
+
"status": status,
|
|
285
|
+
"detail": detail,
|
|
286
|
+
"changes": results,
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
# ---------------------------------------------------------------------------
|
|
291
|
+
# 判据 3:legacy 回退率
|
|
292
|
+
# ---------------------------------------------------------------------------
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def _collect_legacy_fallback(projects: list[Path]) -> dict[str, Any]:
|
|
296
|
+
all_changes: list[tuple[float, str, Path]] = []
|
|
297
|
+
for project in projects:
|
|
298
|
+
for change in _change_dirs(project):
|
|
299
|
+
all_changes.append((change.stat().st_mtime, change.name, change))
|
|
300
|
+
all_changes.sort(key=lambda item: (item[0], item[1]))
|
|
301
|
+
window = [change for _mtime, _name, change in all_changes[-LEGACY_WINDOW:]]
|
|
302
|
+
sample = len(window)
|
|
303
|
+
legacy = [
|
|
304
|
+
change.name
|
|
305
|
+
for change in window
|
|
306
|
+
if (change / "meta" / "plan-finalization.json").is_file()
|
|
307
|
+
]
|
|
308
|
+
if sample < LEGACY_WINDOW:
|
|
309
|
+
status = "indeterminate"
|
|
310
|
+
detail = f"样本 {sample}/{LEGACY_WINDOW},判据不可判定"
|
|
311
|
+
else:
|
|
312
|
+
status = "pass" if len(legacy) <= LEGACY_THRESHOLD else "fail"
|
|
313
|
+
detail = (
|
|
314
|
+
f"最近 {sample} 个 change 中含 legacy plan-finalization.json 的有 "
|
|
315
|
+
f"{len(legacy)} 个(阈值 <= {LEGACY_THRESHOLD})"
|
|
316
|
+
)
|
|
317
|
+
return {
|
|
318
|
+
"id": "legacy_fallback",
|
|
319
|
+
"reading": f"{len(legacy)}/{sample}",
|
|
320
|
+
"threshold": f"<= {LEGACY_THRESHOLD}/{LEGACY_WINDOW}",
|
|
321
|
+
"sample": sample,
|
|
322
|
+
"status": status,
|
|
323
|
+
"detail": detail,
|
|
324
|
+
"legacy_changes": legacy,
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
# ---------------------------------------------------------------------------
|
|
329
|
+
# CLI
|
|
330
|
+
# ---------------------------------------------------------------------------
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def collect_metrics(projects: list[Path]) -> dict[str, Any]:
|
|
334
|
+
criteria = [
|
|
335
|
+
_collect_first_try(projects),
|
|
336
|
+
_collect_evidence_closure(projects),
|
|
337
|
+
_collect_legacy_fallback(projects),
|
|
338
|
+
]
|
|
339
|
+
overall = "pass"
|
|
340
|
+
if any(item["status"] == "fail" for item in criteria):
|
|
341
|
+
overall = "fail"
|
|
342
|
+
elif any(item["status"] == "indeterminate" for item in criteria):
|
|
343
|
+
overall = "indeterminate"
|
|
344
|
+
return {
|
|
345
|
+
"projects": [str(project) for project in projects],
|
|
346
|
+
"overall": overall,
|
|
347
|
+
"criteria": criteria,
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def _print_text(report: dict[str, Any]) -> None:
|
|
352
|
+
print(f"projects: {', '.join(report['projects']) or '(none)'}")
|
|
353
|
+
print(f"overall: {report['overall']}")
|
|
354
|
+
for criterion in report["criteria"]:
|
|
355
|
+
print(
|
|
356
|
+
f"[{criterion['status']:>13}] {criterion['id']}: "
|
|
357
|
+
f"{criterion['reading']}({criterion['detail']})"
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def main(argv: list[str] | None = None) -> int:
|
|
362
|
+
parser = argparse.ArgumentParser(
|
|
363
|
+
description="legacy plan finalize 采纳度度量(只读)",
|
|
364
|
+
)
|
|
365
|
+
parser.add_argument(
|
|
366
|
+
"--project",
|
|
367
|
+
action="append",
|
|
368
|
+
default=[],
|
|
369
|
+
help="项目根(含 .harness/changes/),可重复",
|
|
370
|
+
)
|
|
371
|
+
parser.add_argument("--json", action="store_true", help="JSON 输出")
|
|
372
|
+
parser.add_argument(
|
|
373
|
+
"--strict",
|
|
374
|
+
action="store_true",
|
|
375
|
+
help="任何 fail 或 indeterminate 时 exit 1",
|
|
376
|
+
)
|
|
377
|
+
args = parser.parse_args(argv)
|
|
378
|
+
|
|
379
|
+
projects = [Path(item).resolve() for item in args.project]
|
|
380
|
+
report = collect_metrics(projects)
|
|
381
|
+
if args.json:
|
|
382
|
+
print(json.dumps(report, ensure_ascii=False, indent=2))
|
|
383
|
+
else:
|
|
384
|
+
_print_text(report)
|
|
385
|
+
if args.strict and report["overall"] != "pass":
|
|
386
|
+
return 1
|
|
387
|
+
return 0
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
if __name__ == "__main__":
|
|
391
|
+
raise SystemExit(main())
|
|
@@ -3762,7 +3762,7 @@ def _risks_from_test_results(change_dir: Path) -> tuple[list[dict[str, Any]], li
|
|
|
3762
3762
|
note = str(item.get("note") or item.get("message") or "").strip()
|
|
3763
3763
|
risks.append(
|
|
3764
3764
|
{
|
|
3765
|
-
"phase": "
|
|
3765
|
+
"phase": "execute",
|
|
3766
3766
|
"severity": "medium",
|
|
3767
3767
|
"scenarioId": scenario_id,
|
|
3768
3768
|
"status": status,
|
|
@@ -3771,7 +3771,7 @@ def _risks_from_test_results(change_dir: Path) -> tuple[list[dict[str, Any]], li
|
|
|
3771
3771
|
)
|
|
3772
3772
|
actions.append(
|
|
3773
3773
|
{
|
|
3774
|
-
"stage": "
|
|
3774
|
+
"stage": "execute",
|
|
3775
3775
|
"status": status,
|
|
3776
3776
|
"scenarioId": scenario_id,
|
|
3777
3777
|
"action": "补齐该场景的完整验证或显式接受风险",
|
|
@@ -4028,15 +4028,17 @@ def _skill_calls_from_stages(stages: list[dict[str, Any]]) -> list[dict[str, Any
|
|
|
4028
4028
|
|
|
4029
4029
|
def _phases_from_events_summary(summary: dict[str, Any]) -> dict[str, Any]:
|
|
4030
4030
|
phases_out: dict[str, Any] = {}
|
|
4031
|
-
for name in ("plan", "
|
|
4031
|
+
for name in ("plan", "execute", "review", "submit", "archive"):
|
|
4032
4032
|
phases_out[name] = {"duration_ms": None, "event_count": 0}
|
|
4033
4033
|
for name, info in (summary.get("phases") or {}).items():
|
|
4034
|
-
|
|
4034
|
+
# 旧事件里的 run/test 折叠进 execute(event_count 求和、duration 取后写值)。
|
|
4035
|
+
key = hp.resolve_phase_name(str(name).lower()) or str(name).lower()
|
|
4035
4036
|
if key not in phases_out:
|
|
4036
4037
|
phases_out[key] = {"duration_ms": None, "event_count": 0}
|
|
4037
4038
|
phases_out[key] = {
|
|
4038
4039
|
"duration_ms": info.get("duration_ms"),
|
|
4039
|
-
"event_count": int(info.get("event_count") or 0)
|
|
4040
|
+
"event_count": int(info.get("event_count") or 0)
|
|
4041
|
+
+ int(phases_out[key].get("event_count") or 0),
|
|
4040
4042
|
}
|
|
4041
4043
|
return phases_out
|
|
4042
4044
|
|
|
@@ -4192,20 +4194,23 @@ def _stage_status_from_sources(
|
|
|
4192
4194
|
) -> dict[str, str]:
|
|
4193
4195
|
status = {
|
|
4194
4196
|
"plan": "OK",
|
|
4195
|
-
"
|
|
4196
|
-
"test": "OK",
|
|
4197
|
+
"execute": "OK",
|
|
4197
4198
|
"review": "ADVISORY",
|
|
4198
4199
|
"submit": "OK",
|
|
4199
4200
|
"archive": "OK",
|
|
4200
4201
|
}
|
|
4201
4202
|
projected_events = he.apply_event_corrections(events)
|
|
4203
|
+
execute_phase_end_seen = False
|
|
4202
4204
|
for event in projected_events:
|
|
4203
4205
|
if event.get("type") != "phase.end":
|
|
4204
4206
|
continue
|
|
4205
|
-
|
|
4207
|
+
# 旧事件的 run/test phase.end 折叠进 execute,按事件序后写胜。
|
|
4208
|
+
phase = hp.resolve_phase_name(str(event.get("phase") or "").lower()) or ""
|
|
4206
4209
|
raw = str(event.get("status") or "").upper()
|
|
4207
4210
|
if phase not in status or not raw:
|
|
4208
4211
|
continue
|
|
4212
|
+
if phase == "execute":
|
|
4213
|
+
execute_phase_end_seen = True
|
|
4209
4214
|
if raw in {"PASS", "PASSED", "SUCCESS"}:
|
|
4210
4215
|
raw = "OK"
|
|
4211
4216
|
elif raw in {"FAILED", "ERROR"}:
|
|
@@ -4238,7 +4243,7 @@ def _stage_status_from_sources(
|
|
|
4238
4243
|
continue
|
|
4239
4244
|
if sev in {"", "info", "note", "informational"}:
|
|
4240
4245
|
continue
|
|
4241
|
-
phase = str(e.get("phase") or "").lower()
|
|
4246
|
+
phase = hp.resolve_phase_name(str(e.get("phase") or "").lower()) or ""
|
|
4242
4247
|
if phase in status and sev in {"error", "fail", "failed", "critical"}:
|
|
4243
4248
|
status[phase] = "FAIL"
|
|
4244
4249
|
elif phase in status and sev in {"warn", "warning"} and status[phase] == "OK":
|
|
@@ -4248,17 +4253,21 @@ def _stage_status_from_sources(
|
|
|
4248
4253
|
db = _ledger_db_compat(ledger)
|
|
4249
4254
|
api_status = str(api.get("status") or "").upper()
|
|
4250
4255
|
if api_status in {"FAIL", "FAILED", "ERROR"} or int(api.get("failed") or 0) > 0:
|
|
4251
|
-
status["
|
|
4256
|
+
status["execute"] = "FAIL"
|
|
4252
4257
|
elif api_status in {"BLOCKED", "BLOCKED_BY_ENV", "BLOCKED_BY_DBA"}:
|
|
4253
|
-
status["
|
|
4258
|
+
status["execute"] = api_status
|
|
4254
4259
|
elif api_status == "USER_SKIPPED":
|
|
4255
|
-
status["
|
|
4260
|
+
status["execute"] = "USER_SKIPPED"
|
|
4256
4261
|
elif db == "BLOCKED_BY_DBA":
|
|
4257
|
-
status["
|
|
4262
|
+
status["execute"] = "BLOCKED_BY_DBA"
|
|
4258
4263
|
elif api.get("status") == "NOT_RUN" and not find_test_reports(change_dir):
|
|
4259
4264
|
unit = _ledger_unit_tests(ledger)
|
|
4260
4265
|
if unit.get("source") == "not-run" and unit.get("run", 0) == 0:
|
|
4261
|
-
|
|
4266
|
+
# "无证据按未运行"只在事件侧也没有终态信号时成立:execute 组已有
|
|
4267
|
+
# phase.end(含旧 run/test)说明阶段跑过,缺账本/报告不该把它抹成
|
|
4268
|
+
# NOT_RUN——FAIL/BLOCKED 等有据分支不受影响。
|
|
4269
|
+
if not execute_phase_end_seen:
|
|
4270
|
+
status["execute"] = "NOT_RUN"
|
|
4262
4271
|
|
|
4263
4272
|
if not review_evidence_present(change_dir, projected_events):
|
|
4264
4273
|
status["review"] = "ADVISORY"
|