@hunter-harness/workflow-harness 0.2.16 → 0.2.18
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/CONTEXT.md +14 -1
- package/harness/bundles/general/claude-code/README.md +1 -1
- package/harness/bundles/general/claude-code/harness-archive/SKILL.md +7 -4
- package/harness/bundles/general/claude-code/harness-archive/reference.md +1 -1
- package/harness/bundles/general/claude-code/harness-codebase-map/SKILL.md +4 -2
- package/harness/bundles/general/claude-code/harness-knowledge-ingest/SKILL.md +4 -2
- package/harness/bundles/general/claude-code/harness-knowledge-query/SKILL.md +4 -2
- package/harness/bundles/general/claude-code/harness-plan/SKILL.md +8 -4
- package/harness/bundles/general/claude-code/harness-plan/checklist.md +5 -5
- package/harness/bundles/general/claude-code/harness-plan/reference.md +5 -5
- package/harness/bundles/general/claude-code/harness-review/SKILL.md +5 -2
- package/harness/bundles/general/claude-code/harness-review/checklist.md +1 -1
- package/harness/bundles/general/claude-code/harness-run/SKILL.md +7 -3
- package/harness/bundles/general/claude-code/harness-run/checklist.md +5 -5
- package/harness/bundles/general/claude-code/harness-run/reference.md +1 -1
- package/harness/bundles/general/claude-code/harness-submit/SKILL.md +6 -3
- package/harness/bundles/general/claude-code/harness-submit/checklist.md +11 -11
- package/harness/bundles/general/claude-code/harness-submit/reference.md +3 -3
- package/harness/bundles/general/claude-code/harness-sync/SKILL.md +4 -2
- package/harness/bundles/general/claude-code/harness-sync/reference.md +1 -1
- package/harness/bundles/general/claude-code/harness-test/SKILL.md +7 -4
- package/harness/bundles/general/claude-code/harness-test/checklist.md +8 -5
- package/harness/bundles/general/claude-code/harness-test/pitfalls.md +1 -1
- package/harness/bundles/general/claude-code/harness-test/reference.md +28 -13
- package/harness/bundles/general/claude-code/protocols/submit-protocol.md +1 -1
- package/harness/bundles/general/claude-code/scripts/harness_deploy.py +7 -0
- package/harness/bundles/general/claude-code/scripts/harness_integration.py +26 -2
- package/harness/bundles/general/claude-code/scripts/harness_plan_finalize.py +159 -7
- package/harness/bundles/general/claude-code/scripts/harness_profile.py +1 -0
- package/harness/bundles/general/claude-code/scripts/harness_runtime.py +4 -4
- package/harness/bundles/general/claude-code/scripts/harness_service.py +1 -0
- package/harness/bundles/general/claude-code/scripts/harness_test_cleanup.py +175 -0
- package/harness/bundles/general/codebuddy/.harness-build.json +1 -1
- package/harness/bundles/general/codebuddy/CONTEXT.md +14 -1
- package/harness/bundles/general/codebuddy/README.md +1 -1
- package/harness/bundles/general/codebuddy/harness-archive/SKILL.md +7 -4
- package/harness/bundles/general/codebuddy/harness-archive/reference.md +1 -1
- package/harness/bundles/general/codebuddy/harness-codebase-map/SKILL.md +4 -2
- package/harness/bundles/general/codebuddy/harness-knowledge-ingest/SKILL.md +4 -2
- package/harness/bundles/general/codebuddy/harness-knowledge-query/SKILL.md +4 -2
- package/harness/bundles/general/codebuddy/harness-plan/SKILL.md +8 -4
- package/harness/bundles/general/codebuddy/harness-plan/checklist.md +5 -5
- package/harness/bundles/general/codebuddy/harness-plan/reference.md +5 -5
- package/harness/bundles/general/codebuddy/harness-review/SKILL.md +5 -2
- package/harness/bundles/general/codebuddy/harness-review/checklist.md +1 -1
- package/harness/bundles/general/codebuddy/harness-run/SKILL.md +7 -3
- package/harness/bundles/general/codebuddy/harness-run/checklist.md +5 -5
- package/harness/bundles/general/codebuddy/harness-run/reference.md +1 -1
- package/harness/bundles/general/codebuddy/harness-submit/SKILL.md +6 -3
- package/harness/bundles/general/codebuddy/harness-submit/checklist.md +11 -11
- package/harness/bundles/general/codebuddy/harness-submit/reference.md +3 -3
- package/harness/bundles/general/codebuddy/harness-sync/SKILL.md +4 -2
- package/harness/bundles/general/codebuddy/harness-sync/reference.md +1 -1
- package/harness/bundles/general/codebuddy/harness-test/SKILL.md +7 -4
- package/harness/bundles/general/codebuddy/harness-test/checklist.md +8 -5
- package/harness/bundles/general/codebuddy/harness-test/pitfalls.md +1 -1
- package/harness/bundles/general/codebuddy/harness-test/reference.md +28 -13
- package/harness/bundles/general/codebuddy/protocols/submit-protocol.md +1 -1
- package/harness/bundles/general/codebuddy/scripts/harness_deploy.py +7 -0
- package/harness/bundles/general/codebuddy/scripts/harness_integration.py +26 -2
- package/harness/bundles/general/codebuddy/scripts/harness_plan_finalize.py +159 -7
- package/harness/bundles/general/codebuddy/scripts/harness_profile.py +1 -0
- package/harness/bundles/general/codebuddy/scripts/harness_runtime.py +4 -4
- package/harness/bundles/general/codebuddy/scripts/harness_service.py +1 -0
- package/harness/bundles/general/codebuddy/scripts/harness_test_cleanup.py +175 -0
- package/harness/bundles/general/codex/.harness-build.json +1 -1
- package/harness/bundles/general/codex/CONTEXT.md +14 -1
- package/harness/bundles/general/codex/README.md +1 -1
- package/harness/bundles/general/codex/harness-archive/SKILL.md +7 -4
- package/harness/bundles/general/codex/harness-archive/reference.md +1 -1
- package/harness/bundles/general/codex/harness-codebase-map/SKILL.md +4 -2
- package/harness/bundles/general/codex/harness-knowledge-ingest/SKILL.md +4 -2
- package/harness/bundles/general/codex/harness-knowledge-query/SKILL.md +4 -2
- package/harness/bundles/general/codex/harness-plan/SKILL.md +8 -4
- package/harness/bundles/general/codex/harness-plan/checklist.md +5 -5
- package/harness/bundles/general/codex/harness-plan/reference.md +5 -5
- package/harness/bundles/general/codex/harness-review/SKILL.md +5 -2
- package/harness/bundles/general/codex/harness-review/checklist.md +1 -1
- package/harness/bundles/general/codex/harness-run/SKILL.md +7 -3
- package/harness/bundles/general/codex/harness-run/checklist.md +5 -5
- package/harness/bundles/general/codex/harness-run/reference.md +1 -1
- package/harness/bundles/general/codex/harness-submit/SKILL.md +6 -3
- package/harness/bundles/general/codex/harness-submit/checklist.md +11 -11
- package/harness/bundles/general/codex/harness-submit/reference.md +3 -3
- package/harness/bundles/general/codex/harness-sync/SKILL.md +4 -2
- package/harness/bundles/general/codex/harness-sync/reference.md +1 -1
- package/harness/bundles/general/codex/harness-test/SKILL.md +7 -4
- package/harness/bundles/general/codex/harness-test/checklist.md +8 -5
- package/harness/bundles/general/codex/harness-test/pitfalls.md +1 -1
- package/harness/bundles/general/codex/harness-test/reference.md +28 -13
- package/harness/bundles/general/codex/protocols/submit-protocol.md +1 -1
- package/harness/bundles/general/codex/scripts/harness_deploy.py +7 -0
- package/harness/bundles/general/codex/scripts/harness_integration.py +26 -2
- package/harness/bundles/general/codex/scripts/harness_plan_finalize.py +159 -7
- package/harness/bundles/general/codex/scripts/harness_profile.py +1 -0
- package/harness/bundles/general/codex/scripts/harness_runtime.py +4 -4
- package/harness/bundles/general/codex/scripts/harness_service.py +1 -0
- package/harness/bundles/general/codex/scripts/harness_test_cleanup.py +175 -0
- package/harness/bundles/general/cursor/.harness-build.json +1 -1
- package/harness/bundles/general/cursor/CONTEXT.md +14 -1
- package/harness/bundles/general/cursor/README.md +1 -1
- package/harness/bundles/general/cursor/harness-archive/SKILL.md +7 -4
- package/harness/bundles/general/cursor/harness-archive/reference.md +1 -1
- package/harness/bundles/general/cursor/harness-codebase-map/SKILL.md +4 -2
- package/harness/bundles/general/cursor/harness-knowledge-ingest/SKILL.md +4 -2
- package/harness/bundles/general/cursor/harness-knowledge-query/SKILL.md +4 -2
- package/harness/bundles/general/cursor/harness-plan/SKILL.md +8 -4
- package/harness/bundles/general/cursor/harness-plan/checklist.md +5 -5
- package/harness/bundles/general/cursor/harness-plan/reference.md +5 -5
- package/harness/bundles/general/cursor/harness-review/SKILL.md +5 -2
- package/harness/bundles/general/cursor/harness-review/checklist.md +1 -1
- package/harness/bundles/general/cursor/harness-run/SKILL.md +7 -3
- package/harness/bundles/general/cursor/harness-run/checklist.md +5 -5
- package/harness/bundles/general/cursor/harness-run/reference.md +1 -1
- package/harness/bundles/general/cursor/harness-submit/SKILL.md +6 -3
- package/harness/bundles/general/cursor/harness-submit/checklist.md +11 -11
- package/harness/bundles/general/cursor/harness-submit/reference.md +3 -3
- package/harness/bundles/general/cursor/harness-sync/SKILL.md +4 -2
- package/harness/bundles/general/cursor/harness-sync/reference.md +1 -1
- package/harness/bundles/general/cursor/harness-test/SKILL.md +7 -4
- package/harness/bundles/general/cursor/harness-test/checklist.md +8 -5
- package/harness/bundles/general/cursor/harness-test/pitfalls.md +1 -1
- package/harness/bundles/general/cursor/harness-test/reference.md +28 -13
- package/harness/bundles/general/cursor/protocols/submit-protocol.md +1 -1
- package/harness/bundles/general/cursor/scripts/harness_deploy.py +7 -0
- package/harness/bundles/general/cursor/scripts/harness_integration.py +26 -2
- package/harness/bundles/general/cursor/scripts/harness_plan_finalize.py +159 -7
- package/harness/bundles/general/cursor/scripts/harness_profile.py +1 -0
- package/harness/bundles/general/cursor/scripts/harness_runtime.py +4 -4
- package/harness/bundles/general/cursor/scripts/harness_service.py +1 -0
- package/harness/bundles/general/cursor/scripts/harness_test_cleanup.py +175 -0
- package/harness/bundles/java/claude-code/.harness-build.json +1 -1
- package/harness/bundles/java/claude-code/CONTEXT.md +14 -1
- package/harness/bundles/java/claude-code/README.md +1 -1
- package/harness/bundles/java/claude-code/harness-apidoc/SKILL.md +5 -2
- package/harness/bundles/java/claude-code/harness-archive/SKILL.md +7 -4
- package/harness/bundles/java/claude-code/harness-archive/reference.md +1 -1
- package/harness/bundles/java/claude-code/harness-codebase-map/SKILL.md +4 -2
- package/harness/bundles/java/claude-code/harness-knowledge-ingest/SKILL.md +4 -2
- package/harness/bundles/java/claude-code/harness-knowledge-query/SKILL.md +4 -2
- package/harness/bundles/java/claude-code/harness-package/SKILL.md +5 -2
- package/harness/bundles/java/claude-code/harness-package/checklist.md +5 -5
- package/harness/bundles/java/claude-code/harness-plan/SKILL.md +8 -4
- package/harness/bundles/java/claude-code/harness-plan/checklist.md +5 -5
- package/harness/bundles/java/claude-code/harness-plan/reference.md +5 -5
- package/harness/bundles/java/claude-code/harness-review/SKILL.md +5 -2
- package/harness/bundles/java/claude-code/harness-review/checklist.md +1 -1
- package/harness/bundles/java/claude-code/harness-run/SKILL.md +7 -3
- package/harness/bundles/java/claude-code/harness-run/checklist.md +6 -6
- package/harness/bundles/java/claude-code/harness-run/reference.md +352 -352
- package/harness/bundles/java/claude-code/harness-submit/SKILL.md +6 -3
- package/harness/bundles/java/claude-code/harness-submit/checklist.md +11 -11
- package/harness/bundles/java/claude-code/harness-submit/reference.md +3 -3
- package/harness/bundles/java/claude-code/harness-sync/SKILL.md +4 -2
- package/harness/bundles/java/claude-code/harness-sync/reference.md +1 -1
- package/harness/bundles/java/claude-code/harness-test/SKILL.md +8 -5
- package/harness/bundles/java/claude-code/harness-test/pitfalls.md +1 -1
- package/harness/bundles/java/claude-code/harness-test/reference.md +1 -1
- package/harness/bundles/java/claude-code/protocols/submit-protocol.md +1 -1
- package/harness/bundles/java/claude-code/scripts/harness_deploy.py +7 -0
- package/harness/bundles/java/claude-code/scripts/harness_integration.py +26 -2
- package/harness/bundles/java/claude-code/scripts/harness_plan_finalize.py +159 -7
- package/harness/bundles/java/claude-code/scripts/harness_profile.py +1 -0
- package/harness/bundles/java/claude-code/scripts/harness_runtime.py +4 -4
- package/harness/bundles/java/claude-code/scripts/harness_service.py +1 -0
- package/harness/bundles/java/claude-code/scripts/harness_test_cleanup.py +175 -0
- package/harness/bundles/java/codebuddy/.harness-build.json +1 -1
- package/harness/bundles/java/codebuddy/CONTEXT.md +14 -1
- package/harness/bundles/java/codebuddy/README.md +1 -1
- package/harness/bundles/java/codebuddy/harness-apidoc/SKILL.md +5 -2
- package/harness/bundles/java/codebuddy/harness-archive/SKILL.md +7 -4
- package/harness/bundles/java/codebuddy/harness-archive/reference.md +1 -1
- package/harness/bundles/java/codebuddy/harness-codebase-map/SKILL.md +4 -2
- package/harness/bundles/java/codebuddy/harness-knowledge-ingest/SKILL.md +4 -2
- package/harness/bundles/java/codebuddy/harness-knowledge-query/SKILL.md +4 -2
- package/harness/bundles/java/codebuddy/harness-package/SKILL.md +5 -2
- package/harness/bundles/java/codebuddy/harness-package/checklist.md +5 -5
- package/harness/bundles/java/codebuddy/harness-plan/SKILL.md +8 -4
- package/harness/bundles/java/codebuddy/harness-plan/checklist.md +5 -5
- package/harness/bundles/java/codebuddy/harness-plan/reference.md +5 -5
- package/harness/bundles/java/codebuddy/harness-review/SKILL.md +5 -2
- package/harness/bundles/java/codebuddy/harness-review/checklist.md +1 -1
- package/harness/bundles/java/codebuddy/harness-run/SKILL.md +7 -3
- package/harness/bundles/java/codebuddy/harness-run/checklist.md +6 -6
- package/harness/bundles/java/codebuddy/harness-run/reference.md +352 -352
- package/harness/bundles/java/codebuddy/harness-submit/SKILL.md +6 -3
- package/harness/bundles/java/codebuddy/harness-submit/checklist.md +11 -11
- package/harness/bundles/java/codebuddy/harness-submit/reference.md +3 -3
- package/harness/bundles/java/codebuddy/harness-sync/SKILL.md +4 -2
- package/harness/bundles/java/codebuddy/harness-sync/reference.md +1 -1
- package/harness/bundles/java/codebuddy/harness-test/SKILL.md +8 -5
- package/harness/bundles/java/codebuddy/harness-test/pitfalls.md +1 -1
- package/harness/bundles/java/codebuddy/harness-test/reference.md +1 -1
- package/harness/bundles/java/codebuddy/protocols/submit-protocol.md +1 -1
- package/harness/bundles/java/codebuddy/scripts/harness_deploy.py +7 -0
- package/harness/bundles/java/codebuddy/scripts/harness_integration.py +26 -2
- package/harness/bundles/java/codebuddy/scripts/harness_plan_finalize.py +159 -7
- package/harness/bundles/java/codebuddy/scripts/harness_profile.py +1 -0
- package/harness/bundles/java/codebuddy/scripts/harness_runtime.py +4 -4
- package/harness/bundles/java/codebuddy/scripts/harness_service.py +1 -0
- package/harness/bundles/java/codebuddy/scripts/harness_test_cleanup.py +175 -0
- package/harness/bundles/java/codex/.harness-build.json +1 -1
- package/harness/bundles/java/codex/CONTEXT.md +14 -1
- package/harness/bundles/java/codex/README.md +1 -1
- package/harness/bundles/java/codex/harness-apidoc/SKILL.md +5 -2
- package/harness/bundles/java/codex/harness-archive/SKILL.md +7 -4
- package/harness/bundles/java/codex/harness-archive/reference.md +1 -1
- package/harness/bundles/java/codex/harness-codebase-map/SKILL.md +4 -2
- package/harness/bundles/java/codex/harness-knowledge-ingest/SKILL.md +4 -2
- package/harness/bundles/java/codex/harness-knowledge-query/SKILL.md +4 -2
- package/harness/bundles/java/codex/harness-package/SKILL.md +5 -2
- package/harness/bundles/java/codex/harness-package/checklist.md +5 -5
- package/harness/bundles/java/codex/harness-plan/SKILL.md +8 -4
- package/harness/bundles/java/codex/harness-plan/checklist.md +5 -5
- package/harness/bundles/java/codex/harness-plan/reference.md +5 -5
- package/harness/bundles/java/codex/harness-review/SKILL.md +5 -2
- package/harness/bundles/java/codex/harness-review/checklist.md +1 -1
- package/harness/bundles/java/codex/harness-run/SKILL.md +7 -3
- package/harness/bundles/java/codex/harness-run/checklist.md +6 -6
- package/harness/bundles/java/codex/harness-run/reference.md +352 -352
- package/harness/bundles/java/codex/harness-submit/SKILL.md +6 -3
- package/harness/bundles/java/codex/harness-submit/checklist.md +11 -11
- package/harness/bundles/java/codex/harness-submit/reference.md +3 -3
- package/harness/bundles/java/codex/harness-sync/SKILL.md +4 -2
- package/harness/bundles/java/codex/harness-sync/reference.md +1 -1
- package/harness/bundles/java/codex/harness-test/SKILL.md +8 -5
- package/harness/bundles/java/codex/harness-test/pitfalls.md +1 -1
- package/harness/bundles/java/codex/harness-test/reference.md +1 -1
- package/harness/bundles/java/codex/protocols/submit-protocol.md +1 -1
- package/harness/bundles/java/codex/scripts/harness_deploy.py +7 -0
- package/harness/bundles/java/codex/scripts/harness_integration.py +26 -2
- package/harness/bundles/java/codex/scripts/harness_plan_finalize.py +159 -7
- package/harness/bundles/java/codex/scripts/harness_profile.py +1 -0
- package/harness/bundles/java/codex/scripts/harness_runtime.py +4 -4
- package/harness/bundles/java/codex/scripts/harness_service.py +1 -0
- package/harness/bundles/java/codex/scripts/harness_test_cleanup.py +175 -0
- package/harness/bundles/java/cursor/.harness-build.json +1 -1
- package/harness/bundles/java/cursor/CONTEXT.md +14 -1
- package/harness/bundles/java/cursor/README.md +1 -1
- package/harness/bundles/java/cursor/harness-apidoc/SKILL.md +5 -2
- package/harness/bundles/java/cursor/harness-archive/SKILL.md +7 -4
- package/harness/bundles/java/cursor/harness-archive/reference.md +1 -1
- package/harness/bundles/java/cursor/harness-codebase-map/SKILL.md +4 -2
- package/harness/bundles/java/cursor/harness-knowledge-ingest/SKILL.md +4 -2
- package/harness/bundles/java/cursor/harness-knowledge-query/SKILL.md +4 -2
- package/harness/bundles/java/cursor/harness-package/SKILL.md +5 -2
- package/harness/bundles/java/cursor/harness-package/checklist.md +5 -5
- package/harness/bundles/java/cursor/harness-plan/SKILL.md +8 -4
- package/harness/bundles/java/cursor/harness-plan/checklist.md +5 -5
- package/harness/bundles/java/cursor/harness-plan/reference.md +5 -5
- package/harness/bundles/java/cursor/harness-review/SKILL.md +5 -2
- package/harness/bundles/java/cursor/harness-review/checklist.md +1 -1
- package/harness/bundles/java/cursor/harness-run/SKILL.md +7 -3
- package/harness/bundles/java/cursor/harness-run/checklist.md +6 -6
- package/harness/bundles/java/cursor/harness-run/reference.md +352 -352
- package/harness/bundles/java/cursor/harness-submit/SKILL.md +6 -3
- package/harness/bundles/java/cursor/harness-submit/checklist.md +11 -11
- package/harness/bundles/java/cursor/harness-submit/reference.md +3 -3
- package/harness/bundles/java/cursor/harness-sync/SKILL.md +4 -2
- package/harness/bundles/java/cursor/harness-sync/reference.md +1 -1
- package/harness/bundles/java/cursor/harness-test/SKILL.md +8 -5
- package/harness/bundles/java/cursor/harness-test/pitfalls.md +1 -1
- package/harness/bundles/java/cursor/harness-test/reference.md +1 -1
- package/harness/bundles/java/cursor/protocols/submit-protocol.md +1 -1
- package/harness/bundles/java/cursor/scripts/harness_deploy.py +7 -0
- package/harness/bundles/java/cursor/scripts/harness_integration.py +26 -2
- package/harness/bundles/java/cursor/scripts/harness_plan_finalize.py +159 -7
- package/harness/bundles/java/cursor/scripts/harness_profile.py +1 -0
- package/harness/bundles/java/cursor/scripts/harness_runtime.py +4 -4
- package/harness/bundles/java/cursor/scripts/harness_service.py +1 -0
- package/harness/bundles/java/cursor/scripts/harness_test_cleanup.py +175 -0
- package/harness/manifests/general/claude-code.json +37 -33
- package/harness/manifests/general/codebuddy.json +37 -33
- package/harness/manifests/general/codex.json +37 -33
- package/harness/manifests/general/cursor.json +37 -33
- package/harness/manifests/java/claude-code.json +39 -35
- package/harness/manifests/java/codebuddy.json +39 -35
- package/harness/manifests/java/codex.json +39 -35
- package/harness/manifests/java/cursor.json +39 -35
- package/hunter-workflow-family.json +2 -2
- package/package.json +1 -1
|
@@ -150,7 +150,7 @@ spring:
|
|
|
150
150
|
powershell.exe -NoProfile -Command "mvn spring-boot:run -pl <module> -Dspring-boot.run.profiles=<profile> -Dspring-boot.run.jvmArguments='-Dspring.config.additional-location=file:C:/temp/harness-test-overlay/<change-name>/application-harness-test.yml'"
|
|
151
151
|
```
|
|
152
152
|
|
|
153
|
-
不得默认使用 `.harness/changes/<change>/runtime/application-harness-test.yml` 相对路径作为 JVM `additional-location`。如必须修改 tracked 配置文件,先
|
|
153
|
+
不得默认使用 `.harness/changes/<change>/runtime/application-harness-test.yml` 相对路径作为 JVM `additional-location`。如必须修改 tracked 配置文件,先 blocking user confirmation,最终报告至少 🟡 WARN。
|
|
154
154
|
|
|
155
155
|
## 服务决策门(Service Decision Gate)与服务生命周期管理
|
|
156
156
|
|
|
@@ -361,11 +361,13 @@ async function cleanup(token) {
|
|
|
361
361
|
|
|
362
362
|
// ===== 主流程 =====
|
|
363
363
|
const token = await resolveCredential('admin');
|
|
364
|
+
const batchStart = Date.now();
|
|
364
365
|
await setup(token);
|
|
365
366
|
for (const scenario of scenarios) {
|
|
366
367
|
await runOne(scenario, token);
|
|
367
368
|
}
|
|
368
369
|
const cleanupResult = await cleanup(token);
|
|
370
|
+
const batchDurationMs = Date.now() - batchStart;
|
|
369
371
|
|
|
370
372
|
const summary = {
|
|
371
373
|
runner: 'api-test-executor',
|
|
@@ -377,7 +379,11 @@ const summary = {
|
|
|
377
379
|
setupErrors: setupState.errors,
|
|
378
380
|
cleanupResult,
|
|
379
381
|
credentialRefreshCount,
|
|
380
|
-
|
|
382
|
+
// §5.22: 分层耗时 schema — batchDurationMs 是整个 runner wall-clock;
|
|
383
|
+
// 每个场景的 requestDurationMs 是单 HTTP 请求耗时。聚合合同套件场景
|
|
384
|
+
// 只记录 batch reference/coveredTests,不允许均摊生成伪请求耗时。
|
|
385
|
+
batchDurationMs,
|
|
386
|
+
startedAt: '<ISO>', finishedAt: '<ISO>'
|
|
381
387
|
};
|
|
382
388
|
writeFileSync(RESULTS_FILE, JSON.stringify({ results, summary }, null, 2));
|
|
383
389
|
console.log(`\n结果已写入 ${RESULTS_FILE}`);
|
|
@@ -389,6 +395,7 @@ console.log(`\n结果已写入 ${RESULTS_FILE}`);
|
|
|
389
395
|
> - 凭证从本地 cache 读 + 本地轻量接口验证,**完全不依赖浏览器当前页面 origin**
|
|
390
396
|
> - 同一次执行内 `credentialRefreshCount` 不得 > 1
|
|
391
397
|
> - payload 从数据契约/接口定义/真实样例生成,并在脚本注释中标注字段来源
|
|
398
|
+
> - **§5.22 分层耗时**:`batchDurationMs` 是 runner wall-clock(整个批次耗时),`requestDurationMs` 是单 HTTP 请求耗时。聚合合同套件场景(如 API-C01~C04 共享同一 pytest 批次)只记录 `batchReference` + `coveredTests`,**禁止用 `batchDurationMs / N` 均摊生成伪 `requestDurationMs`**
|
|
392
399
|
|
|
393
400
|
### 执行器降级方案(PowerShell batch runner .ps1)
|
|
394
401
|
|
|
@@ -445,18 +452,20 @@ $results | ConvertTo-Json -Depth 4
|
|
|
445
452
|
| 接口测试执行器(普通) | 30s | **60s** | 抛错继续报告,不再重试 |
|
|
446
453
|
| 接口测试执行器(复杂场景) | 60s | **120s** | 同上 |
|
|
447
454
|
| 凭证获取(认证服务) | 5–10s | **15s** | 计 1 次重取;超过 1 次 → 🟡 WARN |
|
|
448
|
-
| 单个 HTTP GET | < 1s | **8s** |
|
|
449
|
-
| 单个 HTTP POST/PUT | < 2s | **10s** |
|
|
455
|
+
| 单个 HTTP GET | < 1s | **8s** | requestDurationMs > 10s → 🟡 SLOW |
|
|
456
|
+
| 单个 HTTP POST/PUT | < 2s | **10s** | requestDurationMs > 10s → 🟡 SLOW |
|
|
450
457
|
| health probe | < 1s | **3s** | 计入启动状态机 |
|
|
451
458
|
| 构建工具编译(如 Maven compile) | 10–60s | **180s** | 输出最后 30 行日志后停 |
|
|
452
459
|
| 测试命令(如 Maven test) | 60–180s | **300s** | 输出失败用例后停 |
|
|
453
460
|
| service start | 30–60s | **120s** | 4.1 启动状态机收敛 |
|
|
454
461
|
|
|
455
|
-
|
|
462
|
+
**§5.22 分层耗时与反馈规则**:
|
|
456
463
|
- 等待 > 10s 必须输出一次状态行(启动等待、执行器、构建工具均适用)
|
|
457
464
|
- 不得静默等待
|
|
458
|
-
-
|
|
459
|
-
-
|
|
465
|
+
- **`requestDurationMs`**(单 HTTP 请求耗时):`> 10000` → 🟡 SLOW;`> 30000` → ❌ TIMEOUT_RISK
|
|
466
|
+
- **`batchDurationMs`**(runner wall-clock,整个批次耗时):`> 180000`(3 分钟)→ 🟡 BATCH_SLOW;`> 300000`(5 分钟)→ ❌ BATCH_TIMEOUT_RISK
|
|
467
|
+
- **聚合合同套件场景**(如 API-C01~C04 共享同一 pytest 批次):只记录 `batchReference` + `coveredTests`,**禁止用 `batchDurationMs / N` 均摊生成伪 `requestDurationMs`**
|
|
468
|
+
- 报告必须明确标注是哪一层越界(batch-level 还是 request-level),不得混用
|
|
460
469
|
- 请求慢不得忽略,必须说明是服务、网络、认证还是工具调用开销
|
|
461
470
|
|
|
462
471
|
## 认证凭证缓存与复用
|
|
@@ -638,17 +647,23 @@ TEST_<change-name>_<timestamp>_<short-random>
|
|
|
638
647
|
| cleanup | cleaned=X / leftover=Y | 接口 /<resource> 不支持删除,2 条遗留 |
|
|
639
648
|
|
|
640
649
|
### 接口测试
|
|
641
|
-
| # | 场景 | 方法 | URL | 预期 | 实际 |
|
|
650
|
+
| # | 场景 | 方法 | URL | 预期 | 实际 | requestDurationMs | 状态 |
|
|
642
651
|
|:--:|------|:----:|-----|------|------|----------:|:--:|
|
|
643
652
|
| API-001 | 创建资源 | POST | /api/xxx | 200, code=0 | 200, code=0 | 245 | ✅ PASS |
|
|
644
653
|
| API-002 | 参数校验 | POST | /api/xxx | code=xxx | code=xxx | 180 | ✅ PASS |
|
|
645
654
|
| API-003 | 查询子规则 | GET | /api/xxx/{id} | 200 | — | — | 🟡 BLOCKED(setup.createRule 失败) |
|
|
646
655
|
|
|
647
|
-
###
|
|
648
|
-
|
|
|
649
|
-
|
|
650
|
-
| API-001 | POST | /api/xxx |
|
|
651
|
-
| API-010 | GET | /api/yyy |
|
|
656
|
+
### 请求耗时统计(§5.22 分层耗时)
|
|
657
|
+
| 层级 | 场景/批次 | 方法 | URL | 耗时 | 状态 |
|
|
658
|
+
|------|----------|:----:|-----|----:|:----:|
|
|
659
|
+
| request | API-001 | POST | /api/xxx | 245ms | ✅ |
|
|
660
|
+
| request | API-010 | GET | /api/yyy | 12450ms | 🟡SLOW |
|
|
661
|
+
| batch | pytest-batch-1 | — | — | 158000ms | 🟡BATCH_SLOW |
|
|
662
|
+
|
|
663
|
+
> **§5.22 分层耗时说明**:
|
|
664
|
+
> - `request` 层级记录单 HTTP 请求耗时(`requestDurationMs`)
|
|
665
|
+
> - `batch` 层级记录 runner wall-clock(`batchDurationMs`),整个 pytest 批次耗时
|
|
666
|
+
> - 聚合合同套件场景(如 API-C01~C04 共享同一批次)只记录一行 `batch` 条目 + `coveredTests` 列表,**不得均摊为 4 个伪 `request` 条目**
|
|
652
667
|
|
|
653
668
|
### 数据兼容
|
|
654
669
|
| # | 场景 | 预期 | 实际 | 结果 |
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: harness-submit 的确定性提交协议。用于避免
|
|
2
|
+
description: harness-submit 的确定性提交协议。用于避免 blocking user confirmation 参数错误、中文 commit message 失败、Co-Authored-By 违规和先 commit 后 amend。
|
|
3
3
|
---
|
|
4
4
|
|
|
5
5
|
# Submit Protocol
|
|
@@ -26,6 +26,10 @@ if hasattr(sys.stderr, "reconfigure"):
|
|
|
26
26
|
sys.stderr.reconfigure(encoding="utf-8")
|
|
27
27
|
|
|
28
28
|
INCLUDE_RE = re.compile(r"<!--\s*@include\s+shared/([^\s]+)\s*-->")
|
|
29
|
+
# retro §5.17: wiki links [[shared/xxx.md|alias]] reference shared fragments
|
|
30
|
+
# that are inlined by expand_includes. After expansion, replace the wiki link
|
|
31
|
+
# with the alias text (the shared content is already in the document).
|
|
32
|
+
WIKI_LINK_RE = re.compile(r"\[\[shared/([^\]|]+)\|([^\]]*)\]\]")
|
|
29
33
|
# §7.5: overlay anchors support both new section-id and legacy section:"title".
|
|
30
34
|
OVERRIDE_RE = re.compile(r'<!--\s*@override\s+(?:section-id:"([^"]+)"|section:"([^"]+)")\s*-->')
|
|
31
35
|
APPEND_RE = re.compile(r'<!--\s*@append-after\s+(?:section-id:"([^"]+)"|section:"([^"]+)")\s*-->')
|
|
@@ -148,6 +152,9 @@ def expand_includes(text: str, shared_dir: Path) -> str:
|
|
|
148
152
|
return body
|
|
149
153
|
|
|
150
154
|
expanded = INCLUDE_RE.sub(repl, text)
|
|
155
|
+
# §5.17: replace [[shared/xxx.md|alias]] wiki links with the alias text,
|
|
156
|
+
# since the shared content has been inlined by @include expansion above.
|
|
157
|
+
expanded = WIKI_LINK_RE.sub(lambda m: m.group(2), expanded)
|
|
151
158
|
lines = [ln for ln in expanded.splitlines() if not FRAGMENT_HINT_RE.match(ln)]
|
|
152
159
|
return "\n".join(lines) + ("\n" if text.endswith("\n") else "")
|
|
153
160
|
|
|
@@ -1049,10 +1049,11 @@ class IntegrationTransaction:
|
|
|
1049
1049
|
heavy_removed.append(name)
|
|
1050
1050
|
except OSError as exc:
|
|
1051
1051
|
heavy_failures.append({"path": name, "error": str(exc)})
|
|
1052
|
-
self.runner.run(
|
|
1052
|
+
remove_result = self.runner.run(
|
|
1053
1053
|
self.project_root, "worktree", "remove", "--force", str(resolved),
|
|
1054
1054
|
check=False,
|
|
1055
1055
|
)
|
|
1056
|
+
remove_returncode = getattr(remove_result, "returncode", 0)
|
|
1056
1057
|
listed = self.runner.text(self.project_root, "worktree", "list", "--porcelain")
|
|
1057
1058
|
registered_after = {
|
|
1058
1059
|
str(Path(line.removeprefix("worktree ")).resolve())
|
|
@@ -1061,7 +1062,8 @@ class IntegrationTransaction:
|
|
|
1061
1062
|
}
|
|
1062
1063
|
still_registered = str(resolved) in registered_after
|
|
1063
1064
|
residual_paths: list[str] = []
|
|
1064
|
-
|
|
1065
|
+
disk_path_present = resolved.exists()
|
|
1066
|
+
if disk_path_present:
|
|
1065
1067
|
if resolved.is_file():
|
|
1066
1068
|
residual_paths.append(resolved.name)
|
|
1067
1069
|
else:
|
|
@@ -1075,6 +1077,27 @@ class IntegrationTransaction:
|
|
|
1075
1077
|
break
|
|
1076
1078
|
if len(residual_paths) >= 200:
|
|
1077
1079
|
break
|
|
1080
|
+
# §5.30: Windows half-success — git worktree remove returned non-zero
|
|
1081
|
+
# but registration is already deleted and disk path remains. Return a
|
|
1082
|
+
# structured status distinct from generic CLEANUP_RESIDUAL so callers
|
|
1083
|
+
# can apply the allowlisted residual cleaner.
|
|
1084
|
+
if (
|
|
1085
|
+
remove_returncode != 0
|
|
1086
|
+
and not still_registered
|
|
1087
|
+
and disk_path_present
|
|
1088
|
+
and residual_paths
|
|
1089
|
+
and not heavy_failures
|
|
1090
|
+
):
|
|
1091
|
+
return {
|
|
1092
|
+
"ok": False,
|
|
1093
|
+
"code": "REGISTRATION_REMOVED_RESIDUAL_PRESENT",
|
|
1094
|
+
"target": str(resolved),
|
|
1095
|
+
"heavyRootsRemoved": heavy_removed,
|
|
1096
|
+
"heavyRootFailures": heavy_failures,
|
|
1097
|
+
"worktreeRegistered": False,
|
|
1098
|
+
"diskPathPresent": True,
|
|
1099
|
+
"residualPaths": residual_paths,
|
|
1100
|
+
}
|
|
1078
1101
|
ok = not heavy_failures and not still_registered and not residual_paths
|
|
1079
1102
|
return {
|
|
1080
1103
|
"ok": ok,
|
|
@@ -1083,6 +1106,7 @@ class IntegrationTransaction:
|
|
|
1083
1106
|
"heavyRootsRemoved": heavy_removed,
|
|
1084
1107
|
"heavyRootFailures": heavy_failures,
|
|
1085
1108
|
"worktreeRegistered": still_registered,
|
|
1109
|
+
"diskPathPresent": disk_path_present,
|
|
1086
1110
|
"residualPaths": residual_paths,
|
|
1087
1111
|
}
|
|
1088
1112
|
|
|
@@ -300,6 +300,152 @@ def _terminal_exists(change_dir: Path, run_id: str, attempt: int) -> bool:
|
|
|
300
300
|
)
|
|
301
301
|
|
|
302
302
|
|
|
303
|
+
def verify_plan(change_dir: Path) -> dict[str, Any]:
|
|
304
|
+
"""Read-only verification of a finalized plan (retro §5.8).
|
|
305
|
+
|
|
306
|
+
Validates the published artifacts, receipt and events stream without
|
|
307
|
+
requiring the original staging directory. Returns the artifact hash,
|
|
308
|
+
phase.end count/status, frontmatter, and consistency checks. Any parse
|
|
309
|
+
error or inconsistency results in ok=false with a non-zero exit code.
|
|
310
|
+
"""
|
|
311
|
+
change_dir = change_dir.resolve()
|
|
312
|
+
receipt_path = change_dir / "meta" / "plan-finalization.json"
|
|
313
|
+
if not receipt_path.is_file():
|
|
314
|
+
return _result_error("RECEIPT_MISSING", f"receipt not found: {receipt_path}")
|
|
315
|
+
|
|
316
|
+
try:
|
|
317
|
+
receipt = json.loads(receipt_path.read_text(encoding="utf-8-sig"))
|
|
318
|
+
except (OSError, json.JSONDecodeError) as exc:
|
|
319
|
+
return _result_error("RECEIPT_INVALID", str(exc))
|
|
320
|
+
if not isinstance(receipt, dict):
|
|
321
|
+
return _result_error("RECEIPT_INVALID", "receipt top-level must be an object")
|
|
322
|
+
|
|
323
|
+
change_name = str(receipt.get("changeName") or "").strip()
|
|
324
|
+
if not change_name:
|
|
325
|
+
return _result_error("RECEIPT_INVALID", "receipt missing changeName")
|
|
326
|
+
|
|
327
|
+
expected_hash = str(receipt.get("artifactsHash") or "").strip()
|
|
328
|
+
if not expected_hash.startswith("sha256:"):
|
|
329
|
+
return _result_error("RECEIPT_INVALID", "receipt artifactsHash missing or malformed")
|
|
330
|
+
|
|
331
|
+
files_list = receipt.get("files")
|
|
332
|
+
if not isinstance(files_list, list) or not files_list:
|
|
333
|
+
return _result_error("RECEIPT_INVALID", "receipt files list missing or empty")
|
|
334
|
+
|
|
335
|
+
# Recompute artifacts hash from published files.
|
|
336
|
+
digest = hashlib.sha256()
|
|
337
|
+
artifact_names: list[str] = []
|
|
338
|
+
for rel_text in files_list:
|
|
339
|
+
rel = rel_text.as_posix() if hasattr(rel_text, "as_posix") else str(rel_text)
|
|
340
|
+
target = change_dir / rel
|
|
341
|
+
if not target.is_file():
|
|
342
|
+
return _result_error(
|
|
343
|
+
"ARTIFACT_MISSING", f"published artifact missing: {rel}"
|
|
344
|
+
)
|
|
345
|
+
artifact_names.append(rel)
|
|
346
|
+
digest.update(rel.encode("utf-8"))
|
|
347
|
+
digest.update(b"\0")
|
|
348
|
+
digest.update(target.read_bytes())
|
|
349
|
+
digest.update(b"\0")
|
|
350
|
+
actual_hash = "sha256:" + digest.hexdigest()
|
|
351
|
+
if actual_hash != expected_hash:
|
|
352
|
+
return _result_error(
|
|
353
|
+
"ARTIFACT_HASH_DRIFT",
|
|
354
|
+
f"published artifacts hash {actual_hash} != receipt {expected_hash}",
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
# Validate frontmatter on markdown artifacts.
|
|
358
|
+
frontmatter_results: dict[str, Any] = {}
|
|
359
|
+
for rel in artifact_names:
|
|
360
|
+
if not rel.endswith(".md"):
|
|
361
|
+
continue
|
|
362
|
+
target = change_dir / rel
|
|
363
|
+
try:
|
|
364
|
+
text = target.read_text(encoding="utf-8-sig")
|
|
365
|
+
except OSError as exc:
|
|
366
|
+
return _result_error("ARTIFACT_UNREADABLE", f"{rel}: {exc}")
|
|
367
|
+
fm = _frontmatter(text)
|
|
368
|
+
if fm is None:
|
|
369
|
+
return _result_error(
|
|
370
|
+
"ARTIFACT_FRONTMATTER_MISSING", f"{rel}: no frontmatter"
|
|
371
|
+
)
|
|
372
|
+
if fm.get("change-name") != change_name:
|
|
373
|
+
return _result_error(
|
|
374
|
+
"ARTIFACT_FRONTMATTER_INVALID",
|
|
375
|
+
f"{rel}: change-name '{fm.get('change-name')}' != '{change_name}'",
|
|
376
|
+
)
|
|
377
|
+
frontmatter_results[rel] = {
|
|
378
|
+
"change-name": fm.get("change-name"),
|
|
379
|
+
"status": fm.get("status"),
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
# Validate gate-policy.json is parseable JSON.
|
|
383
|
+
gate_policy_path = change_dir / "meta" / "gate-policy.json"
|
|
384
|
+
gate_policy_consistent = True
|
|
385
|
+
if gate_policy_path.is_file():
|
|
386
|
+
try:
|
|
387
|
+
json.loads(gate_policy_path.read_text(encoding="utf-8-sig"))
|
|
388
|
+
except (OSError, json.JSONDecodeError):
|
|
389
|
+
gate_policy_consistent = False
|
|
390
|
+
else:
|
|
391
|
+
gate_policy_consistent = False
|
|
392
|
+
|
|
393
|
+
# Validate events.ndjson: count phase.end, check for parse errors.
|
|
394
|
+
events_path = change_dir / "events.ndjson"
|
|
395
|
+
phase_end_count = 0
|
|
396
|
+
phase_end_status: str | None = None
|
|
397
|
+
events_parse_errors: list[str] = []
|
|
398
|
+
if events_path.is_file():
|
|
399
|
+
for line_no, line in enumerate(events_path.read_text(encoding="utf-8").splitlines(), 1):
|
|
400
|
+
if not line.strip():
|
|
401
|
+
continue
|
|
402
|
+
try:
|
|
403
|
+
event = json.loads(line)
|
|
404
|
+
except json.JSONDecodeError as exc:
|
|
405
|
+
events_parse_errors.append(f"line {line_no}: {exc}")
|
|
406
|
+
continue
|
|
407
|
+
if not isinstance(event, dict):
|
|
408
|
+
events_parse_errors.append(f"line {line_no}: not an object")
|
|
409
|
+
continue
|
|
410
|
+
if (
|
|
411
|
+
event.get("phase") == "plan"
|
|
412
|
+
and event.get("type") == "phase.end"
|
|
413
|
+
):
|
|
414
|
+
phase_end_count += 1
|
|
415
|
+
phase_end_status = str(event.get("status") or "").upper()
|
|
416
|
+
else:
|
|
417
|
+
events_parse_errors.append("events.ndjson missing")
|
|
418
|
+
|
|
419
|
+
if events_parse_errors:
|
|
420
|
+
return _result_error(
|
|
421
|
+
"EVENTS_PARSE_ERROR", "; ".join(events_parse_errors[:5])
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
if phase_end_count == 0:
|
|
425
|
+
return _result_error("PHASE_END_MISSING", "no plan phase.end event found")
|
|
426
|
+
if phase_end_count > 1:
|
|
427
|
+
return _result_error(
|
|
428
|
+
"PHASE_END_DUPLICATE", f"found {phase_end_count} phase.end events"
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
# Receipt consistency: status should be finalized.
|
|
432
|
+
receipt_consistent = receipt.get("status") == "finalized"
|
|
433
|
+
|
|
434
|
+
return {
|
|
435
|
+
"ok": True,
|
|
436
|
+
"action": "verify",
|
|
437
|
+
"changeDir": str(change_dir),
|
|
438
|
+
"changeName": change_name,
|
|
439
|
+
"artifactsHash": actual_hash,
|
|
440
|
+
"phaseEndCount": phase_end_count,
|
|
441
|
+
"phaseEndStatus": phase_end_status,
|
|
442
|
+
"frontmatter": frontmatter_results,
|
|
443
|
+
"gatePolicyConsistent": gate_policy_consistent,
|
|
444
|
+
"receiptConsistent": receipt_consistent,
|
|
445
|
+
"files": artifact_names,
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
|
|
303
449
|
def _read_design_capabilities(staging: Path, change_name: str) -> list[str]:
|
|
304
450
|
"""Read capabilities from design frontmatter (retro §5.4)."""
|
|
305
451
|
design_path = staging / "spec" / f"{change_name}-design.md"
|
|
@@ -569,18 +715,24 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
569
715
|
finalize.add_argument("--run-id", required=True)
|
|
570
716
|
finalize.add_argument("--attempt", required=True, type=int)
|
|
571
717
|
finalize.add_argument("--json", action="store_true")
|
|
718
|
+
verify = sub.add_parser("verify")
|
|
719
|
+
verify.add_argument("--change-dir", required=True)
|
|
720
|
+
verify.add_argument("--json", action="store_true")
|
|
572
721
|
return parser
|
|
573
722
|
|
|
574
723
|
|
|
575
724
|
def main(argv: list[str] | None = None) -> int:
|
|
576
725
|
args = build_parser().parse_args(argv)
|
|
577
|
-
|
|
578
|
-
Path(args.change_dir)
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
726
|
+
if args.command == "verify":
|
|
727
|
+
result = verify_plan(Path(args.change_dir))
|
|
728
|
+
else:
|
|
729
|
+
result = finalize_plan(
|
|
730
|
+
Path(args.change_dir),
|
|
731
|
+
Path(args.staging_dir),
|
|
732
|
+
change_name=args.change,
|
|
733
|
+
run_id=args.run_id,
|
|
734
|
+
attempt=args.attempt,
|
|
735
|
+
)
|
|
584
736
|
stream = sys.stdout if result["ok"] else sys.stderr
|
|
585
737
|
if args.json:
|
|
586
738
|
stream.write(json.dumps(result, ensure_ascii=False, indent=2) + "\n")
|
|
@@ -23,10 +23,10 @@ if hasattr(sys.stderr, "reconfigure"):
|
|
|
23
23
|
SCHEMA_VERSION = 1
|
|
24
24
|
_CHANGE_ID = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$")
|
|
25
25
|
_ADAPTERS = {
|
|
26
|
-
"claude-code": (".
|
|
27
|
-
"codex": (".
|
|
28
|
-
"cursor": (".
|
|
29
|
-
"codebuddy": (".
|
|
26
|
+
"claude-code": (".worktrees", "harness/"),
|
|
27
|
+
"codex": (".worktrees", "harness/"),
|
|
28
|
+
"cursor": (".worktrees", "harness/"),
|
|
29
|
+
"codebuddy": (".worktrees", "harness/"),
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
|
|
@@ -164,6 +164,7 @@ def get_service_start(profile: dict[str, Any]) -> dict[str, Any]:
|
|
|
164
164
|
# runtime resolve 把具体 overlay/profile 注入到 session,不写回持久 profile
|
|
165
165
|
# (spec §3.1 持久 profile 只保存模板;§3.4 修复输入端陈旧 profile)。
|
|
166
166
|
STALE_WORKTREE_MARKERS: tuple[str, ...] = (
|
|
167
|
+
".worktrees/",
|
|
167
168
|
".claude/worktrees/",
|
|
168
169
|
".cursor/worktrees/",
|
|
169
170
|
".codeium/worktrees/",
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Controlled cleanup helper for test fixtures (retro §5.23).
|
|
3
|
+
|
|
4
|
+
Provides a host-policy-friendly entry point for removing gitignored test
|
|
5
|
+
fixtures (e.g. .pytest_data) within an execution root. Performs realpath
|
|
6
|
+
containment, rejects symlink/reparse escapes, lists exact counts before
|
|
7
|
+
deletion, and outputs a structured receipt.
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import argparse
|
|
12
|
+
import json
|
|
13
|
+
import os
|
|
14
|
+
import shutil
|
|
15
|
+
import sys
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
if hasattr(sys.stdout, "reconfigure"):
|
|
20
|
+
sys.stdout.reconfigure(encoding="utf-8")
|
|
21
|
+
if hasattr(sys.stderr, "reconfigure"):
|
|
22
|
+
sys.stderr.reconfigure(encoding="utf-8")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _resolve_within(execution_root: Path, rel: str) -> Path | None:
|
|
26
|
+
"""Resolve rel under execution_root, rejecting escapes and symlinks."""
|
|
27
|
+
if not rel or rel.startswith("/"):
|
|
28
|
+
return None
|
|
29
|
+
# Reject absolute paths (Windows drive letter or POSIX).
|
|
30
|
+
if len(rel) >= 2 and rel[1] == ":":
|
|
31
|
+
return None
|
|
32
|
+
parts = rel.replace("\\", "/").split("/")
|
|
33
|
+
if ".." in parts:
|
|
34
|
+
return None
|
|
35
|
+
candidate = (execution_root / rel).resolve(strict=False)
|
|
36
|
+
# Containment check: candidate must be under execution_root.
|
|
37
|
+
try:
|
|
38
|
+
candidate.relative_to(execution_root.resolve(strict=False))
|
|
39
|
+
except ValueError:
|
|
40
|
+
return None
|
|
41
|
+
# Reject symlink/reparse escape: if the path is a symlink, check its target.
|
|
42
|
+
if candidate.is_symlink():
|
|
43
|
+
target = Path(os.readlink(candidate)).resolve(strict=False)
|
|
44
|
+
try:
|
|
45
|
+
target.relative_to(execution_root.resolve(strict=False))
|
|
46
|
+
except ValueError:
|
|
47
|
+
return None
|
|
48
|
+
# Even if target is inside, a symlinked cleanup root is suspicious;
|
|
49
|
+
# reject to avoid following links into unexpected locations.
|
|
50
|
+
return None
|
|
51
|
+
return candidate
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _count_files(root: Path) -> tuple[int, int]:
|
|
55
|
+
"""Count files and total bytes under root."""
|
|
56
|
+
count = 0
|
|
57
|
+
total = 0
|
|
58
|
+
for dirpath, _dirs, files in os.walk(root):
|
|
59
|
+
for name in files:
|
|
60
|
+
fp = Path(dirpath) / name
|
|
61
|
+
try:
|
|
62
|
+
total += fp.stat().st_size
|
|
63
|
+
count += 1
|
|
64
|
+
except OSError:
|
|
65
|
+
continue
|
|
66
|
+
return count, total
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def cleanup(execution_root: Path, cleanup_roots: list[str]) -> dict[str, Any]:
|
|
70
|
+
"""Remove allowlisted cleanup roots within execution_root.
|
|
71
|
+
|
|
72
|
+
Returns a structured receipt. Any path escape, symlink escape, or
|
|
73
|
+
unallowed root results in ok=false with zero files removed.
|
|
74
|
+
"""
|
|
75
|
+
execution_root = Path(execution_root).resolve(strict=False)
|
|
76
|
+
if not execution_root.is_dir():
|
|
77
|
+
return {
|
|
78
|
+
"ok": False,
|
|
79
|
+
"code": "EXECUTION_ROOT_MISSING",
|
|
80
|
+
"executionRoot": str(execution_root),
|
|
81
|
+
"removedFiles": 0,
|
|
82
|
+
"removedBytes": 0,
|
|
83
|
+
"rejectedEscapes": [],
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
rejected: list[dict[str, str]] = []
|
|
87
|
+
removed_files = 0
|
|
88
|
+
removed_bytes = 0
|
|
89
|
+
cleaned: list[str] = []
|
|
90
|
+
|
|
91
|
+
for rel in cleanup_roots:
|
|
92
|
+
target = _resolve_within(execution_root, rel)
|
|
93
|
+
if target is None:
|
|
94
|
+
rejected.append({"path": rel, "reason": "PATH_ESCAPE_REJECTED"})
|
|
95
|
+
continue
|
|
96
|
+
if not target.exists():
|
|
97
|
+
# Already absent — idempotent reentry.
|
|
98
|
+
continue
|
|
99
|
+
# Re-check symlink after resolution (the parent might be a symlink).
|
|
100
|
+
if target.is_symlink():
|
|
101
|
+
rejected.append({"path": rel, "reason": "SYMLINK_ESCAPE_REJECTED"})
|
|
102
|
+
continue
|
|
103
|
+
# Count before deletion for the receipt.
|
|
104
|
+
try:
|
|
105
|
+
count, total = _count_files(target)
|
|
106
|
+
except OSError:
|
|
107
|
+
count, total = 0, 0
|
|
108
|
+
# Delete.
|
|
109
|
+
try:
|
|
110
|
+
shutil.rmtree(target)
|
|
111
|
+
except OSError as exc:
|
|
112
|
+
rejected.append({"path": rel, "reason": f"DELETE_FAILED: {exc}"})
|
|
113
|
+
continue
|
|
114
|
+
removed_files += count
|
|
115
|
+
removed_bytes += total
|
|
116
|
+
cleaned.append(rel)
|
|
117
|
+
|
|
118
|
+
if rejected:
|
|
119
|
+
return {
|
|
120
|
+
"ok": False,
|
|
121
|
+
"code": rejected[0]["reason"],
|
|
122
|
+
"executionRoot": str(execution_root),
|
|
123
|
+
"removedFiles": removed_files,
|
|
124
|
+
"removedBytes": removed_bytes,
|
|
125
|
+
"rejectedEscapes": rejected,
|
|
126
|
+
"cleaned": cleaned,
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if removed_files == 0 and not cleaned:
|
|
130
|
+
return {
|
|
131
|
+
"ok": True,
|
|
132
|
+
"code": "ALREADY_ABSENT",
|
|
133
|
+
"executionRoot": str(execution_root),
|
|
134
|
+
"removedFiles": 0,
|
|
135
|
+
"removedBytes": 0,
|
|
136
|
+
"rejectedEscapes": [],
|
|
137
|
+
"cleaned": [],
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
"ok": True,
|
|
142
|
+
"code": "CLEANUP_COMPLETE",
|
|
143
|
+
"executionRoot": str(execution_root),
|
|
144
|
+
"removedFiles": removed_files,
|
|
145
|
+
"removedBytes": removed_bytes,
|
|
146
|
+
"rejectedEscapes": [],
|
|
147
|
+
"cleaned": cleaned,
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def main(argv: list[str] | None = None) -> int:
|
|
152
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
153
|
+
sub = parser.add_subparsers(dest="action", required=True)
|
|
154
|
+
clean_parser = sub.add_parser("cleanup")
|
|
155
|
+
clean_parser.add_argument("--execution-root", required=True)
|
|
156
|
+
clean_parser.add_argument("--cleanup-roots", required=True, help="JSON array of relative paths")
|
|
157
|
+
clean_parser.add_argument("--json", action="store_true")
|
|
158
|
+
args = parser.parse_args(argv)
|
|
159
|
+
|
|
160
|
+
try:
|
|
161
|
+
roots = json.loads(args.cleanup_roots)
|
|
162
|
+
if not isinstance(roots, list):
|
|
163
|
+
raise ValueError("cleanup-roots must be a JSON array")
|
|
164
|
+
except (json.JSONDecodeError, ValueError) as exc:
|
|
165
|
+
result = {"ok": False, "code": "INVALID_CLEANUP_ROOTS", "error": str(exc)}
|
|
166
|
+
print(json.dumps(result, ensure_ascii=False, indent=2))
|
|
167
|
+
return 1
|
|
168
|
+
|
|
169
|
+
result = cleanup(Path(args.execution_root), roots)
|
|
170
|
+
print(json.dumps(result, ensure_ascii=False, indent=2 if args.json else None))
|
|
171
|
+
return 0 if result["ok"] else 1
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
if __name__ == "__main__":
|
|
175
|
+
raise SystemExit(main())
|
|
@@ -53,9 +53,22 @@ _Avoid_: 仅凭 diffHash 复用, 无证据跳过
|
|
|
53
53
|
_Avoid_: 每轮测试后必杀服务
|
|
54
54
|
|
|
55
55
|
**交互白名单**:
|
|
56
|
-
每个 skill 显式声明的允许
|
|
56
|
+
每个 skill 显式声明的允许 `blocking user confirmation` 场景清单。白名单外一律取默认值并记 decision 事件。
|
|
57
57
|
_Avoid_: 逐决策点询问
|
|
58
58
|
|
|
59
|
+
**阻断式用户确认(blocking user confirmation)**:
|
|
60
|
+
宿主无关的审批原语。canonical skill 使用此术语表达"展示完整审批包并等待明确答复"的语义,不绑定具体宿主工具。adapter 按下表映射到宿主具体工具:
|
|
61
|
+
|
|
62
|
+
| Adapter | 工具 |
|
|
63
|
+
|---------|------|
|
|
64
|
+
| claude-code | AskUserQuestion |
|
|
65
|
+
| codebuddy | AskUserQuestion |
|
|
66
|
+
| codex | request_user_input 或普通对话 |
|
|
67
|
+
| cursor | 普通对话 |
|
|
68
|
+
|
|
69
|
+
finalizer 只校验 approval receipt/decision 的顺序和内容,不校验交互工具品牌。
|
|
70
|
+
_Avoid_: 硬编码宿主工具名, 跨宿主不可移植
|
|
71
|
+
|
|
59
72
|
**部署合成**:
|
|
60
73
|
`harness_deploy.py build` 把通用核心 + shared 片段 + overlay 合成为自包含 SKILL.md 后部署。Vault 源头 DRY,运行时单文件。
|
|
61
74
|
_Avoid_: 运行时跨文件拼规则, fork 维护
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
| harness-knowledge-query | `[Bash(powershell.exe:*), Read, Write, Edit, Glob, Grep]` | 同上 |
|
|
60
60
|
| harness-knowledge-ingest | `[Bash(powershell.exe:*), Read, Write, Edit, Glob, Grep]` | 同上 |
|
|
61
61
|
|
|
62
|
-
> **
|
|
62
|
+
> **blocking user confirmation 不预批准**:各 skill 需要向用户确认时通过普通权限提示调用 `blocking user confirmation`(adapter 映射到 `AskUserQuestion`/`request_user_input`/普通对话),不在 `allowed-tools` 中预批准(遵循 skill-optimizer 规则)。`harness-codebase-map` 额外预批准 `Agent` 用于派发并行 mapper。
|
|
63
63
|
|
|
64
64
|
> **codegraph 命令**:原 `Bash(codegraph *)` 改为 MCP 工具调用(`mcp__codegraph__codegraph_explore` 等),不再通过 Bash。
|
|
65
65
|
>
|