@hunter-harness/workflow-harness 0.2.85 → 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 -9
- package/harness/bundles/general/claude-code/harness-plan/checklist.md +19 -91
- package/harness/bundles/general/claude-code/harness-plan/protocols.md +3 -1
- package/harness/bundles/general/claude-code/harness-plan/reference.md +74 -83
- 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 +13 -5
- 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_change.py +25 -7
- package/harness/bundles/general/claude-code/scripts/harness_context.py +154 -60
- 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 +14 -10
- package/harness/bundles/general/claude-code/scripts/harness_gate.py +293 -90
- package/harness/bundles/general/claude-code/scripts/harness_ledger.py +47 -2
- package/harness/bundles/general/claude-code/scripts/harness_paths.py +175 -0
- package/harness/bundles/general/claude-code/scripts/harness_phase.py +65 -23
- package/harness/bundles/general/claude-code/scripts/harness_plan_finalize.py +140 -874
- package/harness/bundles/general/claude-code/scripts/harness_review.py +25 -4
- 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 -9
- package/harness/bundles/general/codebuddy/harness-plan/checklist.md +19 -91
- package/harness/bundles/general/codebuddy/harness-plan/protocols.md +3 -1
- package/harness/bundles/general/codebuddy/harness-plan/reference.md +74 -83
- 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 +13 -5
- 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_change.py +25 -7
- package/harness/bundles/general/codebuddy/scripts/harness_context.py +154 -60
- 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 +14 -10
- package/harness/bundles/general/codebuddy/scripts/harness_gate.py +293 -90
- package/harness/bundles/general/codebuddy/scripts/harness_ledger.py +47 -2
- package/harness/bundles/general/codebuddy/scripts/harness_paths.py +175 -0
- package/harness/bundles/general/codebuddy/scripts/harness_phase.py +65 -23
- package/harness/bundles/general/codebuddy/scripts/harness_plan_finalize.py +140 -874
- package/harness/bundles/general/codebuddy/scripts/harness_review.py +25 -4
- 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 -9
- package/harness/bundles/general/codex/harness-plan/checklist.md +19 -91
- package/harness/bundles/general/codex/harness-plan/protocols.md +3 -1
- package/harness/bundles/general/codex/harness-plan/reference.md +74 -83
- 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 +13 -5
- 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_change.py +25 -7
- package/harness/bundles/general/codex/scripts/harness_context.py +154 -60
- 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 +14 -10
- package/harness/bundles/general/codex/scripts/harness_gate.py +293 -90
- package/harness/bundles/general/codex/scripts/harness_ledger.py +47 -2
- package/harness/bundles/general/codex/scripts/harness_paths.py +175 -0
- package/harness/bundles/general/codex/scripts/harness_phase.py +65 -23
- package/harness/bundles/general/codex/scripts/harness_plan_finalize.py +140 -874
- package/harness/bundles/general/codex/scripts/harness_review.py +25 -4
- 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 -9
- package/harness/bundles/general/cursor/harness-plan/checklist.md +19 -91
- package/harness/bundles/general/cursor/harness-plan/protocols.md +3 -1
- package/harness/bundles/general/cursor/harness-plan/reference.md +74 -83
- 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 +13 -5
- 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_change.py +25 -7
- package/harness/bundles/general/cursor/scripts/harness_context.py +154 -60
- 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 +14 -10
- package/harness/bundles/general/cursor/scripts/harness_gate.py +293 -90
- package/harness/bundles/general/cursor/scripts/harness_ledger.py +47 -2
- package/harness/bundles/general/cursor/scripts/harness_paths.py +175 -0
- package/harness/bundles/general/cursor/scripts/harness_phase.py +65 -23
- package/harness/bundles/general/cursor/scripts/harness_plan_finalize.py +140 -874
- package/harness/bundles/general/cursor/scripts/harness_review.py +25 -4
- 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 -9
- package/harness/bundles/java/claude-code/harness-plan/checklist.md +19 -91
- package/harness/bundles/java/claude-code/harness-plan/protocols.md +3 -1
- package/harness/bundles/java/claude-code/harness-plan/reference.md +74 -83
- 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 +13 -5
- 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_change.py +25 -7
- package/harness/bundles/java/claude-code/scripts/harness_context.py +154 -60
- 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 +14 -10
- package/harness/bundles/java/claude-code/scripts/harness_gate.py +293 -90
- package/harness/bundles/java/claude-code/scripts/harness_ledger.py +47 -2
- package/harness/bundles/java/claude-code/scripts/harness_paths.py +175 -0
- package/harness/bundles/java/claude-code/scripts/harness_phase.py +65 -23
- package/harness/bundles/java/claude-code/scripts/harness_plan_finalize.py +140 -874
- package/harness/bundles/java/claude-code/scripts/harness_review.py +25 -4
- 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 -9
- package/harness/bundles/java/codebuddy/harness-plan/checklist.md +19 -91
- package/harness/bundles/java/codebuddy/harness-plan/protocols.md +3 -1
- package/harness/bundles/java/codebuddy/harness-plan/reference.md +74 -83
- 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 +13 -5
- 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_change.py +25 -7
- package/harness/bundles/java/codebuddy/scripts/harness_context.py +154 -60
- 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 +14 -10
- package/harness/bundles/java/codebuddy/scripts/harness_gate.py +293 -90
- package/harness/bundles/java/codebuddy/scripts/harness_ledger.py +47 -2
- package/harness/bundles/java/codebuddy/scripts/harness_paths.py +175 -0
- package/harness/bundles/java/codebuddy/scripts/harness_phase.py +65 -23
- package/harness/bundles/java/codebuddy/scripts/harness_plan_finalize.py +140 -874
- package/harness/bundles/java/codebuddy/scripts/harness_review.py +25 -4
- 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 -9
- package/harness/bundles/java/codex/harness-plan/checklist.md +19 -91
- package/harness/bundles/java/codex/harness-plan/protocols.md +3 -1
- package/harness/bundles/java/codex/harness-plan/reference.md +74 -83
- 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 +13 -5
- 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_change.py +25 -7
- package/harness/bundles/java/codex/scripts/harness_context.py +154 -60
- 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 +14 -10
- package/harness/bundles/java/codex/scripts/harness_gate.py +293 -90
- package/harness/bundles/java/codex/scripts/harness_ledger.py +47 -2
- package/harness/bundles/java/codex/scripts/harness_paths.py +175 -0
- package/harness/bundles/java/codex/scripts/harness_phase.py +65 -23
- package/harness/bundles/java/codex/scripts/harness_plan_finalize.py +140 -874
- package/harness/bundles/java/codex/scripts/harness_review.py +25 -4
- 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 -9
- package/harness/bundles/java/cursor/harness-plan/checklist.md +19 -91
- package/harness/bundles/java/cursor/harness-plan/protocols.md +3 -1
- package/harness/bundles/java/cursor/harness-plan/reference.md +74 -83
- 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 +13 -5
- 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_change.py +25 -7
- package/harness/bundles/java/cursor/scripts/harness_context.py +154 -60
- 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 +14 -10
- package/harness/bundles/java/cursor/scripts/harness_gate.py +293 -90
- package/harness/bundles/java/cursor/scripts/harness_ledger.py +47 -2
- package/harness/bundles/java/cursor/scripts/harness_paths.py +175 -0
- package/harness/bundles/java/cursor/scripts/harness_phase.py +65 -23
- package/harness/bundles/java/cursor/scripts/harness_plan_finalize.py +140 -874
- package/harness/bundles/java/cursor/scripts/harness_review.py +25 -4
- package/harness/manifests/general/claude-code.json +40 -32
- package/harness/manifests/general/codebuddy.json +40 -32
- package/harness/manifests/general/codex.json +40 -32
- package/harness/manifests/general/cursor.json +40 -32
- package/harness/manifests/java/claude-code.json +42 -34
- package/harness/manifests/java/codebuddy.json +42 -34
- package/harness/manifests/java/codex.json +42 -34
- package/harness/manifests/java/cursor.json +42 -34
- package/hunter-workflow-family.json +4 -4
- package/package.json +1 -1
|
@@ -155,7 +155,47 @@ GATE_WARNINGS_REL = Path("evidence") / "gate-warnings.ndjson"
|
|
|
155
155
|
# Lifecycle order used to scope C9 scenario coverage by scenario ownerPhase.
|
|
156
156
|
# A scenario owned by a later phase is deferred, not missing, at an earlier
|
|
157
157
|
# phase close. Must stay a superset-ordering of hpf.VALID_OWNER_PHASES.
|
|
158
|
-
SCENARIO_OWNER_PHASE_ORDER = ("plan", "
|
|
158
|
+
SCENARIO_OWNER_PHASE_ORDER = ("plan", "execute", "review", "submit")
|
|
159
|
+
|
|
160
|
+
# ---------------------------------------------------------------------------
|
|
161
|
+
# 阶段能力表:每个阶段在 begin/close 里额外做什么,集中在这里声明。
|
|
162
|
+
#
|
|
163
|
+
# 这些开关以前是 cmd_begin / cmd_close 里散落的 `if args.phase == ...` —— 两个
|
|
164
|
+
# 五百行的函数,想知道"execute 阶段关门时到底跑哪几项"要通读全文。更要紧的是
|
|
165
|
+
# execute/review 的差异被摊平在控制流里,看不出它们其实共用同一套生命周期。
|
|
166
|
+
#
|
|
167
|
+
# 键的含义:
|
|
168
|
+
# plan_handoff begin 时校验 plan 已 finalize(只有进入实现的第一个阶段需要)
|
|
169
|
+
# test_guard begin 建测试基线快照、close 比对(改测试的阶段才需要)
|
|
170
|
+
# scenario_coverage close 时跑 C9 场景覆盖
|
|
171
|
+
# ledger_blocking close 时 ledger 校验失败是否阻断(否则只进 payload)
|
|
172
|
+
# review_outputs close 时校验 review sidecar 与 runId 绑定
|
|
173
|
+
# head_may_advance capsule 校验允许 HEAD 前移(会产生 commit 的阶段)
|
|
174
|
+
# projection_drift close 时 projection receipt 变化即硬失败(外发边界)
|
|
175
|
+
PHASE_GATE_RULES: dict[str, frozenset[str]] = {
|
|
176
|
+
"plan": frozenset(),
|
|
177
|
+
"execute": frozenset({
|
|
178
|
+
"plan_handoff", "test_guard", "scenario_coverage",
|
|
179
|
+
"ledger_blocking", "head_may_advance",
|
|
180
|
+
}),
|
|
181
|
+
"review": frozenset({"review_outputs"}),
|
|
182
|
+
"package": frozenset({"ledger_blocking"}),
|
|
183
|
+
"apidoc": frozenset(),
|
|
184
|
+
"submit": frozenset({"head_may_advance", "projection_drift"}),
|
|
185
|
+
"merge": frozenset({"head_may_advance"}),
|
|
186
|
+
"archive": frozenset({"projection_drift"}),
|
|
187
|
+
}
|
|
188
|
+
# 不在 WORKFLOW_PHASES 里、但 projection 门禁按发布阶段对待的名字。
|
|
189
|
+
_EXTRA_PROJECTION_DRIFT_PHASES = frozenset({"release", "deploy"})
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def phase_gate_rule(phase: str | None, rule: str) -> bool:
|
|
193
|
+
"""这个阶段是否启用某项门禁能力。未知阶段一律不启用(fail-safe)。"""
|
|
194
|
+
name = str(phase or "")
|
|
195
|
+
if rule == "projection_drift" and name in _EXTRA_PROJECTION_DRIFT_PHASES:
|
|
196
|
+
return True
|
|
197
|
+
resolved = hp.resolve_phase_name(name) or name
|
|
198
|
+
return rule in PHASE_GATE_RULES.get(resolved, frozenset())
|
|
159
199
|
|
|
160
200
|
|
|
161
201
|
def gate_severity_mode(project: Path, change_dir: Path | None = None) -> str:
|
|
@@ -440,8 +480,20 @@ def _phase_capsule_path(change_dir: Path, phase: str, run_id: str) -> Path:
|
|
|
440
480
|
def load_phase_capsule(
|
|
441
481
|
change_dir: Path, phase: str, run_id: str
|
|
442
482
|
) -> dict[str, Any] | None:
|
|
443
|
-
|
|
444
|
-
|
|
483
|
+
# 在途 change 的 capsule 可能按旧名(run/test)写入:先归一入参,canonical
|
|
484
|
+
# 查不到时对所有解析为该 canonical 的旧名各查一次。新写一律 canonical。
|
|
485
|
+
phase = hp.resolve_phase_name(phase) or phase
|
|
486
|
+
candidates = [phase] + [
|
|
487
|
+
legacy for legacy, canonical in hp.LEGACY_PHASE_ALIASES.items()
|
|
488
|
+
if canonical == phase
|
|
489
|
+
]
|
|
490
|
+
path: Path | None = None
|
|
491
|
+
for name in candidates:
|
|
492
|
+
candidate = _phase_capsule_path(change_dir, name, run_id)
|
|
493
|
+
if candidate.is_file():
|
|
494
|
+
path = candidate
|
|
495
|
+
break
|
|
496
|
+
if path is None:
|
|
445
497
|
return None
|
|
446
498
|
try:
|
|
447
499
|
data = _read_json(path)
|
|
@@ -554,19 +606,39 @@ def load_checkpoints(change_dir: Path) -> dict[str, Any] | None:
|
|
|
554
606
|
data = _read_json(path)
|
|
555
607
|
except (OSError, json.JSONDecodeError):
|
|
556
608
|
return None
|
|
609
|
+
# 刻意返回**原样**文档:cmd_checkpoint approve 会把这里的返回值写回盘,
|
|
610
|
+
# 返回归一化结构会用合成内容覆盖 v2 的哈希绑定产物(ARTIFACT_HASH_DRIFT)。
|
|
611
|
+
# 形状差异在只读的 checkpoint_status 里消化。
|
|
557
612
|
return data if isinstance(data, dict) else None
|
|
558
613
|
|
|
559
614
|
|
|
560
615
|
def checkpoint_status(checkpoints: dict[str, Any] | None, checkpoint_id: str) -> str:
|
|
616
|
+
"""Read a checkpoint's status across the three shapes this file is written in.
|
|
617
|
+
|
|
618
|
+
`meta/implementation-checkpoints.json` 有三个写入者,形状各不相同:
|
|
619
|
+
`harness_change.migrate` 写 ``checkpoints[{id,status}]``;legacy plan finalize 写
|
|
620
|
+
顶层 ``foundationGate``;v2 plan finalize 写 artifact 包装体,状态在
|
|
621
|
+
``content.foundation_gate``。以前只认第一种,另外两种一律返回 "missing"
|
|
622
|
+
——checkpoint 的状态对整条 v2 路径和 legacy finalize 之后都是读不出来的。
|
|
623
|
+
|
|
624
|
+
只读归一化:调用方拿到的是状态,不是文档。写回路径(cmd_checkpoint approve)
|
|
625
|
+
仍然操作原始文档,不能用归一化结构覆盖哈希绑定的 v2 产物。
|
|
626
|
+
"""
|
|
561
627
|
if not checkpoints:
|
|
562
628
|
return "missing"
|
|
563
629
|
items = checkpoints.get("checkpoints")
|
|
564
|
-
if
|
|
630
|
+
if isinstance(items, list):
|
|
631
|
+
for item in items:
|
|
632
|
+
if isinstance(item, dict) and item.get("id") == checkpoint_id:
|
|
633
|
+
return str(item.get("status") or "pending")
|
|
565
634
|
return "missing"
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
635
|
+
if checkpoint_id != "foundation-gate":
|
|
636
|
+
return "missing"
|
|
637
|
+
unpacked = hpf.unpack_v2_implementation_checkpoints(checkpoints)
|
|
638
|
+
if unpacked is not None:
|
|
639
|
+
return str(unpacked["checkpoints"][0]["status"])
|
|
640
|
+
legacy = checkpoints.get("foundationGate")
|
|
641
|
+
return str(legacy).strip() if str(legacy or "").strip() else "missing"
|
|
570
642
|
|
|
571
643
|
|
|
572
644
|
def foundation_gate_blocks(task_number: int | None, change_dir: Path) -> dict[str, Any] | None:
|
|
@@ -658,7 +730,16 @@ def validate_ledger_for_phase_close(
|
|
|
658
730
|
individual validation status to be FAIL/NOT_RUN, so a failing phase can
|
|
659
731
|
close honestly without polluting evidence or blocking lease release.
|
|
660
732
|
"""
|
|
661
|
-
|
|
733
|
+
resolved_phase = hp.resolve_phase_name(phase) or phase
|
|
734
|
+
# requiredValidations 的键可能还是合并前的旧名(在途 change 的旧契约),
|
|
735
|
+
# 入参也可能是旧名(手工 --phase run)——两侧都归一后再匹配;旧契约的
|
|
736
|
+
# run/test 两个键都映射到 execute,取并集。
|
|
737
|
+
required: list[str] = []
|
|
738
|
+
for key, value in (policy.get("requiredValidations") or {}).items():
|
|
739
|
+
if (hp.resolve_phase_name(key) or str(key)) == resolved_phase:
|
|
740
|
+
for item in value or []:
|
|
741
|
+
if item not in required:
|
|
742
|
+
required.append(item)
|
|
662
743
|
if not required:
|
|
663
744
|
return {"ok": True, "code": "LEDGER_NOT_REQUIRED", "phase": phase}
|
|
664
745
|
|
|
@@ -852,11 +933,17 @@ def validate_ledger_for_phase_close(
|
|
|
852
933
|
def effective_workflow_policy(
|
|
853
934
|
workflow: dict[str, Any], change_dir: Path
|
|
854
935
|
) -> dict[str, Any]:
|
|
855
|
-
"""Overlay a classified change's per-phase gate requirements.
|
|
856
|
-
|
|
857
|
-
|
|
936
|
+
"""Overlay a classified change's per-phase gate requirements.
|
|
937
|
+
|
|
938
|
+
权威切换(2026-08):经 ``harness_paths.load_change_gate_policy`` 读取——
|
|
939
|
+
v2 发布快照(meta/plan-profile.json)优先,classify 工作副本回退。
|
|
940
|
+
返回值保持与历史一致的纯净 workflow 形状;source/drift 由需要记录的
|
|
941
|
+
调用方另行调用 ``load_change_gate_policy`` 获取(见 gate begin 路径)。
|
|
942
|
+
"""
|
|
943
|
+
loaded = hp.load_change_gate_policy(change_dir)
|
|
944
|
+
document = loaded.get("policy")
|
|
945
|
+
if document is None:
|
|
858
946
|
return workflow
|
|
859
|
-
document = json.loads(path.read_text(encoding="utf-8"))
|
|
860
947
|
if not isinstance(document, dict) or document.get("schemaVersion") != 1:
|
|
861
948
|
raise ValueError("gate-policy.json must be a schemaVersion 1 object")
|
|
862
949
|
by_phase = document.get("requiredValidationsByPhase")
|
|
@@ -901,8 +988,13 @@ def validate_identity(
|
|
|
901
988
|
core_hash = str(build.get("coreHash") or "").strip()
|
|
902
989
|
if not agent or not core_hash:
|
|
903
990
|
raise ValueError("BUNDLE_IDENTITY_INVALID: agent/coreHash is required")
|
|
904
|
-
|
|
905
|
-
|
|
991
|
+
|
|
992
|
+
# 「这个 bundle 是否可信」和「现在是哪个工具在跑」是两件事。以前
|
|
993
|
+
# executor_tool != agent 直接 BUNDLE_IDENTITY_MISMATCH,于是 Codex 换
|
|
994
|
+
# CodeBuddy 接手同一个 change 会被当成 bundle 身份不符挡下来。下面每一项
|
|
995
|
+
# 校验用的都是 bundle 自称的 agent,与当前工具无关;工具名降级为审计字段,
|
|
996
|
+
# 随 phase.start 事件留痕。
|
|
997
|
+
executor_matches_bundle = not executor_tool or executor_tool == agent
|
|
906
998
|
|
|
907
999
|
context = _read_json(project / ".harness" / "context-index.json")
|
|
908
1000
|
installed = _read_json(
|
|
@@ -953,6 +1045,8 @@ def validate_identity(
|
|
|
953
1045
|
"adapter": agent,
|
|
954
1046
|
"buildMarkerHash": actual_hash,
|
|
955
1047
|
"contextIndexPresent": True,
|
|
1048
|
+
"executorTool": executor_tool or None,
|
|
1049
|
+
"executorMatchesBundle": executor_matches_bundle,
|
|
956
1050
|
}
|
|
957
1051
|
|
|
958
1052
|
|
|
@@ -1266,7 +1360,7 @@ def _apply_required_gate_contract(
|
|
|
1266
1360
|
if stage_name == "package":
|
|
1267
1361
|
dependencies = [
|
|
1268
1362
|
f"validation:{item}" for item in required
|
|
1269
|
-
if validation_phases.get(item)
|
|
1363
|
+
if hp.resolve_phase_name(validation_phases.get(item)) == "execute"
|
|
1270
1364
|
]
|
|
1271
1365
|
elif stage_name == "apidoc" and "apiTest" in required_set:
|
|
1272
1366
|
dependencies = ["validation:apiTest"]
|
|
@@ -1295,8 +1389,13 @@ def classify_risk(
|
|
|
1295
1389
|
stage: str,
|
|
1296
1390
|
workflow: dict[str, Any] | None = None,
|
|
1297
1391
|
) -> dict[str, Any]:
|
|
1298
|
-
|
|
1299
|
-
|
|
1392
|
+
# 无风险信号时的起步档。曾经是 full,于是每个普通变更都默认背上
|
|
1393
|
+
# plan→execute→review→submit→archive 五阶段和 apiTest;风险信号推断只在
|
|
1394
|
+
# --stage post-run 下跑,而生产流程没有任何地方调用它,起步值实际就是终值。
|
|
1395
|
+
# standard 保留 compile/unitTest/unitTestFull 的证据闭环,只去掉默认的
|
|
1396
|
+
# review 阶段与 apiTest。下面的单调升级逻辑不变,有信号照样升到 full。
|
|
1397
|
+
tier = "standard"
|
|
1398
|
+
source = "default-standard"
|
|
1300
1399
|
capabilities = _design_capabilities(change_dir)
|
|
1301
1400
|
plan_path = change_dir / "plans"
|
|
1302
1401
|
for candidate in sorted(plan_path.glob("*.md")) if plan_path.is_dir() else []:
|
|
@@ -1551,8 +1650,9 @@ def classify_defaults(
|
|
|
1551
1650
|
change_id: str,
|
|
1552
1651
|
stage: str,
|
|
1553
1652
|
) -> dict[str, Any]:
|
|
1554
|
-
|
|
1555
|
-
|
|
1653
|
+
# change 目录不存在时的兜底,与 classify_risk 的起步档保持一致。
|
|
1654
|
+
tier = "standard"
|
|
1655
|
+
source = "default-standard"
|
|
1556
1656
|
tier_policy = workflow["riskTiers"][tier]
|
|
1557
1657
|
payload = {
|
|
1558
1658
|
"ok": True,
|
|
@@ -1668,6 +1768,7 @@ def append_phase_event(
|
|
|
1668
1768
|
executor_agent: str | None = None,
|
|
1669
1769
|
executor_model: str | None = None,
|
|
1670
1770
|
code: str | None = None,
|
|
1771
|
+
fixback: bool = False,
|
|
1671
1772
|
) -> dict[str, Any]:
|
|
1672
1773
|
"""Skill-facing phase lifecycle append (begin/close use this directly).
|
|
1673
1774
|
|
|
@@ -1720,8 +1821,17 @@ def append_phase_event(
|
|
|
1720
1821
|
execution_mode=None,
|
|
1721
1822
|
decision_reason_code=None,
|
|
1722
1823
|
fallback_reason_code=None,
|
|
1723
|
-
|
|
1724
|
-
|
|
1824
|
+
# 显式信号优先。以前只嗅探 note 文本,而真正的 fixback 启动路径
|
|
1825
|
+
# (harness_fixback._default_gate_begin)写的 note 是
|
|
1826
|
+
# "开始处理评审中确认需要修改的代码问题。",一个 "fixback" 字都没有
|
|
1827
|
+
# ——于是 fixback 的 run 事件从来没被打上 trigger/from_phase。
|
|
1828
|
+
# note 嗅探保留为兼容回退:手工传 --note 带 fixback 的老用法仍然有效。
|
|
1829
|
+
trigger=("fixback" if fixback or "fixback" in note.lower() else None),
|
|
1830
|
+
from_phase=(
|
|
1831
|
+
"review"
|
|
1832
|
+
if phase == "execute" and (fixback or "fixback" in note.lower())
|
|
1833
|
+
else None
|
|
1834
|
+
),
|
|
1725
1835
|
result_status=None,
|
|
1726
1836
|
)
|
|
1727
1837
|
event = he.build_event(args, existing)
|
|
@@ -1856,7 +1966,7 @@ def validate_context_for_gate_begin(
|
|
|
1856
1966
|
if not isinstance(current, dict):
|
|
1857
1967
|
return {"ok": True, "code": "CONTEXT_LEGACY_COMPATIBLE"}
|
|
1858
1968
|
current_phase = str(current.get("phase") or view.get("currentPhase") or "")
|
|
1859
|
-
if current_phase != phase:
|
|
1969
|
+
if hp.resolve_phase_name(current_phase) != phase:
|
|
1860
1970
|
return {
|
|
1861
1971
|
"ok": False,
|
|
1862
1972
|
"code": "CONTEXT_HANDOFF_REQUIRED",
|
|
@@ -1870,11 +1980,11 @@ def validate_context_for_gate_begin(
|
|
|
1870
1980
|
begin_items = begins if isinstance(begins, list) else []
|
|
1871
1981
|
acknowledged = any(
|
|
1872
1982
|
isinstance(item, dict)
|
|
1873
|
-
and item.get("phase") == phase
|
|
1983
|
+
and hp.resolve_phase_name(item.get("phase")) == phase
|
|
1874
1984
|
and item.get("receiptHash") == receipt_hash
|
|
1875
1985
|
for item in begin_items
|
|
1876
1986
|
)
|
|
1877
|
-
if latest.get("toPhase") != phase or not acknowledged:
|
|
1987
|
+
if hp.resolve_phase_name(latest.get("toPhase")) != phase or not acknowledged:
|
|
1878
1988
|
return {
|
|
1879
1989
|
"ok": False,
|
|
1880
1990
|
"code": "CONTEXT_BEGIN_REQUIRED",
|
|
@@ -1886,7 +1996,7 @@ def validate_context_for_gate_begin(
|
|
|
1886
1996
|
|
|
1887
1997
|
def _phase_event_exists(change_dir: Path, phase: str, type_: str, run_id: str) -> bool:
|
|
1888
1998
|
return any(
|
|
1889
|
-
event.get("phase") == phase
|
|
1999
|
+
hp.resolve_phase_name(event.get("phase")) == phase
|
|
1890
2000
|
and event.get("type") == type_
|
|
1891
2001
|
and event.get("run_id") == run_id
|
|
1892
2002
|
for event in he.load_events(he.events_path(change_dir))
|
|
@@ -1899,7 +2009,10 @@ def _latest_phase_end(
|
|
|
1899
2009
|
run_id: str | None = None,
|
|
1900
2010
|
) -> dict[str, Any] | None:
|
|
1901
2011
|
for event in reversed(he.load_events(he.events_path(change_dir))):
|
|
1902
|
-
if
|
|
2012
|
+
if (
|
|
2013
|
+
hp.resolve_phase_name(event.get("phase")) != phase
|
|
2014
|
+
or event.get("type") != "phase.end"
|
|
2015
|
+
):
|
|
1903
2016
|
continue
|
|
1904
2017
|
if run_id and event.get("run_id") != run_id:
|
|
1905
2018
|
continue
|
|
@@ -1917,7 +2030,7 @@ def _latest_open_run_id(change_dir: Path, phase: str) -> str | None:
|
|
|
1917
2030
|
started: list[str] = []
|
|
1918
2031
|
ended: set[str] = set()
|
|
1919
2032
|
for event in he.load_events(he.events_path(change_dir)):
|
|
1920
|
-
if event.get("phase") != phase:
|
|
2033
|
+
if hp.resolve_phase_name(event.get("phase")) != phase:
|
|
1921
2034
|
continue
|
|
1922
2035
|
run_id = str(event.get("run_id") or "")
|
|
1923
2036
|
if not run_id:
|
|
@@ -1963,12 +2076,15 @@ def _close_context_handoff(
|
|
|
1963
2076
|
to_phase = getattr(args, "to_phase", None)
|
|
1964
2077
|
if not to_phase:
|
|
1965
2078
|
return None
|
|
2079
|
+
# 写边界归一:旧名(--to-phase test)落 canonical;未知名交给
|
|
2080
|
+
# close_transition 的 PHASE_UNKNOWN 报错,不在此吞掉。
|
|
2081
|
+
to_phase = hp.resolve_phase_name(to_phase) or str(to_phase)
|
|
1966
2082
|
try:
|
|
1967
2083
|
return hctx.close_transition(
|
|
1968
2084
|
project,
|
|
1969
2085
|
change_id,
|
|
1970
2086
|
from_phase=args.phase,
|
|
1971
|
-
to_phase=
|
|
2087
|
+
to_phase=to_phase,
|
|
1972
2088
|
executor=getattr(args, "executor", None),
|
|
1973
2089
|
artifacts=list(getattr(args, "artifact", None) or []),
|
|
1974
2090
|
status=status,
|
|
@@ -1997,6 +2113,10 @@ def _scenario_owner_phase_rank(owner_phase: str | None) -> int | None:
|
|
|
1997
2113
|
if not owner_phase:
|
|
1998
2114
|
return None
|
|
1999
2115
|
normalized = str(owner_phase).strip().lower()
|
|
2116
|
+
# 旧 manifest 的 ownerPhase=run/test 经别名表归一到 execute 后再定秩。
|
|
2117
|
+
resolved = hp.resolve_phase_name(normalized)
|
|
2118
|
+
if resolved is not None:
|
|
2119
|
+
normalized = resolved
|
|
2000
2120
|
if normalized not in hpf.VALID_OWNER_PHASES:
|
|
2001
2121
|
return None
|
|
2002
2122
|
try:
|
|
@@ -2068,52 +2188,6 @@ def _resolve_project(args: argparse.Namespace) -> Path:
|
|
|
2068
2188
|
return hc.resolve_main_project_root()
|
|
2069
2189
|
|
|
2070
2190
|
|
|
2071
|
-
# 门禁消费的场景字段。v2 plan finalize 派生的 scenario_manifest 一个都没有:
|
|
2072
|
-
# 它只带 scenario_id/coverage_dimension/execution_level/evidence_requirements/
|
|
2073
|
-
# risk_level/task_refs/requirement_refs。
|
|
2074
|
-
_V2_MANIFEST_REQUIRED_FIELDS = (
|
|
2075
|
-
"id",
|
|
2076
|
-
"priority",
|
|
2077
|
-
"requiredEvidenceKind",
|
|
2078
|
-
"ownerPhase",
|
|
2079
|
-
"executableTestId",
|
|
2080
|
-
"testFile",
|
|
2081
|
-
"testTitle",
|
|
2082
|
-
)
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
def _v2_artifact_manifest_gap(manifest: Any) -> dict[str, Any] | None:
|
|
2086
|
-
"""v2 plan artifact 包装的 scenario-manifest → 明确的不可消费错误。
|
|
2087
|
-
|
|
2088
|
-
不能"解包 + 字段改名"了事:包装体里既没有 priority 也没有
|
|
2089
|
-
requiredEvidenceKind,`required_ids` 会算成空集,C9 随即返回
|
|
2090
|
-
NO_LEDGER_REQUIRED_SCENARIOS——那是把证据门禁对所有 v2 计划静默关掉,
|
|
2091
|
-
比直接失败危险得多。所以这里点名缺口,让调用方回规划阶段补齐后重新发布。
|
|
2092
|
-
|
|
2093
|
-
v2 场景契约补齐这些字段后,解包逻辑加在这里。
|
|
2094
|
-
"""
|
|
2095
|
-
if not isinstance(manifest, dict) or manifest.get("artifact_type") != "scenario_manifest":
|
|
2096
|
-
return None
|
|
2097
|
-
content = manifest.get("content")
|
|
2098
|
-
present: set[str] = set()
|
|
2099
|
-
if isinstance(content, dict) and isinstance(content.get("scenarios"), list):
|
|
2100
|
-
for scenario in content["scenarios"]:
|
|
2101
|
-
if isinstance(scenario, dict):
|
|
2102
|
-
present.update(scenario.keys())
|
|
2103
|
-
return {
|
|
2104
|
-
"ok": False,
|
|
2105
|
-
"code": "SCENARIO_MANIFEST_V2_UNSUPPORTED",
|
|
2106
|
-
"message": (
|
|
2107
|
-
"meta/scenario-manifest.json 是 v2 plan artifact 包装体,缺少门禁消费的场景字段;"
|
|
2108
|
-
"请在规划阶段补齐后重新发布,不要手改派生产物"
|
|
2109
|
-
),
|
|
2110
|
-
"missingFields": [
|
|
2111
|
-
field for field in _V2_MANIFEST_REQUIRED_FIELDS if field not in present
|
|
2112
|
-
],
|
|
2113
|
-
"artifactType": "scenario_manifest",
|
|
2114
|
-
}
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
2191
|
def _validate_scenario_coverage(
|
|
2118
2192
|
change_dir: Path, phase: str | None = None
|
|
2119
2193
|
) -> dict[str, Any]:
|
|
@@ -2140,9 +2214,12 @@ def _validate_scenario_coverage(
|
|
|
2140
2214
|
"code": "SCENARIO_MANIFEST_INVALID",
|
|
2141
2215
|
"message": f"scenario-manifest.json unreadable: {exc}",
|
|
2142
2216
|
}
|
|
2143
|
-
|
|
2144
|
-
if
|
|
2145
|
-
|
|
2217
|
+
unpacked = hpf.unpack_v2_scenario_manifest(manifest)
|
|
2218
|
+
if unpacked is not None:
|
|
2219
|
+
if not unpacked.get("ok"):
|
|
2220
|
+
return unpacked
|
|
2221
|
+
# 解包成 legacy 形状后,下面的判定逻辑对 v2 与 legacy 完全一致。
|
|
2222
|
+
manifest = unpacked["manifest"]
|
|
2146
2223
|
scenarios = manifest.get("scenarios") if isinstance(manifest, dict) else None
|
|
2147
2224
|
if not isinstance(scenarios, list):
|
|
2148
2225
|
return {
|
|
@@ -2473,8 +2550,32 @@ def validate_review_outputs_for_close(
|
|
|
2473
2550
|
}
|
|
2474
2551
|
|
|
2475
2552
|
|
|
2553
|
+
def _normalize_gate_phase(args: argparse.Namespace, as_json: bool) -> int | None:
|
|
2554
|
+
"""cmd_begin/cmd_close 入口归一:旧名(run/test)映射到现阶段名。
|
|
2555
|
+
|
|
2556
|
+
归一后 args.phase 全链路只流动 canonical 名;未知名报错并列出合法名与别名。
|
|
2557
|
+
"""
|
|
2558
|
+
resolved = hp.resolve_phase_name(getattr(args, "phase", None))
|
|
2559
|
+
if resolved is not None:
|
|
2560
|
+
args.phase = resolved
|
|
2561
|
+
return None
|
|
2562
|
+
return emit_error(
|
|
2563
|
+
"PHASE_UNKNOWN",
|
|
2564
|
+
f"未知阶段名 {getattr(args, 'phase', None)!r};合法阶段 "
|
|
2565
|
+
f"{list(hp.WORKFLOW_PHASES)},旧名别名 {hp.LEGACY_PHASE_ALIASES}。",
|
|
2566
|
+
as_json=as_json,
|
|
2567
|
+
extra={
|
|
2568
|
+
"allowedPhases": list(hp.WORKFLOW_PHASES),
|
|
2569
|
+
"legacyAliases": dict(hp.LEGACY_PHASE_ALIASES),
|
|
2570
|
+
},
|
|
2571
|
+
)
|
|
2572
|
+
|
|
2573
|
+
|
|
2476
2574
|
def cmd_begin(args: argparse.Namespace) -> int:
|
|
2477
2575
|
as_json = bool(args.json)
|
|
2576
|
+
normalized = _normalize_gate_phase(args, as_json)
|
|
2577
|
+
if normalized is not None:
|
|
2578
|
+
return normalized
|
|
2478
2579
|
project = hc.resolve_main_project_root()
|
|
2479
2580
|
resolved = hc.resolve_change(project, args.change)
|
|
2480
2581
|
if not resolved.get("ok"):
|
|
@@ -2514,7 +2615,7 @@ def cmd_begin(args: argparse.Namespace) -> int:
|
|
|
2514
2615
|
severity_mode = gate_severity_mode(project, change_dir)
|
|
2515
2616
|
gate_warnings: list[dict[str, Any]] = []
|
|
2516
2617
|
plan_verification: dict[str, Any] | None = None
|
|
2517
|
-
if args.phase
|
|
2618
|
+
if phase_gate_rule(args.phase, "plan_handoff"):
|
|
2518
2619
|
plan_verification = validate_plan_handoff(change_dir)
|
|
2519
2620
|
if not plan_verification.get("ok"):
|
|
2520
2621
|
code = str(
|
|
@@ -2605,7 +2706,9 @@ def cmd_begin(args: argparse.Namespace) -> int:
|
|
|
2605
2706
|
current_lease = hc.inspect_lease(project, resolved["changeId"])
|
|
2606
2707
|
run_id = explicit_run_id or (
|
|
2607
2708
|
str(current_lease.get("runId"))
|
|
2608
|
-
if isinstance(current_lease, dict) and
|
|
2709
|
+
if isinstance(current_lease, dict) and (
|
|
2710
|
+
hp.resolve_phase_name(current_lease.get("phase")) == args.phase
|
|
2711
|
+
)
|
|
2609
2712
|
else "run-" + uuid.uuid4().hex
|
|
2610
2713
|
)
|
|
2611
2714
|
try:
|
|
@@ -2700,7 +2803,7 @@ def cmd_begin(args: argparse.Namespace) -> int:
|
|
|
2700
2803
|
if capsule is not None:
|
|
2701
2804
|
guard_result = {"ok": True, "code": "SNAPSHOT_REUSED"}
|
|
2702
2805
|
else:
|
|
2703
|
-
if args.phase
|
|
2806
|
+
if phase_gate_rule(args.phase, "test_guard"):
|
|
2704
2807
|
guard_result = htg.begin(execution_root, change_dir)
|
|
2705
2808
|
if not guard_result.get("ok"):
|
|
2706
2809
|
if gate_soft_allowed(severity_mode, args.phase, "test-guard"):
|
|
@@ -2767,6 +2870,7 @@ def cmd_begin(args: argparse.Namespace) -> int:
|
|
|
2767
2870
|
phase=args.phase,
|
|
2768
2871
|
type_="phase.start",
|
|
2769
2872
|
note=args.note or "",
|
|
2873
|
+
fixback=bool(getattr(args, "fixback", False)),
|
|
2770
2874
|
identity=identity,
|
|
2771
2875
|
run_id=run_id,
|
|
2772
2876
|
executor_tool=executor_tool,
|
|
@@ -2828,6 +2932,9 @@ def cmd_begin(args: argparse.Namespace) -> int:
|
|
|
2828
2932
|
|
|
2829
2933
|
def cmd_close(args: argparse.Namespace) -> int:
|
|
2830
2934
|
as_json = bool(args.json)
|
|
2935
|
+
normalized = _normalize_gate_phase(args, as_json)
|
|
2936
|
+
if normalized is not None:
|
|
2937
|
+
return normalized
|
|
2831
2938
|
project = hc.resolve_main_project_root()
|
|
2832
2939
|
resolved = hc.resolve_change(project, args.change)
|
|
2833
2940
|
if not resolved.get("ok"):
|
|
@@ -2857,6 +2964,83 @@ def cmd_close(args: argparse.Namespace) -> int:
|
|
|
2857
2964
|
|
|
2858
2965
|
explicit_run_id = args.run_id or os.environ.get("HUNTER_HARNESS_RUN_ID")
|
|
2859
2966
|
current_lease = hc.inspect_lease(project, resolved["changeId"])
|
|
2967
|
+
lease_lapsed: dict[str, Any] | None = None
|
|
2968
|
+
# 只有在"没有活跃租约"时才需要区分不存在/已过期/已损坏——三者要给的答案
|
|
2969
|
+
# 完全不同,而 inspect_lease 把它们一律压成 None。
|
|
2970
|
+
lease_state = (
|
|
2971
|
+
{"state": "active", "lease": current_lease}
|
|
2972
|
+
if current_lease is not None
|
|
2973
|
+
else hc.inspect_lease_state(project, resolved["changeId"])
|
|
2974
|
+
)
|
|
2975
|
+
|
|
2976
|
+
if lease_state["state"] == "corrupt":
|
|
2977
|
+
# 损坏的租约证明不了"没人抢占过",所以不自动恢复——那正是自动重取唯一
|
|
2978
|
+
# 的安全前提。
|
|
2979
|
+
return emit_error(
|
|
2980
|
+
"LEASE_INVALID",
|
|
2981
|
+
"phase lease file is unreadable; it cannot prove the phase was not taken over",
|
|
2982
|
+
as_json=as_json,
|
|
2983
|
+
extra={
|
|
2984
|
+
"phase": args.phase,
|
|
2985
|
+
"changeId": resolved["changeId"],
|
|
2986
|
+
"recoveryAction": (
|
|
2987
|
+
"租约文件已损坏,无法自动恢复。确认没有其他进程在跑同一个 change 后,"
|
|
2988
|
+
"用本阶段原 run-id 重新取得租约再重试 close:"
|
|
2989
|
+
"python <skills-root>/scripts/harness_change.py claim "
|
|
2990
|
+
f"--change {resolved['changeId']} --phase {args.phase} "
|
|
2991
|
+
"--run-id <原 run-id> --ttl-seconds 3600 --json"
|
|
2992
|
+
),
|
|
2993
|
+
},
|
|
2994
|
+
)
|
|
2995
|
+
|
|
2996
|
+
if lease_state["state"] == "expired":
|
|
2997
|
+
expired_lease = lease_state["lease"] or {}
|
|
2998
|
+
expired_run_id = str(expired_lease.get("runId") or "")
|
|
2999
|
+
expected_run_id = explicit_run_id or expired_run_id
|
|
3000
|
+
# 租约过期、runId 还是本轮的,恰好证明没有第三方抢占过:抢占会重写
|
|
3001
|
+
# 租约文件把 runId 换掉。所以过期只说明这个阶段跑得比 TTL 久,不说明
|
|
3002
|
+
# 所有权变了——harness_context.close_transition 早就是这么推理的
|
|
3003
|
+
# (只记 leaseLapsed 照常收尾),这里把 Gate 侧对齐过去,不再要求
|
|
3004
|
+
# 用户手工 claim 一遍再原样重试。
|
|
3005
|
+
if (
|
|
3006
|
+
expired_run_id
|
|
3007
|
+
and expired_run_id == expected_run_id
|
|
3008
|
+
and hp.resolve_phase_name(expired_lease.get("phase")) == args.phase
|
|
3009
|
+
):
|
|
3010
|
+
reacquired = hc.claim_lease(
|
|
3011
|
+
project,
|
|
3012
|
+
change_id=str(resolved["changeId"]),
|
|
3013
|
+
phase=args.phase,
|
|
3014
|
+
run_id=expired_run_id,
|
|
3015
|
+
ttl_seconds=int(getattr(args, "ttl_seconds", 0) or 3600),
|
|
3016
|
+
)
|
|
3017
|
+
if not reacquired.get("ok"):
|
|
3018
|
+
return emit_error(
|
|
3019
|
+
str(reacquired.get("code") or "LEASE_CONFLICT"),
|
|
3020
|
+
"expired phase lease could not be reacquired for close",
|
|
3021
|
+
as_json=as_json,
|
|
3022
|
+
extra={
|
|
3023
|
+
"phase": args.phase,
|
|
3024
|
+
"changeId": resolved["changeId"],
|
|
3025
|
+
"holder": reacquired.get("holder"),
|
|
3026
|
+
},
|
|
3027
|
+
)
|
|
3028
|
+
current_lease = reacquired.get("lease")
|
|
3029
|
+
lease_lapsed = {
|
|
3030
|
+
"acquiredAt": expired_lease.get("acquiredAt"),
|
|
3031
|
+
"expiresAt": expired_lease.get("expiresAt"),
|
|
3032
|
+
"reacquiredAt": (current_lease or {}).get("refreshedAt")
|
|
3033
|
+
or (current_lease or {}).get("acquiredAt"),
|
|
3034
|
+
"runId": expired_run_id,
|
|
3035
|
+
}
|
|
3036
|
+
else:
|
|
3037
|
+
return emit_error(
|
|
3038
|
+
"LEASE_OWNER_MISMATCH",
|
|
3039
|
+
"expired lease belongs to a different run or phase",
|
|
3040
|
+
as_json=as_json,
|
|
3041
|
+
extra={"holder": expired_lease},
|
|
3042
|
+
)
|
|
3043
|
+
|
|
2860
3044
|
if current_lease is None:
|
|
2861
3045
|
terminal = _latest_phase_end(change_dir, args.phase, explicit_run_id)
|
|
2862
3046
|
if (
|
|
@@ -2911,8 +3095,10 @@ def cmd_close(args: argparse.Namespace) -> int:
|
|
|
2911
3095
|
"changeId": resolved["changeId"],
|
|
2912
3096
|
"resumeRunId": resume_run_id,
|
|
2913
3097
|
"recoveryAction": (
|
|
2914
|
-
"
|
|
2915
|
-
"
|
|
3098
|
+
"本阶段没有任何租约记录——过期租约现在由 close 自动重取,所以这里说明的是"
|
|
3099
|
+
"租约从未建立或已被释放。先确认 gate begin 是否真的跑过(events.ndjson 里"
|
|
3100
|
+
f"应有 phase={args.phase} 的 phase.start)。确有本轮 run-id 时用它重新取得"
|
|
3101
|
+
"租约再原样重试 close:"
|
|
2916
3102
|
"python <skills-root>/scripts/harness_change.py claim "
|
|
2917
3103
|
f"--change {resolved['changeId']} --phase {args.phase} "
|
|
2918
3104
|
f"--run-id {resume_run_id or '<原 run-id>'} --ttl-seconds 3600 --json"
|
|
@@ -2921,7 +3107,9 @@ def cmd_close(args: argparse.Namespace) -> int:
|
|
|
2921
3107
|
},
|
|
2922
3108
|
)
|
|
2923
3109
|
run_id = explicit_run_id or str(current_lease.get("runId") or "")
|
|
2924
|
-
if str(current_lease.get("runId")) != run_id or
|
|
3110
|
+
if str(current_lease.get("runId")) != run_id or (
|
|
3111
|
+
hp.resolve_phase_name(current_lease.get("phase")) != args.phase
|
|
3112
|
+
):
|
|
2925
3113
|
return emit_error(
|
|
2926
3114
|
"LEASE_OWNER_MISMATCH",
|
|
2927
3115
|
"active lease does not match close phase/run-id",
|
|
@@ -2994,7 +3182,7 @@ def cmd_close(args: argparse.Namespace) -> int:
|
|
|
2994
3182
|
run_id=run_id,
|
|
2995
3183
|
project=project,
|
|
2996
3184
|
execution_root=execution_root,
|
|
2997
|
-
allow_head_advance=args.phase
|
|
3185
|
+
allow_head_advance=phase_gate_rule(args.phase, "head_may_advance"),
|
|
2998
3186
|
)
|
|
2999
3187
|
except ValueError as exc:
|
|
3000
3188
|
if gate_soft_allowed(severity_mode, args.phase, "capsule"):
|
|
@@ -3049,7 +3237,7 @@ def cmd_close(args: argparse.Namespace) -> int:
|
|
|
3049
3237
|
and captured_projection.get("receiptHash")
|
|
3050
3238
|
and captured_projection.get("receiptHash")
|
|
3051
3239
|
!= projection.get("receiptHash")
|
|
3052
|
-
and args.phase
|
|
3240
|
+
and phase_gate_rule(args.phase, "projection_drift")
|
|
3053
3241
|
):
|
|
3054
3242
|
mismatch = {
|
|
3055
3243
|
"code": "PROJECTION_CHANGED_DURING_PHASE",
|
|
@@ -3076,7 +3264,7 @@ def cmd_close(args: argparse.Namespace) -> int:
|
|
|
3076
3264
|
change_dir, args.phase, policy, execution_root=execution_root,
|
|
3077
3265
|
phase_status=args.status,
|
|
3078
3266
|
)
|
|
3079
|
-
if not ledger_result.get("ok") and args.phase
|
|
3267
|
+
if not ledger_result.get("ok") and phase_gate_rule(args.phase, "ledger_blocking"):
|
|
3080
3268
|
persist_close_failure(
|
|
3081
3269
|
change_dir,
|
|
3082
3270
|
args.phase,
|
|
@@ -3102,7 +3290,7 @@ def cmd_close(args: argparse.Namespace) -> int:
|
|
|
3102
3290
|
extra={k: v for k, v in ledger_result.items() if k not in {"ok", "message", "code"}},
|
|
3103
3291
|
)
|
|
3104
3292
|
|
|
3105
|
-
if args.phase
|
|
3293
|
+
if phase_gate_rule(args.phase, "review_outputs"):
|
|
3106
3294
|
review_outputs = validate_review_outputs_for_close(change_dir, run_id)
|
|
3107
3295
|
if not review_outputs.get("ok"):
|
|
3108
3296
|
persist_close_failure(
|
|
@@ -3134,7 +3322,7 @@ def cmd_close(args: argparse.Namespace) -> int:
|
|
|
3134
3322
|
# C9: scenario coverage check — every P0/ledger scenario *due by this phase*
|
|
3135
3323
|
# must have a ledger entry. Scenarios with a later ownerPhase are deferred,
|
|
3136
3324
|
# not missing (see _validate_scenario_coverage).
|
|
3137
|
-
if args.phase
|
|
3325
|
+
if phase_gate_rule(args.phase, "scenario_coverage"):
|
|
3138
3326
|
coverage = _validate_scenario_coverage(change_dir, args.phase)
|
|
3139
3327
|
if not coverage.get("ok"):
|
|
3140
3328
|
if gate_soft_allowed(severity_mode, args.phase, "scenario-coverage"):
|
|
@@ -3192,7 +3380,7 @@ def cmd_close(args: argparse.Namespace) -> int:
|
|
|
3192
3380
|
write_phase_capsule(change_dir, args.phase, run_id, capsule)
|
|
3193
3381
|
|
|
3194
3382
|
guard_result = None
|
|
3195
|
-
if args.phase
|
|
3383
|
+
if phase_gate_rule(args.phase, "test_guard"):
|
|
3196
3384
|
if close_transaction.get("guardClosed"):
|
|
3197
3385
|
guard_result = {"ok": True, "code": "ALREADY_CLOSED", "reused": True}
|
|
3198
3386
|
else:
|
|
@@ -3247,6 +3435,7 @@ def cmd_close(args: argparse.Namespace) -> int:
|
|
|
3247
3435
|
type_="phase.end",
|
|
3248
3436
|
status=close_status,
|
|
3249
3437
|
note=args.note or "",
|
|
3438
|
+
fixback=bool(getattr(args, "fixback", False)),
|
|
3250
3439
|
run_id=run_id,
|
|
3251
3440
|
)
|
|
3252
3441
|
except BaseException as exc:
|
|
@@ -3364,6 +3553,10 @@ def cmd_close(args: argparse.Namespace) -> int:
|
|
|
3364
3553
|
"gateRecovery": recovery_result,
|
|
3365
3554
|
"scratchSwept": scratch_swept,
|
|
3366
3555
|
}
|
|
3556
|
+
if lease_lapsed is not None:
|
|
3557
|
+
# 与 context transition receipt 的 leaseLapsed 同名同义:记录这个阶段
|
|
3558
|
+
# 跑过了 TTL 且租约是自动重取的,不隐藏。
|
|
3559
|
+
payload["leaseLapsed"] = lease_lapsed
|
|
3367
3560
|
emit(payload, as_json=as_json)
|
|
3368
3561
|
return 0
|
|
3369
3562
|
|
|
@@ -3551,6 +3744,11 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
3551
3744
|
p_begin.add_argument("--ttl-seconds", type=int, default=3600)
|
|
3552
3745
|
p_begin.add_argument("--task", type=int, default=None)
|
|
3553
3746
|
p_begin.add_argument("--note", default="")
|
|
3747
|
+
p_begin.add_argument(
|
|
3748
|
+
"--fixback",
|
|
3749
|
+
action="store_true",
|
|
3750
|
+
help="This phase is a review fixback round (tags the lifecycle event).",
|
|
3751
|
+
)
|
|
3554
3752
|
p_begin.add_argument("--executor-tool", default=None)
|
|
3555
3753
|
p_begin.add_argument("--executor-agent", default=None)
|
|
3556
3754
|
p_begin.add_argument("--executor-model", default=None)
|
|
@@ -3571,6 +3769,11 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
3571
3769
|
p_close.add_argument("--run-id", default=None)
|
|
3572
3770
|
p_close.add_argument("--task", type=int, default=None)
|
|
3573
3771
|
p_close.add_argument("--note", default="")
|
|
3772
|
+
p_close.add_argument(
|
|
3773
|
+
"--fixback",
|
|
3774
|
+
action="store_true",
|
|
3775
|
+
help="This phase is a review fixback round (tags the lifecycle event).",
|
|
3776
|
+
)
|
|
3574
3777
|
p_close.add_argument("--to-phase", default=None)
|
|
3575
3778
|
p_close.add_argument("--executor", default=None)
|
|
3576
3779
|
p_close.add_argument("--artifact", action="append", default=[])
|