@hunter-harness/workflow-harness 0.2.5 → 0.2.7
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 +169 -0
- package/harness/bundles/general/claude-code/harness-archive/SKILL.md +7 -2
- package/harness/bundles/general/claude-code/harness-archive/reference.md +1 -1
- package/harness/bundles/general/claude-code/harness-archive/templates/render-summary.mjs +78 -155
- package/harness/bundles/general/claude-code/harness-codebase-map/SKILL.md +4 -2
- package/harness/bundles/general/claude-code/harness-knowledge-ingest/SKILL.md +5 -3
- package/harness/bundles/general/claude-code/harness-knowledge-ingest/scripts/harness_knowledge.py +109 -7
- package/harness/bundles/general/claude-code/harness-knowledge-ingest/scripts/harness_knowledge_mcp.py +2 -0
- package/harness/bundles/general/claude-code/harness-knowledge-query/SKILL.md +6 -2
- package/harness/bundles/general/claude-code/harness-plan/SKILL.md +8 -3
- package/harness/bundles/general/claude-code/harness-review/SKILL.md +8 -3
- package/harness/bundles/general/claude-code/harness-run/SKILL.md +13 -7
- package/harness/bundles/general/claude-code/harness-run/checklist.md +2 -2
- package/harness/bundles/general/claude-code/harness-run/reference.md +6 -3
- package/harness/bundles/general/claude-code/harness-submit/SKILL.md +11 -6
- package/harness/bundles/general/claude-code/harness-sync/SKILL.md +5 -3
- package/harness/bundles/general/claude-code/harness-test/SKILL.md +12 -3
- package/harness/bundles/general/claude-code/harness-test/reference.md +1 -1
- package/harness/bundles/general/claude-code/protocols/ledger-protocol.md +1 -1
- package/harness/bundles/general/claude-code/protocols/report-pipeline-protocol.md +11 -4
- package/harness/bundles/general/claude-code/scripts/harness_apidoc.py +53 -0
- package/harness/bundles/general/claude-code/scripts/harness_archive.py +231 -48
- package/harness/bundles/general/claude-code/scripts/harness_change.py +854 -0
- package/harness/bundles/general/claude-code/scripts/harness_events.py +134 -40
- package/harness/bundles/general/claude-code/scripts/harness_gate.py +1038 -0
- package/harness/bundles/general/claude-code/scripts/harness_preflight.py +46 -3
- package/harness/bundles/general/claude-code/scripts/harness_profile.py +2 -2
- package/harness/bundles/general/claude-code/scripts/harness_service.py +34 -1
- package/harness/bundles/general/claude-code/scripts/harness_state.py +148 -0
- package/harness/bundles/general/claude-code/scripts/harness_test_guard.py +210 -14
- package/harness/bundles/general/claude-code/scripts/harness_workflow_policy.py +160 -0
- package/harness/bundles/general/codebuddy/.harness-build.json +1 -1
- package/harness/bundles/general/codebuddy/contracts/workflow-policy.json +169 -0
- package/harness/bundles/general/codebuddy/harness-archive/SKILL.md +7 -2
- package/harness/bundles/general/codebuddy/harness-archive/reference.md +1 -1
- package/harness/bundles/general/codebuddy/harness-archive/templates/render-summary.mjs +78 -155
- package/harness/bundles/general/codebuddy/harness-codebase-map/SKILL.md +4 -2
- package/harness/bundles/general/codebuddy/harness-knowledge-ingest/SKILL.md +5 -3
- package/harness/bundles/general/codebuddy/harness-knowledge-ingest/scripts/harness_knowledge.py +109 -7
- package/harness/bundles/general/codebuddy/harness-knowledge-ingest/scripts/harness_knowledge_mcp.py +2 -0
- package/harness/bundles/general/codebuddy/harness-knowledge-query/SKILL.md +6 -2
- package/harness/bundles/general/codebuddy/harness-plan/SKILL.md +8 -3
- package/harness/bundles/general/codebuddy/harness-review/SKILL.md +8 -3
- package/harness/bundles/general/codebuddy/harness-run/SKILL.md +13 -7
- package/harness/bundles/general/codebuddy/harness-run/checklist.md +2 -2
- package/harness/bundles/general/codebuddy/harness-run/reference.md +6 -3
- package/harness/bundles/general/codebuddy/harness-submit/SKILL.md +11 -6
- package/harness/bundles/general/codebuddy/harness-sync/SKILL.md +5 -3
- package/harness/bundles/general/codebuddy/harness-test/SKILL.md +12 -3
- package/harness/bundles/general/codebuddy/harness-test/reference.md +1 -1
- package/harness/bundles/general/codebuddy/protocols/ledger-protocol.md +1 -1
- package/harness/bundles/general/codebuddy/protocols/report-pipeline-protocol.md +11 -4
- package/harness/bundles/general/codebuddy/scripts/harness_apidoc.py +53 -0
- package/harness/bundles/general/codebuddy/scripts/harness_archive.py +231 -48
- package/harness/bundles/general/codebuddy/scripts/harness_change.py +854 -0
- package/harness/bundles/general/codebuddy/scripts/harness_events.py +134 -40
- package/harness/bundles/general/codebuddy/scripts/harness_gate.py +1038 -0
- package/harness/bundles/general/codebuddy/scripts/harness_preflight.py +46 -3
- package/harness/bundles/general/codebuddy/scripts/harness_profile.py +2 -2
- package/harness/bundles/general/codebuddy/scripts/harness_service.py +34 -1
- package/harness/bundles/general/codebuddy/scripts/harness_state.py +148 -0
- package/harness/bundles/general/codebuddy/scripts/harness_test_guard.py +210 -14
- package/harness/bundles/general/codebuddy/scripts/harness_workflow_policy.py +160 -0
- package/harness/bundles/general/codex/.harness-build.json +1 -1
- package/harness/bundles/general/codex/contracts/workflow-policy.json +169 -0
- package/harness/bundles/general/codex/harness-archive/SKILL.md +7 -2
- package/harness/bundles/general/codex/harness-archive/reference.md +1 -1
- package/harness/bundles/general/codex/harness-archive/templates/render-summary.mjs +78 -155
- package/harness/bundles/general/codex/harness-codebase-map/SKILL.md +4 -2
- package/harness/bundles/general/codex/harness-knowledge-ingest/SKILL.md +5 -3
- package/harness/bundles/general/codex/harness-knowledge-ingest/scripts/harness_knowledge.py +109 -7
- package/harness/bundles/general/codex/harness-knowledge-ingest/scripts/harness_knowledge_mcp.py +2 -0
- package/harness/bundles/general/codex/harness-knowledge-query/SKILL.md +6 -2
- package/harness/bundles/general/codex/harness-plan/SKILL.md +8 -3
- package/harness/bundles/general/codex/harness-review/SKILL.md +7 -2
- package/harness/bundles/general/codex/harness-run/SKILL.md +13 -7
- package/harness/bundles/general/codex/harness-run/checklist.md +2 -2
- package/harness/bundles/general/codex/harness-run/reference.md +6 -3
- package/harness/bundles/general/codex/harness-submit/SKILL.md +11 -6
- package/harness/bundles/general/codex/harness-sync/SKILL.md +5 -3
- package/harness/bundles/general/codex/harness-test/SKILL.md +12 -3
- package/harness/bundles/general/codex/harness-test/reference.md +1 -1
- package/harness/bundles/general/codex/protocols/ledger-protocol.md +1 -1
- package/harness/bundles/general/codex/protocols/report-pipeline-protocol.md +11 -4
- package/harness/bundles/general/codex/scripts/harness_apidoc.py +53 -0
- package/harness/bundles/general/codex/scripts/harness_archive.py +231 -48
- package/harness/bundles/general/codex/scripts/harness_change.py +854 -0
- package/harness/bundles/general/codex/scripts/harness_events.py +134 -40
- package/harness/bundles/general/codex/scripts/harness_gate.py +1038 -0
- package/harness/bundles/general/codex/scripts/harness_preflight.py +46 -3
- package/harness/bundles/general/codex/scripts/harness_profile.py +2 -2
- package/harness/bundles/general/codex/scripts/harness_service.py +34 -1
- package/harness/bundles/general/codex/scripts/harness_state.py +148 -0
- package/harness/bundles/general/codex/scripts/harness_test_guard.py +210 -14
- package/harness/bundles/general/codex/scripts/harness_workflow_policy.py +160 -0
- package/harness/bundles/general/cursor/.harness-build.json +1 -1
- package/harness/bundles/general/cursor/contracts/workflow-policy.json +169 -0
- package/harness/bundles/general/cursor/harness-archive/SKILL.md +7 -2
- package/harness/bundles/general/cursor/harness-archive/reference.md +1 -1
- package/harness/bundles/general/cursor/harness-archive/templates/render-summary.mjs +78 -155
- package/harness/bundles/general/cursor/harness-codebase-map/SKILL.md +4 -2
- package/harness/bundles/general/cursor/harness-knowledge-ingest/SKILL.md +5 -3
- package/harness/bundles/general/cursor/harness-knowledge-ingest/scripts/harness_knowledge.py +109 -7
- package/harness/bundles/general/cursor/harness-knowledge-ingest/scripts/harness_knowledge_mcp.py +2 -0
- package/harness/bundles/general/cursor/harness-knowledge-query/SKILL.md +6 -2
- package/harness/bundles/general/cursor/harness-plan/SKILL.md +8 -3
- package/harness/bundles/general/cursor/harness-review/SKILL.md +7 -2
- package/harness/bundles/general/cursor/harness-run/SKILL.md +13 -7
- package/harness/bundles/general/cursor/harness-run/checklist.md +2 -2
- package/harness/bundles/general/cursor/harness-run/reference.md +6 -3
- package/harness/bundles/general/cursor/harness-submit/SKILL.md +11 -6
- package/harness/bundles/general/cursor/harness-sync/SKILL.md +5 -3
- package/harness/bundles/general/cursor/harness-test/SKILL.md +12 -3
- package/harness/bundles/general/cursor/harness-test/reference.md +1 -1
- package/harness/bundles/general/cursor/protocols/ledger-protocol.md +1 -1
- package/harness/bundles/general/cursor/protocols/report-pipeline-protocol.md +11 -4
- package/harness/bundles/general/cursor/scripts/harness_apidoc.py +53 -0
- package/harness/bundles/general/cursor/scripts/harness_archive.py +231 -48
- package/harness/bundles/general/cursor/scripts/harness_change.py +854 -0
- package/harness/bundles/general/cursor/scripts/harness_events.py +134 -40
- package/harness/bundles/general/cursor/scripts/harness_gate.py +1038 -0
- package/harness/bundles/general/cursor/scripts/harness_preflight.py +46 -3
- package/harness/bundles/general/cursor/scripts/harness_profile.py +2 -2
- package/harness/bundles/general/cursor/scripts/harness_service.py +34 -1
- package/harness/bundles/general/cursor/scripts/harness_state.py +148 -0
- package/harness/bundles/general/cursor/scripts/harness_test_guard.py +210 -14
- package/harness/bundles/general/cursor/scripts/harness_workflow_policy.py +160 -0
- package/harness/bundles/java/claude-code/.harness-build.json +1 -1
- package/harness/bundles/java/claude-code/contracts/workflow-policy.json +169 -0
- package/harness/bundles/java/claude-code/harness-apidoc/SKILL.md +11 -2
- package/harness/bundles/java/claude-code/harness-archive/SKILL.md +7 -2
- package/harness/bundles/java/claude-code/harness-archive/reference.md +1 -1
- package/harness/bundles/java/claude-code/harness-archive/templates/render-summary.mjs +78 -155
- package/harness/bundles/java/claude-code/harness-codebase-map/SKILL.md +4 -2
- package/harness/bundles/java/claude-code/harness-knowledge-ingest/SKILL.md +5 -3
- package/harness/bundles/java/claude-code/harness-knowledge-ingest/scripts/harness_knowledge.py +109 -7
- package/harness/bundles/java/claude-code/harness-knowledge-ingest/scripts/harness_knowledge_mcp.py +2 -0
- package/harness/bundles/java/claude-code/harness-knowledge-query/SKILL.md +6 -2
- package/harness/bundles/java/claude-code/harness-package/SKILL.md +10 -4
- package/harness/bundles/java/claude-code/harness-plan/SKILL.md +8 -3
- package/harness/bundles/java/claude-code/harness-review/SKILL.md +8 -3
- package/harness/bundles/java/claude-code/harness-run/SKILL.md +13 -7
- package/harness/bundles/java/claude-code/harness-run/reference.md +1 -1
- package/harness/bundles/java/claude-code/harness-submit/SKILL.md +11 -6
- package/harness/bundles/java/claude-code/harness-sync/SKILL.md +5 -3
- package/harness/bundles/java/claude-code/harness-test/SKILL.md +12 -3
- package/harness/bundles/java/claude-code/harness-test/reference.md +1 -1
- package/harness/bundles/java/claude-code/protocols/ledger-protocol.md +1 -1
- package/harness/bundles/java/claude-code/protocols/report-pipeline-protocol.md +11 -4
- package/harness/bundles/java/claude-code/scripts/harness_apidoc.py +53 -0
- package/harness/bundles/java/claude-code/scripts/harness_archive.py +231 -48
- package/harness/bundles/java/claude-code/scripts/harness_change.py +854 -0
- package/harness/bundles/java/claude-code/scripts/harness_events.py +134 -40
- package/harness/bundles/java/claude-code/scripts/harness_gate.py +1038 -0
- package/harness/bundles/java/claude-code/scripts/harness_preflight.py +46 -3
- package/harness/bundles/java/claude-code/scripts/harness_profile.py +2 -2
- package/harness/bundles/java/claude-code/scripts/harness_service.py +34 -1
- package/harness/bundles/java/claude-code/scripts/harness_state.py +148 -0
- package/harness/bundles/java/claude-code/scripts/harness_test_guard.py +210 -14
- package/harness/bundles/java/claude-code/scripts/harness_workflow_policy.py +160 -0
- package/harness/bundles/java/codebuddy/.harness-build.json +1 -1
- package/harness/bundles/java/codebuddy/contracts/workflow-policy.json +169 -0
- package/harness/bundles/java/codebuddy/harness-apidoc/SKILL.md +11 -2
- package/harness/bundles/java/codebuddy/harness-archive/SKILL.md +7 -2
- package/harness/bundles/java/codebuddy/harness-archive/reference.md +1 -1
- package/harness/bundles/java/codebuddy/harness-archive/templates/render-summary.mjs +78 -155
- package/harness/bundles/java/codebuddy/harness-codebase-map/SKILL.md +4 -2
- package/harness/bundles/java/codebuddy/harness-knowledge-ingest/SKILL.md +5 -3
- package/harness/bundles/java/codebuddy/harness-knowledge-ingest/scripts/harness_knowledge.py +109 -7
- package/harness/bundles/java/codebuddy/harness-knowledge-ingest/scripts/harness_knowledge_mcp.py +2 -0
- package/harness/bundles/java/codebuddy/harness-knowledge-query/SKILL.md +6 -2
- package/harness/bundles/java/codebuddy/harness-package/SKILL.md +10 -4
- package/harness/bundles/java/codebuddy/harness-plan/SKILL.md +8 -3
- package/harness/bundles/java/codebuddy/harness-review/SKILL.md +8 -3
- package/harness/bundles/java/codebuddy/harness-run/SKILL.md +13 -7
- package/harness/bundles/java/codebuddy/harness-run/reference.md +1 -1
- package/harness/bundles/java/codebuddy/harness-submit/SKILL.md +11 -6
- package/harness/bundles/java/codebuddy/harness-sync/SKILL.md +5 -3
- package/harness/bundles/java/codebuddy/harness-test/SKILL.md +12 -3
- package/harness/bundles/java/codebuddy/harness-test/reference.md +1 -1
- package/harness/bundles/java/codebuddy/protocols/ledger-protocol.md +1 -1
- package/harness/bundles/java/codebuddy/protocols/report-pipeline-protocol.md +11 -4
- package/harness/bundles/java/codebuddy/scripts/harness_apidoc.py +53 -0
- package/harness/bundles/java/codebuddy/scripts/harness_archive.py +231 -48
- package/harness/bundles/java/codebuddy/scripts/harness_change.py +854 -0
- package/harness/bundles/java/codebuddy/scripts/harness_events.py +134 -40
- package/harness/bundles/java/codebuddy/scripts/harness_gate.py +1038 -0
- package/harness/bundles/java/codebuddy/scripts/harness_preflight.py +46 -3
- package/harness/bundles/java/codebuddy/scripts/harness_profile.py +2 -2
- package/harness/bundles/java/codebuddy/scripts/harness_service.py +34 -1
- package/harness/bundles/java/codebuddy/scripts/harness_state.py +148 -0
- package/harness/bundles/java/codebuddy/scripts/harness_test_guard.py +210 -14
- package/harness/bundles/java/codebuddy/scripts/harness_workflow_policy.py +160 -0
- package/harness/bundles/java/codex/.harness-build.json +1 -1
- package/harness/bundles/java/codex/contracts/workflow-policy.json +169 -0
- package/harness/bundles/java/codex/harness-apidoc/SKILL.md +11 -2
- package/harness/bundles/java/codex/harness-archive/SKILL.md +7 -2
- package/harness/bundles/java/codex/harness-archive/reference.md +1 -1
- package/harness/bundles/java/codex/harness-archive/templates/render-summary.mjs +78 -155
- package/harness/bundles/java/codex/harness-codebase-map/SKILL.md +4 -2
- package/harness/bundles/java/codex/harness-knowledge-ingest/SKILL.md +5 -3
- package/harness/bundles/java/codex/harness-knowledge-ingest/scripts/harness_knowledge.py +109 -7
- package/harness/bundles/java/codex/harness-knowledge-ingest/scripts/harness_knowledge_mcp.py +2 -0
- package/harness/bundles/java/codex/harness-knowledge-query/SKILL.md +6 -2
- package/harness/bundles/java/codex/harness-package/SKILL.md +10 -4
- package/harness/bundles/java/codex/harness-plan/SKILL.md +8 -3
- package/harness/bundles/java/codex/harness-review/SKILL.md +7 -2
- package/harness/bundles/java/codex/harness-run/SKILL.md +13 -7
- package/harness/bundles/java/codex/harness-run/reference.md +1 -1
- package/harness/bundles/java/codex/harness-submit/SKILL.md +11 -6
- package/harness/bundles/java/codex/harness-sync/SKILL.md +5 -3
- package/harness/bundles/java/codex/harness-test/SKILL.md +12 -3
- package/harness/bundles/java/codex/harness-test/reference.md +1 -1
- package/harness/bundles/java/codex/protocols/ledger-protocol.md +1 -1
- package/harness/bundles/java/codex/protocols/report-pipeline-protocol.md +11 -4
- package/harness/bundles/java/codex/scripts/harness_apidoc.py +53 -0
- package/harness/bundles/java/codex/scripts/harness_archive.py +231 -48
- package/harness/bundles/java/codex/scripts/harness_change.py +854 -0
- package/harness/bundles/java/codex/scripts/harness_events.py +134 -40
- package/harness/bundles/java/codex/scripts/harness_gate.py +1038 -0
- package/harness/bundles/java/codex/scripts/harness_preflight.py +46 -3
- package/harness/bundles/java/codex/scripts/harness_profile.py +2 -2
- package/harness/bundles/java/codex/scripts/harness_service.py +34 -1
- package/harness/bundles/java/codex/scripts/harness_state.py +148 -0
- package/harness/bundles/java/codex/scripts/harness_test_guard.py +210 -14
- package/harness/bundles/java/codex/scripts/harness_workflow_policy.py +160 -0
- package/harness/bundles/java/cursor/.harness-build.json +1 -1
- package/harness/bundles/java/cursor/contracts/workflow-policy.json +169 -0
- package/harness/bundles/java/cursor/harness-apidoc/SKILL.md +11 -2
- package/harness/bundles/java/cursor/harness-archive/SKILL.md +7 -2
- package/harness/bundles/java/cursor/harness-archive/reference.md +1 -1
- package/harness/bundles/java/cursor/harness-archive/templates/render-summary.mjs +78 -155
- package/harness/bundles/java/cursor/harness-codebase-map/SKILL.md +4 -2
- package/harness/bundles/java/cursor/harness-knowledge-ingest/SKILL.md +5 -3
- package/harness/bundles/java/cursor/harness-knowledge-ingest/scripts/harness_knowledge.py +109 -7
- package/harness/bundles/java/cursor/harness-knowledge-ingest/scripts/harness_knowledge_mcp.py +2 -0
- package/harness/bundles/java/cursor/harness-knowledge-query/SKILL.md +6 -2
- package/harness/bundles/java/cursor/harness-package/SKILL.md +10 -4
- package/harness/bundles/java/cursor/harness-plan/SKILL.md +8 -3
- package/harness/bundles/java/cursor/harness-review/SKILL.md +7 -2
- package/harness/bundles/java/cursor/harness-run/SKILL.md +13 -7
- package/harness/bundles/java/cursor/harness-run/reference.md +1 -1
- package/harness/bundles/java/cursor/harness-submit/SKILL.md +11 -6
- package/harness/bundles/java/cursor/harness-sync/SKILL.md +5 -3
- package/harness/bundles/java/cursor/harness-test/SKILL.md +12 -3
- package/harness/bundles/java/cursor/harness-test/reference.md +1 -1
- package/harness/bundles/java/cursor/protocols/ledger-protocol.md +1 -1
- package/harness/bundles/java/cursor/protocols/report-pipeline-protocol.md +11 -4
- package/harness/bundles/java/cursor/scripts/harness_apidoc.py +53 -0
- package/harness/bundles/java/cursor/scripts/harness_archive.py +231 -48
- package/harness/bundles/java/cursor/scripts/harness_change.py +854 -0
- package/harness/bundles/java/cursor/scripts/harness_events.py +134 -40
- package/harness/bundles/java/cursor/scripts/harness_gate.py +1038 -0
- package/harness/bundles/java/cursor/scripts/harness_preflight.py +46 -3
- package/harness/bundles/java/cursor/scripts/harness_profile.py +2 -2
- package/harness/bundles/java/cursor/scripts/harness_service.py +34 -1
- package/harness/bundles/java/cursor/scripts/harness_state.py +148 -0
- package/harness/bundles/java/cursor/scripts/harness_test_guard.py +210 -14
- package/harness/bundles/java/cursor/scripts/harness_workflow_policy.py +160 -0
- package/harness/manifests/general/claude-code.json +48 -28
- package/harness/manifests/general/codebuddy.json +48 -28
- package/harness/manifests/general/codex.json +48 -28
- package/harness/manifests/general/cursor.json +48 -28
- package/harness/manifests/java/claude-code.json +49 -29
- package/harness/manifests/java/codebuddy.json +49 -29
- package/harness/manifests/java/codex.json +49 -29
- package/harness/manifests/java/cursor.json +49 -29
- package/hunter-workflow-family.json +2 -2
- package/package.json +1 -1
|
@@ -22,7 +22,7 @@ disallowed-tools:
|
|
|
22
22
|
- Bash(node *)
|
|
23
23
|
- Bash(codegraph *)
|
|
24
24
|
---
|
|
25
|
-
<!-- generated by harness_deploy.py; core=
|
|
25
|
+
<!-- generated by harness_deploy.py; core=89e664933ed69572; overlay=none; agent=claude-code; do not edit -->
|
|
26
26
|
# harness-test — 测试执行
|
|
27
27
|
|
|
28
28
|
## Purpose
|
|
@@ -56,14 +56,21 @@ disallowed-tools:
|
|
|
56
56
|
6. **implementation-detail** — 自适应执行参考;legacy 缺失 🟡WARN,不阻断
|
|
57
57
|
7. **test-scenarios** — 测试真相源:`plans/<change>-test-scenarios.md`
|
|
58
58
|
8. **禁止读取 `docs/superpowers/` 作为正式输入** — 旧草稿仅人工线索
|
|
59
|
+
9. **每个 skill 边界只刷新一次状态快照** — 先运行 `python <skills-root>/scripts/harness_state.py capture --project . --change-dir ".harness/changes/<change-name>" --base <baseCommit> --json`;`changedSegments=[]` 时复用已有 profile/rules/map/knowledge/change/code 指纹,不再重复跑全量 `harness-sync`、知识重建或代码库扫描。只有对应 segment 变化或缺失时才定向刷新;快照不能替代源码读取和验证门禁。
|
|
59
60
|
|
|
60
61
|
状态目录分层:新路径优先,旧路径兼容 → [[../protocols/state-layout-protocol.md|state-layout-protocol]]
|
|
61
62
|
|
|
62
63
|
## Workflow
|
|
63
64
|
|
|
65
|
+
测试跟踪的确定性边界为 `harness_test_guard.py begin` 和 `harness_test_guard.py close`;任一失败都必须传递给 `harness_gate.py close`,不得以自然语言覆盖失败状态。
|
|
66
|
+
|
|
67
|
+
并行服务测试先运行 `harness_change.py lease-port --change <id> --run-id <run-id> --range <start-end> --json`,再把返回端口传给 `harness_service.py ensure --leased-port <port> --lease-owner <run-id>`。`serviceStart` 的 command/health/overlay 可用 `{leasedPort}` 占位符;用户自启进程仍只进入 Service Gate,禁止 kill。测试清理的 `finally` 中运行 `harness_change.py release-port --change <id> --run-id <run-id> --json`,避免租约池耗尽。
|
|
68
|
+
|
|
64
69
|
### Phase 0:环境准备(主会话执行,需要交互确认)
|
|
65
70
|
|
|
66
|
-
|
|
71
|
+
先 `harness_change.py resolve [--change] --json` 解析 change-id;再 **`harness_gate.py begin --phase test --change <id>`**(禁止手工 phase.start / 手写 ledger)。执行各项强制环境检查 + **命令执行模式 preflight (0.1)**;只有首选执行器不可用时,才执行 fallback 执行器探测。
|
|
72
|
+
|
|
73
|
+
验证写入**仅**允许 `harness_ledger.py record` / `can-reuse`;禁止 Write/Edit `verification-ledger.json`。测试跟踪:`harness_test_guard.py begin` → 执行 → `close`(可选 `mark` stale-test-repair)。阶段结束必须 `harness_gate.py close --phase test --status ...`。
|
|
67
74
|
|
|
68
75
|
- **Read `checklist.md`** — 各项检查详情 + 0.1 preflight + Playwright 探测 + 避坑规则指引
|
|
69
76
|
- **失败处理**:任一项检查失败 → 终止流程并报告原因,用户确认修复后才能继续
|
|
@@ -181,7 +188,7 @@ python <skills-root>/scripts/harness_test_guard.py record --project . --change-d
|
|
|
181
188
|
|
|
182
189
|
## 执行日志
|
|
183
190
|
|
|
184
|
-
`events.ndjson` 为唯一事实源(schema_version
|
|
191
|
+
`events.ndjson` 为唯一事实源(schema_version 3,兼容读取 v1/v2;`note` 承载人类可读摘要);`logs/execution-log.md` 由 `harness_events.py` 渲染,**禁止用 Write/Edit 直接维护**。直接修改的内容会在 `phase.end` 或 finalize 时被完整重建覆盖,属于数据丢失;需要保留的详情必须进入事件 `note`。结构 → [[../protocols/report-pipeline-protocol.md|report-pipeline-protocol]]
|
|
185
192
|
|
|
186
193
|
```powershell
|
|
187
194
|
python <skills-root>/scripts/harness_events.py append --change-dir ".harness/changes/<change-name>" --phase <phase> --type phase.start --note "<触发指令>"
|
|
@@ -190,3 +197,5 @@ python <skills-root>/scripts/harness_events.py append --change-dir ".harness/cha
|
|
|
190
197
|
> **脚本接线**:`harness_events.py append`;`harness_archive.py finalize`;`harness_preflight.py check`;`harness_ledger.py can-reuse`;`harness_service.py ensure/stop`(须 `--files`/`serviceStart.inputFiles`)。JSON 输出按 D13 护栏解读。
|
|
191
198
|
|
|
192
199
|
> **Task 4 §6.1 写入契约**:普通 `append` = 加锁 -> 追加一行 -> fsync -> 解锁,**不 load 历史、不渲染**(O(1),跨进程锁 `events.ndjson.lock`,UUID 用完整 `uuid4().hex` 无需去重扫描)。仅 `--type phase.end` append 在追加成功后渲染一次 `execution-log.md`;显式 `harness_events.py render` 随时从完整 events 重建;`harness_archive.py finalize` 在 collect 前强制 render 一次。高频 command append 期间 log 可能滞后,phase 边界保持最新。
|
|
200
|
+
|
|
201
|
+
每次阶段结束必须写 `--status OK|WARN|FAIL|BLOCKED`;重试同一阶段时增加 `--attempt <n>`,不得覆盖或伪装成一次执行。跨工具继续执行时写 `--executor-tool <codex|claude-code|codebuddy|cursor>`,并在接棒事件写 `--handoff-from-tool` / `--handoff-reason`;也可由 `HUNTER_HARNESS_TOOL/AGENT/MODEL/RUN_ID` 环境变量统一注入。
|
|
@@ -786,6 +786,6 @@ python <skills-root>/scripts/harness_ledger.py diff-hash --repo . --base <baseCo
|
|
|
786
786
|
|
|
787
787
|
## 执行日志记录
|
|
788
788
|
|
|
789
|
-
`/harness-test` 只向 `events.ndjson` 追加事件(schema_version
|
|
789
|
+
`/harness-test` 只向 `events.ndjson` 追加事件(schema_version 3,兼容读取 v1/v2);`logs/execution-log.md` 由 `harness_events.py append` 自动渲染。Phase 0 之前 append `phase.start`;各阶段写入 `command` / `verification` / `decision` / `issue` / `artifact`,人类可读摘要放 `note`。事件类型与脚本用法见 [[../protocols/report-pipeline-protocol.md|report-pipeline-protocol]] 与 SKILL.md `## 执行日志`。
|
|
790
790
|
|
|
791
791
|
关键 `note` / 事件须覆盖:0.1 命令执行模式、fallback 执行器、serviceState、凭证策略、单元测试复用、批量执行器、verification-ledger 写入、关门检查、API 状态(不得把 PARTIAL 写成 NOT_RUN)。
|
|
@@ -86,7 +86,7 @@ verification-ledger 把每次验证(compile / unit test / api test / package
|
|
|
86
86
|
},
|
|
87
87
|
"package": {
|
|
88
88
|
"status": "OK",
|
|
89
|
-
"command": "<打包命令(按技术栈:Java=mvn package -pl <module> -am -DskipTests;前端=npm run build;Python=python -m build)>",
|
|
89
|
+
"command": "<打包命令(按技术栈:Java=mvn clean package -pl <module> -am -DskipTests;前端=npm run build;Python=python -m build)>",
|
|
90
90
|
"baseCommit": "<final pushed hash>",
|
|
91
91
|
"deployArtifact": "<构建产物路径(Java=<module>/target/<artifact>.jar;前端=<module>/dist/;Python=<module>/dist/)>",
|
|
92
92
|
"sha256": "<artifact sha256>",
|
|
@@ -43,11 +43,11 @@ python <skills-root>/scripts/harness_events.py append --change-dir ".harness/cha
|
|
|
43
43
|
|
|
44
44
|
`append` 写入契约(Task 4 §6.1):普通 append = 加锁 -> 追加一行 -> fsync -> 解锁,**不 load 历史、不渲染**(O(1),跨进程锁 `events.ndjson.lock`,UUID 用完整 `uuid4().hex`);仅 `--type phase.end` append 成功后渲染一次 `logs/execution-log.md`;显式 `harness_events.py render` 随时从完整 events 重建;`harness_archive.py finalize` 在 collect 前强制 render 一次。人类可读摘要(触发指令、降级原因、阶段结论)写入事件的 `note` 字段;阶段开始/结束、命令、验证、artifact、问题、决策各写对应 `type` 事件。旧 archive 缺少 events 可回放兼容;新变更不得以 execution-log 已存在为理由跳过 events。
|
|
45
45
|
|
|
46
|
-
基础事件结构(schema_version
|
|
46
|
+
基础事件结构(schema_version 3;兼容读取 v1/v2):
|
|
47
47
|
|
|
48
48
|
```json
|
|
49
49
|
{
|
|
50
|
-
"schema_version":
|
|
50
|
+
"schema_version": 3,
|
|
51
51
|
"id": "evt-...",
|
|
52
52
|
"timestamp": "2026-07-02T00:00:00.000Z",
|
|
53
53
|
"phase": "run",
|
|
@@ -55,6 +55,13 @@ python <skills-root>/scripts/harness_events.py append --change-dir ".harness/cha
|
|
|
55
55
|
"command": "npm test",
|
|
56
56
|
"exit_code": 0,
|
|
57
57
|
"duration_ms": 42100,
|
|
58
|
+
"run_id": "run-...",
|
|
59
|
+
"attempt": 1,
|
|
60
|
+
"executor_tool": "codex",
|
|
61
|
+
"executor_agent": "main",
|
|
62
|
+
"executor_model": "gpt-5",
|
|
63
|
+
"handoff_from_tool": "claude-code",
|
|
64
|
+
"handoff_reason": "continue implementation",
|
|
58
65
|
"note": "可选:触发指令、降级原因、阶段摘要"
|
|
59
66
|
}
|
|
60
67
|
```
|
|
@@ -64,7 +71,7 @@ python <skills-root>/scripts/harness_events.py append --change-dir ".harness/cha
|
|
|
64
71
|
| type | 必填/常用字段 | 用途 |
|
|
65
72
|
|---|---|---|
|
|
66
73
|
| `phase.start` | phase, timestamp | 阶段开始 |
|
|
67
|
-
| `phase.end` | phase, timestamp |
|
|
74
|
+
| `phase.end` | phase, timestamp, status | 阶段结束并记录 `OK/WARN/FAIL/BLOCKED`;计算当前 attempt 耗时 |
|
|
68
75
|
| `command` | command, exit_code, duration_ms | 命令事实 |
|
|
69
76
|
| `verification` | name, status, command | 验证事实 |
|
|
70
77
|
| `artifact` | path, kind | 报告、包、manifest 等产物 |
|
|
@@ -79,7 +86,7 @@ summary-data 必须保留原 archive final report 维度,并增加事件层摘
|
|
|
79
86
|
{
|
|
80
87
|
"schemaVersion": "2.2",
|
|
81
88
|
"reportPipeline": {
|
|
82
|
-
"schema_version":
|
|
89
|
+
"schema_version": 1,
|
|
83
90
|
"generated_at": "2026-07-02T00:00:00.000Z",
|
|
84
91
|
"event_count": 0,
|
|
85
92
|
"sources": ["events.ndjson", "evidence/verification-ledger.json"],
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Small deterministic helpers for compact API contract reports."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import datetime as dt
|
|
8
|
+
import json
|
|
9
|
+
import re
|
|
10
|
+
import sys
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
COMMIT_ONLY = re.compile(r"^[0-9a-f]{7,64}$", re.IGNORECASE)
|
|
14
|
+
INVALID_FILENAME = re.compile(r"[<>:\"/\\|?*\x00-\x1f]+")
|
|
15
|
+
SEPARATORS = re.compile(r"[\s_-]+")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def build_filename(description: str, *, date: str | None = None) -> str:
|
|
19
|
+
raw = str(description or "").strip()
|
|
20
|
+
if not raw or COMMIT_ONLY.fullmatch(raw):
|
|
21
|
+
raise ValueError("a human-readable API change description is required")
|
|
22
|
+
slug = INVALID_FILENAME.sub("-", raw)
|
|
23
|
+
slug = SEPARATORS.sub("-", slug).strip(" .-")
|
|
24
|
+
if not slug:
|
|
25
|
+
raise ValueError("description does not contain filename-safe text")
|
|
26
|
+
day = date or dt.date.today().isoformat()
|
|
27
|
+
slug = slug[:86].rstrip(" .-")
|
|
28
|
+
return f"{day}-{slug}.md"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def main(argv: list[str] | None = None) -> int:
|
|
32
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
33
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
34
|
+
filename = sub.add_parser("filename", help="build a safe dated report filename")
|
|
35
|
+
filename.add_argument("--description", required=True)
|
|
36
|
+
filename.add_argument("--date", default=None)
|
|
37
|
+
filename.add_argument("--json", action="store_true")
|
|
38
|
+
args = parser.parse_args(argv)
|
|
39
|
+
try:
|
|
40
|
+
value = build_filename(args.description, date=args.date)
|
|
41
|
+
except ValueError as exc:
|
|
42
|
+
sys.stderr.write(json.dumps({"ok": False, "error": str(exc)}) + "\n")
|
|
43
|
+
return 1
|
|
44
|
+
payload = {"ok": True, "filename": value}
|
|
45
|
+
if args.json:
|
|
46
|
+
sys.stdout.write(json.dumps(payload, ensure_ascii=False) + "\n")
|
|
47
|
+
else:
|
|
48
|
+
sys.stdout.write(value + "\n")
|
|
49
|
+
return 0
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
if __name__ == "__main__":
|
|
53
|
+
raise SystemExit(main())
|
|
@@ -359,6 +359,28 @@ def find_review_reports(change_dir: Path) -> list[Path]:
|
|
|
359
359
|
return found
|
|
360
360
|
|
|
361
361
|
|
|
362
|
+
def review_phase_completed(events: list[dict[str, Any]]) -> bool:
|
|
363
|
+
"""True only when structured events record a review phase.end (UT-042)."""
|
|
364
|
+
for event in events:
|
|
365
|
+
if event.get("type") != "phase.end":
|
|
366
|
+
continue
|
|
367
|
+
if str(event.get("phase") or "").lower() == "review":
|
|
368
|
+
return True
|
|
369
|
+
return False
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def review_evidence_present(
|
|
373
|
+
change_dir: Path,
|
|
374
|
+
events: list[dict[str, Any]] | None = None,
|
|
375
|
+
) -> bool:
|
|
376
|
+
"""Review ran only when report files or review phase.end events exist."""
|
|
377
|
+
if find_review_reports(change_dir):
|
|
378
|
+
return True
|
|
379
|
+
if events is None:
|
|
380
|
+
events = he.load_events(change_dir / "events.ndjson")
|
|
381
|
+
return review_phase_completed(events)
|
|
382
|
+
|
|
383
|
+
|
|
362
384
|
def git_run(project: Path, *args: str) -> tuple[int, str, str]:
|
|
363
385
|
try:
|
|
364
386
|
proc = subprocess.run(
|
|
@@ -531,10 +553,9 @@ def check_status(change_dir: Path) -> dict[str, Any]:
|
|
|
531
553
|
# --- test / review reports ---
|
|
532
554
|
test_reports = find_test_reports(change_dir)
|
|
533
555
|
review_reports = find_review_reports(change_dir)
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
)
|
|
556
|
+
events_path = change_dir / "events.ndjson"
|
|
557
|
+
events = he.load_events(events_path) if events_path.is_file() else []
|
|
558
|
+
review_ran = review_evidence_present(change_dir, events)
|
|
538
559
|
|
|
539
560
|
checks["test_reports"] = [str(p.relative_to(change_dir)) for p in test_reports]
|
|
540
561
|
checks["review_reports"] = [str(p.relative_to(change_dir)) for p in review_reports]
|
|
@@ -555,13 +576,13 @@ def check_status(change_dir: Path) -> dict[str, Any]:
|
|
|
555
576
|
|
|
556
577
|
if review_reports:
|
|
557
578
|
checks["review_report_status"] = "present"
|
|
558
|
-
elif
|
|
579
|
+
elif review_phase_completed(events):
|
|
559
580
|
checks["review_report_status"] = "ran-but-not-persisted"
|
|
560
581
|
warnings.append(
|
|
561
582
|
{
|
|
562
583
|
"code": "review-not-persisted",
|
|
563
584
|
"message": (
|
|
564
|
-
"
|
|
585
|
+
"review phase.end recorded but no review-report file; "
|
|
565
586
|
"prefer persisting report before archive"
|
|
566
587
|
),
|
|
567
588
|
}
|
|
@@ -622,13 +643,16 @@ def _ledger_unit_tests(ledger: dict[str, Any] | None) -> dict[str, Any]:
|
|
|
622
643
|
status = str(unit.get("status") or "").upper()
|
|
623
644
|
evidence = unit.get("evidence") or {}
|
|
624
645
|
if isinstance(evidence, dict):
|
|
625
|
-
run = evidence.get(
|
|
646
|
+
run = evidence.get(
|
|
647
|
+
"run",
|
|
648
|
+
evidence.get("testsRun", unit.get("run", unit.get("testsRun", 0))),
|
|
649
|
+
)
|
|
626
650
|
failures = evidence.get("failures", unit.get("failures", 0))
|
|
627
651
|
errors = evidence.get("errors", unit.get("errors", 0))
|
|
628
652
|
skipped = evidence.get("skipped", unit.get("skipped", 0))
|
|
629
653
|
pass_rate = evidence.get("passRate") or unit.get("passRate")
|
|
630
654
|
else:
|
|
631
|
-
run = unit.get("run", 0)
|
|
655
|
+
run = unit.get("run", unit.get("testsRun", 0))
|
|
632
656
|
failures = unit.get("failures", 0)
|
|
633
657
|
errors = unit.get("errors", 0)
|
|
634
658
|
skipped = unit.get("skipped", 0)
|
|
@@ -886,16 +910,21 @@ def _durations_from_event_phases(event_summary: dict[str, Any]) -> dict[str, Any
|
|
|
886
910
|
minutes = round((dur or 0) / 60000, 2) if dur is not None else 0
|
|
887
911
|
if dur:
|
|
888
912
|
total_ms += int(dur)
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
}
|
|
898
|
-
|
|
913
|
+
attempts = info.get("attempts") if isinstance(info.get("attempts"), list) else []
|
|
914
|
+
result = str(info.get("status") or "UNKNOWN").upper()
|
|
915
|
+
if result in {"PASS", "PASSED", "SUCCESS"}:
|
|
916
|
+
result = "OK"
|
|
917
|
+
elif result in {"FAILED", "ERROR"}:
|
|
918
|
+
result = "FAIL"
|
|
919
|
+
stages.append({
|
|
920
|
+
"stage": str(name),
|
|
921
|
+
"skill": f"harness-{name}",
|
|
922
|
+
"startedAt": info.get("started_at") or NOT_AVAILABLE,
|
|
923
|
+
"endedAt": info.get("ended_at") or NOT_AVAILABLE,
|
|
924
|
+
"minutes": minutes,
|
|
925
|
+
"result": result,
|
|
926
|
+
"attempts": attempts,
|
|
927
|
+
})
|
|
899
928
|
total_min = round(total_ms / 60000, 2)
|
|
900
929
|
return {
|
|
901
930
|
"totalLabel": f"约 {int(round(total_min))} 分",
|
|
@@ -917,11 +946,31 @@ def _stage_status_from_sources(
|
|
|
917
946
|
"submit": "OK",
|
|
918
947
|
"archive": "OK",
|
|
919
948
|
}
|
|
949
|
+
for event in events:
|
|
950
|
+
if event.get("type") != "phase.end":
|
|
951
|
+
continue
|
|
952
|
+
phase = str(event.get("phase") or "").lower()
|
|
953
|
+
raw = str(event.get("status") or "").upper()
|
|
954
|
+
if phase not in status or not raw:
|
|
955
|
+
continue
|
|
956
|
+
if raw in {"PASS", "PASSED", "SUCCESS"}:
|
|
957
|
+
raw = "OK"
|
|
958
|
+
elif raw in {"FAILED", "ERROR"}:
|
|
959
|
+
raw = "FAIL"
|
|
960
|
+
elif raw in {"SKIP", "SKIPPED"}:
|
|
961
|
+
raw = "USER_SKIPPED"
|
|
962
|
+
status[phase] = raw
|
|
920
963
|
# Issues in events can downgrade
|
|
921
964
|
for e in events:
|
|
922
965
|
if e.get("type") != "issue":
|
|
923
966
|
continue
|
|
924
967
|
sev = str(e.get("severity") or "").lower()
|
|
968
|
+
issue_text = " ".join(str(e.get(key) or "") for key in ("message", "note", "code")).lower()
|
|
969
|
+
if not sev:
|
|
970
|
+
if any(token in issue_text for token in ("fail", "error", "blocked", "失败", "阻塞")):
|
|
971
|
+
sev = "error"
|
|
972
|
+
elif any(token in issue_text for token in ("warn", "skip", "风险", "警告")):
|
|
973
|
+
sev = "warn"
|
|
925
974
|
phase = str(e.get("phase") or "").lower()
|
|
926
975
|
if phase in status and sev in {"error", "fail", "failed", "critical"}:
|
|
927
976
|
status[phase] = "FAIL"
|
|
@@ -930,19 +979,22 @@ def _stage_status_from_sources(
|
|
|
930
979
|
|
|
931
980
|
api = _ledger_api_tests(ledger)
|
|
932
981
|
db = _ledger_db_compat(ledger)
|
|
933
|
-
|
|
982
|
+
api_status = str(api.get("status") or "").upper()
|
|
983
|
+
if api_status in {"FAIL", "FAILED", "ERROR"} or int(api.get("failed") or 0) > 0:
|
|
984
|
+
status["test"] = "FAIL"
|
|
985
|
+
elif api_status in {"BLOCKED", "BLOCKED_BY_ENV", "BLOCKED_BY_DBA"}:
|
|
986
|
+
status["test"] = api_status
|
|
987
|
+
elif api_status == "USER_SKIPPED":
|
|
934
988
|
status["test"] = "USER_SKIPPED"
|
|
935
989
|
elif db == "BLOCKED_BY_DBA":
|
|
936
990
|
status["test"] = "BLOCKED_BY_DBA"
|
|
937
991
|
elif api.get("status") == "NOT_RUN" and not find_test_reports(change_dir):
|
|
938
992
|
unit = _ledger_unit_tests(ledger)
|
|
939
993
|
if unit.get("source") == "not-run" and unit.get("run", 0) == 0:
|
|
940
|
-
status["test"] = "
|
|
994
|
+
status["test"] = "NOT_RUN"
|
|
941
995
|
|
|
942
|
-
if not
|
|
943
|
-
|
|
944
|
-
if not re.search(r"harness-review|##\s*review", log_text, re.IGNORECASE):
|
|
945
|
-
status["review"] = "ADVISORY"
|
|
996
|
+
if not review_evidence_present(change_dir, events):
|
|
997
|
+
status["review"] = "ADVISORY"
|
|
946
998
|
|
|
947
999
|
return status
|
|
948
1000
|
|
|
@@ -954,8 +1006,6 @@ def _compute_final_status(
|
|
|
954
1006
|
api = verification.get("apiTests") or {}
|
|
955
1007
|
db = str(verification.get("dbCompatibility") or "")
|
|
956
1008
|
api_status = str(api.get("status") or "")
|
|
957
|
-
if api_status == "USER_SKIPPED" or db == "BLOCKED_BY_DBA":
|
|
958
|
-
return "CONDITIONAL_OK"
|
|
959
1009
|
for v in stage_status.values():
|
|
960
1010
|
if v == "FAIL":
|
|
961
1011
|
return "FAIL"
|
|
@@ -964,8 +1014,14 @@ def _compute_final_status(
|
|
|
964
1014
|
return "FAIL"
|
|
965
1015
|
if int(api.get("failed") or 0) > 0:
|
|
966
1016
|
return "FAIL"
|
|
1017
|
+
conditional = {
|
|
1018
|
+
"USER_SKIPPED", "BLOCKED", "BLOCKED_BY_ENV", "BLOCKED_BY_DBA",
|
|
1019
|
+
"NOT_RUN", "PARTIAL",
|
|
1020
|
+
}
|
|
1021
|
+
if api_status in conditional or db in conditional:
|
|
1022
|
+
return "CONDITIONAL_OK"
|
|
967
1023
|
for v in stage_status.values():
|
|
968
|
-
if v in {"WARN",
|
|
1024
|
+
if v in {"WARN", *conditional}:
|
|
969
1025
|
return "WARN" if v == "WARN" else "CONDITIONAL_OK"
|
|
970
1026
|
return "OK"
|
|
971
1027
|
|
|
@@ -984,6 +1040,8 @@ def _changed_files_from_git(
|
|
|
984
1040
|
changed: list[dict[str, Any]] = []
|
|
985
1041
|
if not base or not head:
|
|
986
1042
|
return diff_stat, changed
|
|
1043
|
+
# The ledger bounds the complete task. Using only ``head^..head`` would
|
|
1044
|
+
# silently omit earlier checkpoint commits from the same change.
|
|
987
1045
|
rng = f"{base}..{head}"
|
|
988
1046
|
code, out, _ = git_run(project, "diff", "--numstat", rng)
|
|
989
1047
|
if code != 0 or not out:
|
|
@@ -1018,7 +1076,11 @@ def _changed_files_from_git(
|
|
|
1018
1076
|
return diff_stat, changed
|
|
1019
1077
|
|
|
1020
1078
|
|
|
1021
|
-
def _review_summary(
|
|
1079
|
+
def _review_summary(
|
|
1080
|
+
change_dir: Path,
|
|
1081
|
+
existing: dict[str, Any] | None,
|
|
1082
|
+
events: list[dict[str, Any]] | None = None,
|
|
1083
|
+
) -> dict[str, Any]:
|
|
1022
1084
|
base = {
|
|
1023
1085
|
"status": "ADVISORY",
|
|
1024
1086
|
"red": 0,
|
|
@@ -1033,13 +1095,102 @@ def _review_summary(change_dir: Path, existing: dict[str, Any] | None) -> dict[s
|
|
|
1033
1095
|
merged = dict(base)
|
|
1034
1096
|
merged.update(existing["reviewSummary"])
|
|
1035
1097
|
return merged
|
|
1036
|
-
if not
|
|
1037
|
-
|
|
1038
|
-
if not re.search(r"harness-review|##\s*review", log_text, re.IGNORECASE):
|
|
1039
|
-
base["status"] = "ADVISORY_NOT_RUN"
|
|
1098
|
+
if not review_evidence_present(change_dir, events):
|
|
1099
|
+
base["status"] = "ADVISORY_NOT_RUN"
|
|
1040
1100
|
return base
|
|
1041
1101
|
|
|
1042
1102
|
|
|
1103
|
+
def _final_commit_from_sources(
|
|
1104
|
+
ledger: dict[str, Any] | None,
|
|
1105
|
+
events: list[dict[str, Any]],
|
|
1106
|
+
existing: dict[str, Any] | None,
|
|
1107
|
+
project: Path,
|
|
1108
|
+
) -> str:
|
|
1109
|
+
if ledger:
|
|
1110
|
+
for key in (
|
|
1111
|
+
"mergeFinalHash", "finalCommit", "finalHash", "changeCommit", "headCommit",
|
|
1112
|
+
):
|
|
1113
|
+
value = str(ledger.get(key) or "").strip()
|
|
1114
|
+
if value:
|
|
1115
|
+
return value
|
|
1116
|
+
commit_pattern = re.compile(r"\b[0-9a-f]{7,40}\b", re.IGNORECASE)
|
|
1117
|
+
for event in reversed(events):
|
|
1118
|
+
if str(event.get("phase") or "").lower() not in {"submit", "merge", "archive"}:
|
|
1119
|
+
continue
|
|
1120
|
+
text = " ".join(str(event.get(key) or "") for key in ("note", "message", "command"))
|
|
1121
|
+
match = commit_pattern.search(text)
|
|
1122
|
+
if match:
|
|
1123
|
+
return match.group(0)
|
|
1124
|
+
if existing and existing.get("finalCommit"):
|
|
1125
|
+
return str(existing["finalCommit"])
|
|
1126
|
+
code, head, _ = git_run(project, "rev-parse", "HEAD")
|
|
1127
|
+
return head if code == 0 and head else ""
|
|
1128
|
+
|
|
1129
|
+
|
|
1130
|
+
def _business_goal_from_sources(change_dir: Path, events: list[dict[str, Any]]) -> str:
|
|
1131
|
+
plans_root = change_dir / "plans"
|
|
1132
|
+
primary = sorted(plans_root.glob("*-plan.md"))
|
|
1133
|
+
secondary = [
|
|
1134
|
+
path for path in sorted(plans_root.glob("*.md"))
|
|
1135
|
+
if path not in primary
|
|
1136
|
+
and "implementation-detail" not in path.name
|
|
1137
|
+
and "test-scenarios" not in path.name
|
|
1138
|
+
]
|
|
1139
|
+
for plan in [*primary, *secondary]:
|
|
1140
|
+
try:
|
|
1141
|
+
text = plan.read_text(encoding="utf-8-sig")
|
|
1142
|
+
except OSError:
|
|
1143
|
+
continue
|
|
1144
|
+
body = re.sub(r"\A---\s*\n.*?\n---\s*\n", "", text, count=1, flags=re.DOTALL)
|
|
1145
|
+
goal = re.search(r"(?im)^\s*(?:goal|目标|业务目标|需求)\s*[::]\s*(.+)$", body)
|
|
1146
|
+
if goal:
|
|
1147
|
+
return goal.group(1).strip()
|
|
1148
|
+
scope = re.search(r"(?im)^\s*>?\s*(?:变更范围|目标)\s*[::]\s*(.+)$", body)
|
|
1149
|
+
if scope:
|
|
1150
|
+
return scope.group(1).strip()
|
|
1151
|
+
first_task = re.search(r"(?m)^\s*\|\s*1\s*\|\s*([^|]+?)\s*\|", body)
|
|
1152
|
+
if first_task:
|
|
1153
|
+
return first_task.group(1).strip()
|
|
1154
|
+
for line in body.splitlines():
|
|
1155
|
+
clean = line.strip().lstrip("#").strip()
|
|
1156
|
+
if clean and not clean.startswith(("---", ">", "|")) and len(clean) > 8:
|
|
1157
|
+
return clean
|
|
1158
|
+
for event in events:
|
|
1159
|
+
if event.get("type") == "decision":
|
|
1160
|
+
value = str(event.get("decision") or event.get("note") or "").strip()
|
|
1161
|
+
if value:
|
|
1162
|
+
return re.sub(r"^(?:需求收敛|目标)\s*[::]\s*", "", value)
|
|
1163
|
+
return ""
|
|
1164
|
+
|
|
1165
|
+
|
|
1166
|
+
def _timeline_from_events(event_summary: dict[str, Any], events: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
1167
|
+
timeline: list[dict[str, Any]] = []
|
|
1168
|
+
for phase, info in (event_summary.get("phases") or {}).items():
|
|
1169
|
+
attempts = info.get("attempts") if isinstance(info.get("attempts"), list) else []
|
|
1170
|
+
for attempt in attempts:
|
|
1171
|
+
timeline.append({
|
|
1172
|
+
"phase": phase,
|
|
1173
|
+
"attempt": attempt.get("attempt"),
|
|
1174
|
+
"startedAt": attempt.get("started_at"),
|
|
1175
|
+
"endedAt": attempt.get("ended_at"),
|
|
1176
|
+
"durationMs": attempt.get("duration_ms"),
|
|
1177
|
+
"status": attempt.get("status") or "UNKNOWN",
|
|
1178
|
+
"executorTool": attempt.get("executor_tool"),
|
|
1179
|
+
"executorAgent": attempt.get("executor_agent"),
|
|
1180
|
+
"handoffFromTool": attempt.get("handoff_from_tool"),
|
|
1181
|
+
})
|
|
1182
|
+
for event in events:
|
|
1183
|
+
if event.get("type") not in {"decision", "issue"}:
|
|
1184
|
+
continue
|
|
1185
|
+
timeline.append({
|
|
1186
|
+
"phase": event.get("phase"),
|
|
1187
|
+
"timestamp": event.get("timestamp"),
|
|
1188
|
+
"type": event.get("type"),
|
|
1189
|
+
"summary": event.get("decision") or event.get("message") or event.get("note") or "",
|
|
1190
|
+
})
|
|
1191
|
+
return timeline
|
|
1192
|
+
|
|
1193
|
+
|
|
1043
1194
|
def collect_summary_data(
|
|
1044
1195
|
change_dir: Path,
|
|
1045
1196
|
*,
|
|
@@ -1118,18 +1269,14 @@ def collect_summary_data(
|
|
|
1118
1269
|
if existing and existing.get("businessGoal"):
|
|
1119
1270
|
data["businessGoal"] = existing["businessGoal"]
|
|
1120
1271
|
else:
|
|
1121
|
-
|
|
1272
|
+
inferred_goal = _business_goal_from_sources(change_dir, events)
|
|
1273
|
+
data["businessGoal"] = inferred_goal or (NOT_AVAILABLE if for_replay else "")
|
|
1122
1274
|
|
|
1123
1275
|
# commits
|
|
1124
1276
|
project = find_project_root(change_dir)
|
|
1125
1277
|
if not data.get("finalCommit") or str(data.get("finalCommit")).startswith("<"):
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
data["finalCommit"] = head
|
|
1129
|
-
elif existing and existing.get("finalCommit"):
|
|
1130
|
-
data["finalCommit"] = existing["finalCommit"]
|
|
1131
|
-
else:
|
|
1132
|
-
data["finalCommit"] = NOT_AVAILABLE if for_replay else ""
|
|
1278
|
+
final_commit = _final_commit_from_sources(ledger, events, existing, project)
|
|
1279
|
+
data["finalCommit"] = final_commit or (NOT_AVAILABLE if for_replay else "")
|
|
1133
1280
|
|
|
1134
1281
|
if not data.get("baseCommit") or str(data.get("baseCommit")).startswith("<"):
|
|
1135
1282
|
if ledger and ledger.get("baseCommit"):
|
|
@@ -1235,18 +1382,47 @@ def collect_summary_data(
|
|
|
1235
1382
|
if not isinstance(data.get("artifacts"), list):
|
|
1236
1383
|
data["artifacts"] = []
|
|
1237
1384
|
if not for_replay:
|
|
1238
|
-
|
|
1239
|
-
pass
|
|
1385
|
+
data["artifacts"] = _artifacts_from_events(events)
|
|
1240
1386
|
|
|
1241
|
-
data["reviewSummary"] = _review_summary(
|
|
1242
|
-
|
|
1387
|
+
data["reviewSummary"] = _review_summary(
|
|
1388
|
+
change_dir,
|
|
1389
|
+
existing if for_replay else None,
|
|
1390
|
+
events,
|
|
1391
|
+
)
|
|
1392
|
+
if not for_replay:
|
|
1393
|
+
data["timeline"] = _timeline_from_events(event_summary, events)
|
|
1394
|
+
else:
|
|
1395
|
+
data.setdefault("timeline", [])
|
|
1243
1396
|
data.setdefault("uncommittedTestEvidence", [])
|
|
1244
1397
|
|
|
1245
|
-
#
|
|
1398
|
+
# Derive risks/actions from evidence. These fields are facts, not model prose.
|
|
1246
1399
|
if not for_replay:
|
|
1247
|
-
data["maintenanceNotes"] = [
|
|
1248
|
-
|
|
1400
|
+
data["maintenanceNotes"] = [
|
|
1401
|
+
str(event.get("note") or event.get("message") or "")
|
|
1402
|
+
for event in events
|
|
1403
|
+
if event.get("type") == "decision" and (event.get("note") or event.get("message"))
|
|
1404
|
+
]
|
|
1405
|
+
data["knownRisks"] = [
|
|
1406
|
+
{
|
|
1407
|
+
"phase": event.get("phase"),
|
|
1408
|
+
"severity": event.get("severity") or "unknown",
|
|
1409
|
+
"message": event.get("message") or event.get("note") or event.get("code") or "",
|
|
1410
|
+
}
|
|
1411
|
+
for event in events
|
|
1412
|
+
if event.get("type") == "issue"
|
|
1413
|
+
and not (
|
|
1414
|
+
event.get("phase") == "archive"
|
|
1415
|
+
and event.get("code") == "missing-command"
|
|
1416
|
+
)
|
|
1417
|
+
]
|
|
1249
1418
|
data["manualActions"] = []
|
|
1419
|
+
for name, value in (data.get("stageStatus") or {}).items():
|
|
1420
|
+
if value in {"BLOCKED", "BLOCKED_BY_ENV", "BLOCKED_BY_DBA", "NOT_RUN", "USER_SKIPPED"}:
|
|
1421
|
+
data["manualActions"].append({
|
|
1422
|
+
"stage": name,
|
|
1423
|
+
"status": value,
|
|
1424
|
+
"action": "补充或确认该阶段的真实验证证据",
|
|
1425
|
+
})
|
|
1250
1426
|
else:
|
|
1251
1427
|
data.setdefault("maintenanceNotes", [])
|
|
1252
1428
|
data.setdefault("knownRisks", [])
|
|
@@ -1978,6 +2154,7 @@ def cmd_finalize(
|
|
|
1978
2154
|
_restore_target,
|
|
1979
2155
|
phase="archive",
|
|
1980
2156
|
type_="phase.end",
|
|
2157
|
+
status="FAIL",
|
|
1981
2158
|
note="finalize aborted; render failed; original preserved",
|
|
1982
2159
|
)
|
|
1983
2160
|
except OSError:
|
|
@@ -2117,6 +2294,7 @@ def cmd_finalize(
|
|
|
2117
2294
|
_safe_append_restored,
|
|
2118
2295
|
phase="archive",
|
|
2119
2296
|
type_="phase.end",
|
|
2297
|
+
status="FAIL",
|
|
2120
2298
|
note="finalize aborted; original preserved",
|
|
2121
2299
|
)
|
|
2122
2300
|
except OSError:
|
|
@@ -2168,7 +2346,12 @@ def cmd_finalize(
|
|
|
2168
2346
|
if service_result.get("warning"):
|
|
2169
2347
|
warnings.append(str(service_result["warning"]))
|
|
2170
2348
|
|
|
2171
|
-
_safe_append(
|
|
2349
|
+
_safe_append(
|
|
2350
|
+
phase="archive",
|
|
2351
|
+
type_="phase.end",
|
|
2352
|
+
status="WARN" if warnings else "OK",
|
|
2353
|
+
note="finalize complete",
|
|
2354
|
+
)
|
|
2172
2355
|
|
|
2173
2356
|
payload["ok"] = True
|
|
2174
2357
|
payload["warnings"] = warnings
|