@hunter-harness/workflow-harness 0.2.8 → 0.2.10
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 +3 -3
- package/harness/bundles/general/claude-code/harness-archive/SKILL.md +3 -3
- package/harness/bundles/general/claude-code/harness-archive/reference.md +30 -12
- package/harness/bundles/general/claude-code/harness-archive/templates/render-summary.mjs +5 -1
- package/harness/bundles/general/claude-code/harness-archive/templates/summary-data-template.json +6 -2
- package/harness/bundles/general/claude-code/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-knowledge-ingest/SKILL.md +3 -1
- package/harness/bundles/general/claude-code/harness-knowledge-ingest/reference.md +7 -0
- package/harness/bundles/general/claude-code/harness-knowledge-ingest/scripts/harness_knowledge.py +117 -0
- package/harness/bundles/general/claude-code/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-plan/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-plan/checklist.md +6 -0
- package/harness/bundles/general/claude-code/harness-review/SKILL.md +3 -3
- package/harness/bundles/general/claude-code/harness-run/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-run/checklist.md +5 -0
- package/harness/bundles/general/claude-code/harness-run/reference.md +33 -1
- package/harness/bundles/general/claude-code/harness-submit/SKILL.md +15 -10
- package/harness/bundles/general/claude-code/harness-submit/checklist.md +40 -76
- package/harness/bundles/general/claude-code/harness-sync/SKILL.md +17 -2
- package/harness/bundles/general/claude-code/harness-sync/reference.md +2 -2
- package/harness/bundles/general/claude-code/harness-test/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-test/checklist.md +9 -0
- package/harness/bundles/general/claude-code/harness-test/pitfalls-java.md +14 -0
- package/harness/bundles/general/claude-code/harness-test/reference.md +38 -0
- package/harness/bundles/general/claude-code/protocols/archive-report-protocol.md +26 -4
- package/harness/bundles/general/claude-code/protocols/ledger-protocol.md +38 -0
- package/harness/bundles/general/claude-code/protocols/powershell-protocol.md +16 -0
- package/harness/bundles/general/claude-code/protocols/report-pipeline-protocol.md +17 -0
- package/harness/bundles/general/claude-code/protocols/state-layout-protocol.md +15 -0
- package/harness/bundles/general/claude-code/scripts/harness_archive.py +1209 -170
- package/harness/bundles/general/claude-code/scripts/harness_change.py +18 -0
- package/harness/bundles/general/claude-code/scripts/harness_deploy.py +1 -44
- package/harness/bundles/general/claude-code/scripts/harness_events.py +174 -25
- package/harness/bundles/general/claude-code/scripts/harness_gate.py +203 -29
- package/harness/bundles/general/claude-code/scripts/harness_integration.py +878 -0
- package/harness/bundles/general/claude-code/scripts/harness_ledger.py +457 -10
- package/harness/bundles/general/claude-code/scripts/harness_paths.py +315 -0
- package/harness/bundles/general/claude-code/scripts/harness_profile.py +45 -0
- package/harness/bundles/general/claude-code/scripts/harness_review.py +326 -0
- package/harness/bundles/general/claude-code/scripts/harness_sync.py +407 -0
- package/harness/bundles/general/claude-code/scripts/harness_test_guard.py +244 -49
- package/harness/bundles/general/codebuddy/.harness-build.json +1 -1
- package/harness/bundles/general/codebuddy/contracts/workflow-policy.json +3 -3
- package/harness/bundles/general/codebuddy/harness-archive/SKILL.md +3 -3
- package/harness/bundles/general/codebuddy/harness-archive/reference.md +30 -12
- package/harness/bundles/general/codebuddy/harness-archive/templates/render-summary.mjs +5 -1
- package/harness/bundles/general/codebuddy/harness-archive/templates/summary-data-template.json +6 -2
- package/harness/bundles/general/codebuddy/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-knowledge-ingest/SKILL.md +3 -1
- package/harness/bundles/general/codebuddy/harness-knowledge-ingest/reference.md +7 -0
- package/harness/bundles/general/codebuddy/harness-knowledge-ingest/scripts/harness_knowledge.py +117 -0
- package/harness/bundles/general/codebuddy/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-plan/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-plan/checklist.md +6 -0
- package/harness/bundles/general/codebuddy/harness-review/SKILL.md +3 -3
- package/harness/bundles/general/codebuddy/harness-run/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-run/checklist.md +5 -0
- package/harness/bundles/general/codebuddy/harness-run/reference.md +33 -1
- package/harness/bundles/general/codebuddy/harness-submit/SKILL.md +15 -10
- package/harness/bundles/general/codebuddy/harness-submit/checklist.md +40 -76
- package/harness/bundles/general/codebuddy/harness-sync/SKILL.md +17 -2
- package/harness/bundles/general/codebuddy/harness-sync/reference.md +2 -2
- package/harness/bundles/general/codebuddy/harness-test/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-test/checklist.md +9 -0
- package/harness/bundles/general/codebuddy/harness-test/pitfalls-java.md +14 -0
- package/harness/bundles/general/codebuddy/harness-test/reference.md +38 -0
- package/harness/bundles/general/codebuddy/protocols/archive-report-protocol.md +26 -4
- package/harness/bundles/general/codebuddy/protocols/ledger-protocol.md +38 -0
- package/harness/bundles/general/codebuddy/protocols/powershell-protocol.md +16 -0
- package/harness/bundles/general/codebuddy/protocols/report-pipeline-protocol.md +17 -0
- package/harness/bundles/general/codebuddy/protocols/state-layout-protocol.md +15 -0
- package/harness/bundles/general/codebuddy/scripts/harness_archive.py +1209 -170
- package/harness/bundles/general/codebuddy/scripts/harness_change.py +18 -0
- package/harness/bundles/general/codebuddy/scripts/harness_deploy.py +1 -44
- package/harness/bundles/general/codebuddy/scripts/harness_events.py +174 -25
- package/harness/bundles/general/codebuddy/scripts/harness_gate.py +203 -29
- package/harness/bundles/general/codebuddy/scripts/harness_integration.py +878 -0
- package/harness/bundles/general/codebuddy/scripts/harness_ledger.py +457 -10
- package/harness/bundles/general/codebuddy/scripts/harness_paths.py +315 -0
- package/harness/bundles/general/codebuddy/scripts/harness_profile.py +45 -0
- package/harness/bundles/general/codebuddy/scripts/harness_review.py +326 -0
- package/harness/bundles/general/codebuddy/scripts/harness_sync.py +407 -0
- package/harness/bundles/general/codebuddy/scripts/harness_test_guard.py +244 -49
- package/harness/bundles/general/codex/.harness-build.json +1 -1
- package/harness/bundles/general/codex/contracts/workflow-policy.json +3 -3
- package/harness/bundles/general/codex/harness-archive/SKILL.md +3 -3
- package/harness/bundles/general/codex/harness-archive/reference.md +30 -12
- package/harness/bundles/general/codex/harness-archive/templates/render-summary.mjs +5 -1
- package/harness/bundles/general/codex/harness-archive/templates/summary-data-template.json +6 -2
- package/harness/bundles/general/codex/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-knowledge-ingest/SKILL.md +3 -1
- package/harness/bundles/general/codex/harness-knowledge-ingest/reference.md +7 -0
- package/harness/bundles/general/codex/harness-knowledge-ingest/scripts/harness_knowledge.py +117 -0
- package/harness/bundles/general/codex/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-plan/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-plan/checklist.md +6 -0
- package/harness/bundles/general/codex/harness-review/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-run/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-run/checklist.md +5 -0
- package/harness/bundles/general/codex/harness-run/reference.md +33 -1
- package/harness/bundles/general/codex/harness-submit/SKILL.md +15 -10
- package/harness/bundles/general/codex/harness-submit/checklist.md +40 -76
- package/harness/bundles/general/codex/harness-sync/SKILL.md +17 -2
- package/harness/bundles/general/codex/harness-sync/reference.md +2 -2
- package/harness/bundles/general/codex/harness-test/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-test/checklist.md +9 -0
- package/harness/bundles/general/codex/harness-test/pitfalls-java.md +14 -0
- package/harness/bundles/general/codex/harness-test/reference.md +38 -0
- package/harness/bundles/general/codex/protocols/archive-report-protocol.md +26 -4
- package/harness/bundles/general/codex/protocols/ledger-protocol.md +38 -0
- package/harness/bundles/general/codex/protocols/powershell-protocol.md +16 -0
- package/harness/bundles/general/codex/protocols/report-pipeline-protocol.md +17 -0
- package/harness/bundles/general/codex/protocols/state-layout-protocol.md +15 -0
- package/harness/bundles/general/codex/scripts/harness_archive.py +1209 -170
- package/harness/bundles/general/codex/scripts/harness_change.py +18 -0
- package/harness/bundles/general/codex/scripts/harness_deploy.py +1 -44
- package/harness/bundles/general/codex/scripts/harness_events.py +174 -25
- package/harness/bundles/general/codex/scripts/harness_gate.py +203 -29
- package/harness/bundles/general/codex/scripts/harness_integration.py +878 -0
- package/harness/bundles/general/codex/scripts/harness_ledger.py +457 -10
- package/harness/bundles/general/codex/scripts/harness_paths.py +315 -0
- package/harness/bundles/general/codex/scripts/harness_profile.py +45 -0
- package/harness/bundles/general/codex/scripts/harness_review.py +326 -0
- package/harness/bundles/general/codex/scripts/harness_sync.py +407 -0
- package/harness/bundles/general/codex/scripts/harness_test_guard.py +244 -49
- package/harness/bundles/general/cursor/.harness-build.json +1 -1
- package/harness/bundles/general/cursor/contracts/workflow-policy.json +3 -3
- package/harness/bundles/general/cursor/harness-archive/SKILL.md +3 -3
- package/harness/bundles/general/cursor/harness-archive/reference.md +30 -12
- package/harness/bundles/general/cursor/harness-archive/templates/render-summary.mjs +5 -1
- package/harness/bundles/general/cursor/harness-archive/templates/summary-data-template.json +6 -2
- package/harness/bundles/general/cursor/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-knowledge-ingest/SKILL.md +3 -1
- package/harness/bundles/general/cursor/harness-knowledge-ingest/reference.md +7 -0
- package/harness/bundles/general/cursor/harness-knowledge-ingest/scripts/harness_knowledge.py +117 -0
- package/harness/bundles/general/cursor/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-plan/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-plan/checklist.md +6 -0
- package/harness/bundles/general/cursor/harness-review/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-run/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-run/checklist.md +5 -0
- package/harness/bundles/general/cursor/harness-run/reference.md +33 -1
- package/harness/bundles/general/cursor/harness-submit/SKILL.md +15 -10
- package/harness/bundles/general/cursor/harness-submit/checklist.md +40 -76
- package/harness/bundles/general/cursor/harness-sync/SKILL.md +17 -2
- package/harness/bundles/general/cursor/harness-sync/reference.md +2 -2
- package/harness/bundles/general/cursor/harness-test/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-test/checklist.md +9 -0
- package/harness/bundles/general/cursor/harness-test/pitfalls-java.md +14 -0
- package/harness/bundles/general/cursor/harness-test/reference.md +38 -0
- package/harness/bundles/general/cursor/protocols/archive-report-protocol.md +26 -4
- package/harness/bundles/general/cursor/protocols/ledger-protocol.md +38 -0
- package/harness/bundles/general/cursor/protocols/powershell-protocol.md +16 -0
- package/harness/bundles/general/cursor/protocols/report-pipeline-protocol.md +17 -0
- package/harness/bundles/general/cursor/protocols/state-layout-protocol.md +15 -0
- package/harness/bundles/general/cursor/scripts/harness_archive.py +1209 -170
- package/harness/bundles/general/cursor/scripts/harness_change.py +18 -0
- package/harness/bundles/general/cursor/scripts/harness_deploy.py +1 -44
- package/harness/bundles/general/cursor/scripts/harness_events.py +174 -25
- package/harness/bundles/general/cursor/scripts/harness_gate.py +203 -29
- package/harness/bundles/general/cursor/scripts/harness_integration.py +878 -0
- package/harness/bundles/general/cursor/scripts/harness_ledger.py +457 -10
- package/harness/bundles/general/cursor/scripts/harness_paths.py +315 -0
- package/harness/bundles/general/cursor/scripts/harness_profile.py +45 -0
- package/harness/bundles/general/cursor/scripts/harness_review.py +326 -0
- package/harness/bundles/general/cursor/scripts/harness_sync.py +407 -0
- package/harness/bundles/general/cursor/scripts/harness_test_guard.py +244 -49
- package/harness/bundles/java/claude-code/.harness-build.json +1 -1
- package/harness/bundles/java/claude-code/contracts/workflow-policy.json +3 -3
- package/harness/bundles/java/claude-code/harness-apidoc/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-archive/SKILL.md +3 -3
- package/harness/bundles/java/claude-code/harness-archive/reference.md +30 -12
- package/harness/bundles/java/claude-code/harness-archive/templates/render-summary.mjs +5 -1
- package/harness/bundles/java/claude-code/harness-archive/templates/summary-data-template.json +6 -2
- package/harness/bundles/java/claude-code/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-knowledge-ingest/SKILL.md +3 -1
- package/harness/bundles/java/claude-code/harness-knowledge-ingest/reference.md +7 -0
- package/harness/bundles/java/claude-code/harness-knowledge-ingest/scripts/harness_knowledge.py +117 -0
- package/harness/bundles/java/claude-code/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-package/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-plan/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-plan/checklist.md +6 -0
- package/harness/bundles/java/claude-code/harness-review/SKILL.md +3 -3
- package/harness/bundles/java/claude-code/harness-run/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-submit/SKILL.md +15 -10
- package/harness/bundles/java/claude-code/harness-submit/checklist.md +40 -76
- package/harness/bundles/java/claude-code/harness-sync/SKILL.md +17 -2
- package/harness/bundles/java/claude-code/harness-sync/reference.md +2 -2
- package/harness/bundles/java/claude-code/harness-test/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/protocols/archive-report-protocol.md +26 -4
- package/harness/bundles/java/claude-code/protocols/ledger-protocol.md +38 -0
- package/harness/bundles/java/claude-code/protocols/powershell-protocol.md +16 -0
- package/harness/bundles/java/claude-code/protocols/report-pipeline-protocol.md +17 -0
- package/harness/bundles/java/claude-code/protocols/state-layout-protocol.md +15 -0
- package/harness/bundles/java/claude-code/scripts/harness_archive.py +1209 -170
- package/harness/bundles/java/claude-code/scripts/harness_change.py +18 -0
- package/harness/bundles/java/claude-code/scripts/harness_deploy.py +1 -44
- package/harness/bundles/java/claude-code/scripts/harness_events.py +174 -25
- package/harness/bundles/java/claude-code/scripts/harness_gate.py +203 -29
- package/harness/bundles/java/claude-code/scripts/harness_integration.py +878 -0
- package/harness/bundles/java/claude-code/scripts/harness_ledger.py +457 -10
- package/harness/bundles/java/claude-code/scripts/harness_paths.py +315 -0
- package/harness/bundles/java/claude-code/scripts/harness_profile.py +45 -0
- package/harness/bundles/java/claude-code/scripts/harness_review.py +326 -0
- package/harness/bundles/java/claude-code/scripts/harness_sync.py +407 -0
- package/harness/bundles/java/claude-code/scripts/harness_test_guard.py +244 -49
- package/harness/bundles/java/codebuddy/.harness-build.json +1 -1
- package/harness/bundles/java/codebuddy/contracts/workflow-policy.json +3 -3
- package/harness/bundles/java/codebuddy/harness-apidoc/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-archive/SKILL.md +3 -3
- package/harness/bundles/java/codebuddy/harness-archive/reference.md +30 -12
- package/harness/bundles/java/codebuddy/harness-archive/templates/render-summary.mjs +5 -1
- package/harness/bundles/java/codebuddy/harness-archive/templates/summary-data-template.json +6 -2
- package/harness/bundles/java/codebuddy/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-knowledge-ingest/SKILL.md +3 -1
- package/harness/bundles/java/codebuddy/harness-knowledge-ingest/reference.md +7 -0
- package/harness/bundles/java/codebuddy/harness-knowledge-ingest/scripts/harness_knowledge.py +117 -0
- package/harness/bundles/java/codebuddy/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-package/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-plan/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-plan/checklist.md +6 -0
- package/harness/bundles/java/codebuddy/harness-review/SKILL.md +3 -3
- package/harness/bundles/java/codebuddy/harness-run/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-submit/SKILL.md +15 -10
- package/harness/bundles/java/codebuddy/harness-submit/checklist.md +40 -76
- package/harness/bundles/java/codebuddy/harness-sync/SKILL.md +17 -2
- package/harness/bundles/java/codebuddy/harness-sync/reference.md +2 -2
- package/harness/bundles/java/codebuddy/harness-test/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/protocols/archive-report-protocol.md +26 -4
- package/harness/bundles/java/codebuddy/protocols/ledger-protocol.md +38 -0
- package/harness/bundles/java/codebuddy/protocols/powershell-protocol.md +16 -0
- package/harness/bundles/java/codebuddy/protocols/report-pipeline-protocol.md +17 -0
- package/harness/bundles/java/codebuddy/protocols/state-layout-protocol.md +15 -0
- package/harness/bundles/java/codebuddy/scripts/harness_archive.py +1209 -170
- package/harness/bundles/java/codebuddy/scripts/harness_change.py +18 -0
- package/harness/bundles/java/codebuddy/scripts/harness_deploy.py +1 -44
- package/harness/bundles/java/codebuddy/scripts/harness_events.py +174 -25
- package/harness/bundles/java/codebuddy/scripts/harness_gate.py +203 -29
- package/harness/bundles/java/codebuddy/scripts/harness_integration.py +878 -0
- package/harness/bundles/java/codebuddy/scripts/harness_ledger.py +457 -10
- package/harness/bundles/java/codebuddy/scripts/harness_paths.py +315 -0
- package/harness/bundles/java/codebuddy/scripts/harness_profile.py +45 -0
- package/harness/bundles/java/codebuddy/scripts/harness_review.py +326 -0
- package/harness/bundles/java/codebuddy/scripts/harness_sync.py +407 -0
- package/harness/bundles/java/codebuddy/scripts/harness_test_guard.py +244 -49
- package/harness/bundles/java/codex/.harness-build.json +1 -1
- package/harness/bundles/java/codex/contracts/workflow-policy.json +3 -3
- package/harness/bundles/java/codex/harness-apidoc/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-archive/SKILL.md +3 -3
- package/harness/bundles/java/codex/harness-archive/reference.md +30 -12
- package/harness/bundles/java/codex/harness-archive/templates/render-summary.mjs +5 -1
- package/harness/bundles/java/codex/harness-archive/templates/summary-data-template.json +6 -2
- package/harness/bundles/java/codex/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-knowledge-ingest/SKILL.md +3 -1
- package/harness/bundles/java/codex/harness-knowledge-ingest/reference.md +7 -0
- package/harness/bundles/java/codex/harness-knowledge-ingest/scripts/harness_knowledge.py +117 -0
- package/harness/bundles/java/codex/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-package/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-plan/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-plan/checklist.md +6 -0
- package/harness/bundles/java/codex/harness-review/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-run/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-submit/SKILL.md +15 -10
- package/harness/bundles/java/codex/harness-submit/checklist.md +40 -76
- package/harness/bundles/java/codex/harness-sync/SKILL.md +17 -2
- package/harness/bundles/java/codex/harness-sync/reference.md +2 -2
- package/harness/bundles/java/codex/harness-test/SKILL.md +1 -1
- package/harness/bundles/java/codex/protocols/archive-report-protocol.md +26 -4
- package/harness/bundles/java/codex/protocols/ledger-protocol.md +38 -0
- package/harness/bundles/java/codex/protocols/powershell-protocol.md +16 -0
- package/harness/bundles/java/codex/protocols/report-pipeline-protocol.md +17 -0
- package/harness/bundles/java/codex/protocols/state-layout-protocol.md +15 -0
- package/harness/bundles/java/codex/scripts/harness_archive.py +1209 -170
- package/harness/bundles/java/codex/scripts/harness_change.py +18 -0
- package/harness/bundles/java/codex/scripts/harness_deploy.py +1 -44
- package/harness/bundles/java/codex/scripts/harness_events.py +174 -25
- package/harness/bundles/java/codex/scripts/harness_gate.py +203 -29
- package/harness/bundles/java/codex/scripts/harness_integration.py +878 -0
- package/harness/bundles/java/codex/scripts/harness_ledger.py +457 -10
- package/harness/bundles/java/codex/scripts/harness_paths.py +315 -0
- package/harness/bundles/java/codex/scripts/harness_profile.py +45 -0
- package/harness/bundles/java/codex/scripts/harness_review.py +326 -0
- package/harness/bundles/java/codex/scripts/harness_sync.py +407 -0
- package/harness/bundles/java/codex/scripts/harness_test_guard.py +244 -49
- package/harness/bundles/java/cursor/.harness-build.json +1 -1
- package/harness/bundles/java/cursor/contracts/workflow-policy.json +3 -3
- package/harness/bundles/java/cursor/harness-apidoc/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-archive/SKILL.md +3 -3
- package/harness/bundles/java/cursor/harness-archive/reference.md +30 -12
- package/harness/bundles/java/cursor/harness-archive/templates/render-summary.mjs +5 -1
- package/harness/bundles/java/cursor/harness-archive/templates/summary-data-template.json +6 -2
- package/harness/bundles/java/cursor/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-knowledge-ingest/SKILL.md +3 -1
- package/harness/bundles/java/cursor/harness-knowledge-ingest/reference.md +7 -0
- package/harness/bundles/java/cursor/harness-knowledge-ingest/scripts/harness_knowledge.py +117 -0
- package/harness/bundles/java/cursor/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-package/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-plan/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-plan/checklist.md +6 -0
- package/harness/bundles/java/cursor/harness-review/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-run/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-submit/SKILL.md +15 -10
- package/harness/bundles/java/cursor/harness-submit/checklist.md +40 -76
- package/harness/bundles/java/cursor/harness-sync/SKILL.md +17 -2
- package/harness/bundles/java/cursor/harness-sync/reference.md +2 -2
- package/harness/bundles/java/cursor/harness-test/SKILL.md +1 -1
- package/harness/bundles/java/cursor/protocols/archive-report-protocol.md +26 -4
- package/harness/bundles/java/cursor/protocols/ledger-protocol.md +38 -0
- package/harness/bundles/java/cursor/protocols/powershell-protocol.md +16 -0
- package/harness/bundles/java/cursor/protocols/report-pipeline-protocol.md +17 -0
- package/harness/bundles/java/cursor/protocols/state-layout-protocol.md +15 -0
- package/harness/bundles/java/cursor/scripts/harness_archive.py +1209 -170
- package/harness/bundles/java/cursor/scripts/harness_change.py +18 -0
- package/harness/bundles/java/cursor/scripts/harness_deploy.py +1 -44
- package/harness/bundles/java/cursor/scripts/harness_events.py +174 -25
- package/harness/bundles/java/cursor/scripts/harness_gate.py +203 -29
- package/harness/bundles/java/cursor/scripts/harness_integration.py +878 -0
- package/harness/bundles/java/cursor/scripts/harness_ledger.py +457 -10
- package/harness/bundles/java/cursor/scripts/harness_paths.py +315 -0
- package/harness/bundles/java/cursor/scripts/harness_profile.py +45 -0
- package/harness/bundles/java/cursor/scripts/harness_review.py +326 -0
- package/harness/bundles/java/cursor/scripts/harness_sync.py +407 -0
- package/harness/bundles/java/cursor/scripts/harness_test_guard.py +244 -49
- package/harness/manifests/general/claude-code.json +58 -38
- package/harness/manifests/general/codebuddy.json +58 -38
- package/harness/manifests/general/codex.json +58 -38
- package/harness/manifests/general/cursor.json +58 -38
- package/harness/manifests/java/claude-code.json +52 -36
- package/harness/manifests/java/codebuddy.json +52 -36
- package/harness/manifests/java/codex.json +52 -36
- package/harness/manifests/java/cursor.json +52 -36
- package/hunter-workflow-family.json +2 -2
- package/package.json +1 -1
|
@@ -15,11 +15,19 @@ import argparse
|
|
|
15
15
|
import datetime as dt
|
|
16
16
|
import hashlib
|
|
17
17
|
import json
|
|
18
|
+
import os
|
|
18
19
|
import subprocess
|
|
19
20
|
import sys
|
|
21
|
+
import uuid
|
|
20
22
|
from pathlib import Path
|
|
21
23
|
from typing import Any
|
|
22
24
|
|
|
25
|
+
SCRIPTS_DIR = Path(__file__).resolve().parent
|
|
26
|
+
if str(SCRIPTS_DIR) not in sys.path:
|
|
27
|
+
sys.path.insert(0, str(SCRIPTS_DIR))
|
|
28
|
+
|
|
29
|
+
import harness_paths # noqa: E402
|
|
30
|
+
|
|
23
31
|
|
|
24
32
|
if hasattr(sys.stdout, "reconfigure"):
|
|
25
33
|
sys.stdout.reconfigure(encoding="utf-8")
|
|
@@ -143,6 +151,22 @@ def _git_bytes(args: list[str], cwd: Path) -> bytes:
|
|
|
143
151
|
return proc.stdout
|
|
144
152
|
|
|
145
153
|
|
|
154
|
+
def _git_text(cwd: Path, *args: str) -> str | None:
|
|
155
|
+
"""Run git, return stripped stdout; None on non-zero exit."""
|
|
156
|
+
proc = subprocess.run(
|
|
157
|
+
["git", *args],
|
|
158
|
+
cwd=str(cwd),
|
|
159
|
+
capture_output=True,
|
|
160
|
+
text=True,
|
|
161
|
+
encoding="utf-8",
|
|
162
|
+
errors="replace",
|
|
163
|
+
check=False,
|
|
164
|
+
)
|
|
165
|
+
if proc.returncode != 0:
|
|
166
|
+
return None
|
|
167
|
+
return proc.stdout.strip()
|
|
168
|
+
|
|
169
|
+
|
|
146
170
|
def _root_commit(repo_root: Path) -> str | None:
|
|
147
171
|
out = _git_bytes(["rev-list", "--max-parents=0", "HEAD"], repo_root)
|
|
148
172
|
lines = [ln for ln in out.decode("utf-8", "replace").splitlines() if ln.strip()]
|
|
@@ -195,10 +219,12 @@ def _tracked_test_contents(
|
|
|
195
219
|
if not candidate.is_absolute():
|
|
196
220
|
candidate = repo_root / candidate
|
|
197
221
|
change_root = candidate.resolve()
|
|
198
|
-
|
|
222
|
+
main_root = harness_paths.resolve_main_project_root(repo_root)
|
|
223
|
+
if not (_inside(change_root, repo_root) or _inside(change_root, main_root)):
|
|
199
224
|
raise ValueError("TEST_TRACKING_CHANGE_DIR_OUTSIDE_PROJECT")
|
|
200
|
-
|
|
201
|
-
|
|
225
|
+
state_root = _state_dir(change_root)
|
|
226
|
+
manifest_path = (state_root / TEST_TRACKING_REL).resolve()
|
|
227
|
+
if not _inside(manifest_path, state_root):
|
|
202
228
|
raise ValueError("TEST_TRACKING_MANIFEST_OUTSIDE_CHANGE")
|
|
203
229
|
if not manifest_path.is_file():
|
|
204
230
|
return {}, None
|
|
@@ -206,6 +232,8 @@ def _tracked_test_contents(
|
|
|
206
232
|
manifest = json.loads(manifest_path.read_text(encoding="utf-8-sig"))
|
|
207
233
|
except (OSError, json.JSONDecodeError) as exc:
|
|
208
234
|
raise ValueError(f"TEST_TRACKING_MANIFEST_INVALID: {exc}") from exc
|
|
235
|
+
if isinstance(manifest, dict) and manifest.get("schemaVersion") == 2:
|
|
236
|
+
return _tracked_test_contents_v2(repo_root, manifest, manifest_path)
|
|
209
237
|
if (
|
|
210
238
|
not isinstance(manifest, dict)
|
|
211
239
|
or manifest.get("schemaVersion") != 1
|
|
@@ -247,6 +275,87 @@ def _tracked_test_contents(
|
|
|
247
275
|
return {rel: contents[rel] for rel in sorted(contents)}, str(manifest_path)
|
|
248
276
|
|
|
249
277
|
|
|
278
|
+
def _tracked_test_contents_v2(
|
|
279
|
+
repo_root: Path,
|
|
280
|
+
manifest: dict[str, Any],
|
|
281
|
+
manifest_path: Path,
|
|
282
|
+
) -> tuple[dict[str, bytes], str | None]:
|
|
283
|
+
"""Validate a schema-2 manifest: repositoryId equality + logical hashes."""
|
|
284
|
+
if manifest.get("mode") != "force-track-touched":
|
|
285
|
+
raise ValueError("TEST_TRACKING_MANIFEST_INVALID")
|
|
286
|
+
repository_id = manifest.get("repositoryId")
|
|
287
|
+
if not isinstance(repository_id, str) or not repository_id.startswith("sha256:"):
|
|
288
|
+
raise ValueError("TEST_TRACKING_MANIFEST_INVALID")
|
|
289
|
+
if repository_id != harness_paths.repository_identity(repo_root):
|
|
290
|
+
raise ValueError("TEST_TRACKING_REPOSITORY_MISMATCH")
|
|
291
|
+
entries = manifest.get("files")
|
|
292
|
+
if not isinstance(entries, list) or not entries:
|
|
293
|
+
raise ValueError("TEST_TRACKING_MANIFEST_INVALID: EMPTY_FILES")
|
|
294
|
+
|
|
295
|
+
contents: dict[str, bytes] = {}
|
|
296
|
+
for item in entries:
|
|
297
|
+
if not isinstance(item, dict):
|
|
298
|
+
raise ValueError("TEST_TRACKING_MANIFEST_INVALID")
|
|
299
|
+
rel = item.get("path")
|
|
300
|
+
expected = item.get("logicalHash") or item.get("binaryHash")
|
|
301
|
+
if (
|
|
302
|
+
not isinstance(rel, str)
|
|
303
|
+
or not rel
|
|
304
|
+
or not isinstance(expected, str)
|
|
305
|
+
or not (expected.startswith("gitblob:") or expected.startswith("sha256:"))
|
|
306
|
+
or item.get("reason") not in TEST_TRACKING_REASONS
|
|
307
|
+
or type(item.get("ignored")) is not bool
|
|
308
|
+
or not isinstance(item.get("introducedBy"), str)
|
|
309
|
+
or not isinstance(item.get("touchedBy"), list)
|
|
310
|
+
or item.get("commitScope") not in ("current-change", "foreign-change")
|
|
311
|
+
):
|
|
312
|
+
raise ValueError("TEST_TRACKING_MANIFEST_INVALID")
|
|
313
|
+
raw_path = Path(rel)
|
|
314
|
+
resolved = (repo_root / raw_path).resolve()
|
|
315
|
+
if raw_path.is_absolute() or not _inside(resolved, repo_root):
|
|
316
|
+
raise ValueError(f"TEST_TRACKING_PATH_OUTSIDE_PROJECT: {rel}")
|
|
317
|
+
normalized = resolved.relative_to(repo_root).as_posix()
|
|
318
|
+
if normalized != rel or not resolved.is_file():
|
|
319
|
+
raise ValueError(f"TEST_TRACKING_FILE_INVALID: {rel}")
|
|
320
|
+
content = resolved.read_bytes()
|
|
321
|
+
actual = _logical_file_hash(repo_root, rel, content)
|
|
322
|
+
if actual != expected:
|
|
323
|
+
raise ValueError(f"TEST_TRACKING_HASH_DRIFT: {rel}")
|
|
324
|
+
contents[rel] = content
|
|
325
|
+
return {rel: contents[rel] for rel in sorted(contents)}, str(manifest_path)
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def _logical_file_hash(repo_root: Path, rel: str, content: bytes) -> str:
|
|
329
|
+
"""Mirror of harness_test_guard.logical_file_hash for validation."""
|
|
330
|
+
attr = subprocess.run(
|
|
331
|
+
["git", "check-attr", "text", "--", rel],
|
|
332
|
+
cwd=str(repo_root),
|
|
333
|
+
capture_output=True,
|
|
334
|
+
text=True,
|
|
335
|
+
encoding="utf-8",
|
|
336
|
+
errors="replace",
|
|
337
|
+
check=False,
|
|
338
|
+
)
|
|
339
|
+
attr_out = attr.stdout.strip() if attr.returncode == 0 else ""
|
|
340
|
+
byte_hash = "sha256:" + hashlib.sha256(content).hexdigest()
|
|
341
|
+
if attr_out.endswith(": unset") or b"\x00" in content:
|
|
342
|
+
return byte_hash
|
|
343
|
+
try:
|
|
344
|
+
content.decode("utf-8")
|
|
345
|
+
except UnicodeDecodeError:
|
|
346
|
+
return byte_hash
|
|
347
|
+
proc = subprocess.run(
|
|
348
|
+
["git", "hash-object", "--path", rel, "--stdin"],
|
|
349
|
+
input=content,
|
|
350
|
+
capture_output=True,
|
|
351
|
+
cwd=str(repo_root),
|
|
352
|
+
check=False,
|
|
353
|
+
)
|
|
354
|
+
if proc.returncode != 0:
|
|
355
|
+
return byte_hash
|
|
356
|
+
return "gitblob:" + proc.stdout.decode("ascii").strip()
|
|
357
|
+
|
|
358
|
+
|
|
250
359
|
def compute_diff_hash(
|
|
251
360
|
repo_root: Path,
|
|
252
361
|
base: str | None = None,
|
|
@@ -367,11 +476,20 @@ def expand_profile_input_files(
|
|
|
367
476
|
return sorted(seen), None
|
|
368
477
|
|
|
369
478
|
|
|
479
|
+
def _state_dir(change_dir: Path) -> Path:
|
|
480
|
+
return Path(harness_paths.resolve_state_dir_for_contract(change_dir))
|
|
481
|
+
|
|
482
|
+
|
|
370
483
|
def ledger_candidates(change_dir: Path) -> list[Path]:
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
484
|
+
state = _state_dir(change_dir)
|
|
485
|
+
contract = Path(change_dir)
|
|
486
|
+
candidates = [state / "evidence" / "verification-ledger.json"]
|
|
487
|
+
if state != contract:
|
|
488
|
+
candidates.append(contract / "evidence" / "verification-ledger.json")
|
|
489
|
+
candidates.append(state / "verification-ledger.json")
|
|
490
|
+
if state != contract:
|
|
491
|
+
candidates.append(contract / "verification-ledger.json")
|
|
492
|
+
return candidates
|
|
375
493
|
|
|
376
494
|
|
|
377
495
|
def find_ledger_path(change_dir: Path) -> Path | None:
|
|
@@ -382,8 +500,9 @@ def find_ledger_path(change_dir: Path) -> Path | None:
|
|
|
382
500
|
|
|
383
501
|
|
|
384
502
|
def preferred_write_path(change_dir: Path) -> Path:
|
|
385
|
-
# New writes always go to evidence/ (protocol preferred path)
|
|
386
|
-
|
|
503
|
+
# New writes always go to evidence/ (protocol preferred path); split-v1
|
|
504
|
+
# changes route to the dynamic state root.
|
|
505
|
+
return _state_dir(change_dir) / "evidence" / "verification-ledger.json"
|
|
387
506
|
|
|
388
507
|
|
|
389
508
|
def load_ledger(change_dir: Path) -> tuple[dict[str, Any] | None, Path | None]:
|
|
@@ -402,9 +521,237 @@ def load_ledger(change_dir: Path) -> tuple[dict[str, Any] | None, Path | None]:
|
|
|
402
521
|
|
|
403
522
|
|
|
404
523
|
def write_ledger(path: Path, data: dict[str, Any]) -> None:
|
|
524
|
+
"""Atomic ledger write: temp -> fsync -> replace (ledger v3)."""
|
|
405
525
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
406
526
|
payload = json.dumps(data, ensure_ascii=False, indent=2) + "\n"
|
|
407
|
-
path.
|
|
527
|
+
tmp = path.with_name(f".{path.name}.{os.getpid()}.{uuid.uuid4().hex}.tmp")
|
|
528
|
+
try:
|
|
529
|
+
with open(tmp, "w", encoding="utf-8", newline="\n") as handle:
|
|
530
|
+
handle.write(payload)
|
|
531
|
+
handle.flush()
|
|
532
|
+
os.fsync(handle.fileno())
|
|
533
|
+
os.replace(tmp, path)
|
|
534
|
+
except BaseException:
|
|
535
|
+
tmp.unlink(missing_ok=True)
|
|
536
|
+
raise
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
LEDGER_SCHEMA_VERSION = 3
|
|
540
|
+
LEDGER_IDENTITY_FIELDS = (
|
|
541
|
+
"repositoryId",
|
|
542
|
+
"changeName",
|
|
543
|
+
"baseCommit",
|
|
544
|
+
"currentHead",
|
|
545
|
+
"diffHash",
|
|
546
|
+
"ownershipHash",
|
|
547
|
+
)
|
|
548
|
+
|
|
549
|
+
|
|
550
|
+
def validate_ledger_identity(ledger: dict[str, Any]) -> list[str]:
|
|
551
|
+
"""Missing/invalid top-level identity fields (ledger v3)."""
|
|
552
|
+
missing: list[str] = []
|
|
553
|
+
if not isinstance(ledger, dict):
|
|
554
|
+
return ["ledger"]
|
|
555
|
+
if ledger.get("schemaVersion") != LEDGER_SCHEMA_VERSION:
|
|
556
|
+
missing.append("schemaVersion")
|
|
557
|
+
for field in LEDGER_IDENTITY_FIELDS:
|
|
558
|
+
value = ledger.get(field)
|
|
559
|
+
if not isinstance(value, str) or not value.strip():
|
|
560
|
+
missing.append(field)
|
|
561
|
+
return missing
|
|
562
|
+
|
|
563
|
+
|
|
564
|
+
def _contract_is_v2(change_dir: Path) -> bool:
|
|
565
|
+
try:
|
|
566
|
+
contract = harness_paths.load_change_contract(change_dir)
|
|
567
|
+
except (OSError, ValueError):
|
|
568
|
+
return False
|
|
569
|
+
if harness_paths.contract_layout_kind(contract) == "split-v1":
|
|
570
|
+
return True
|
|
571
|
+
version = contract.get("schemaVersion")
|
|
572
|
+
return isinstance(version, int) and version >= 2
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
def ownership_hash(contract: dict[str, Any]) -> str:
|
|
576
|
+
ownership = contract.get("ownership") or {}
|
|
577
|
+
canonical = json.dumps(ownership, ensure_ascii=False, sort_keys=True)
|
|
578
|
+
return "sha256:" + hashlib.sha256(canonical.encode("utf-8")).hexdigest()
|
|
579
|
+
|
|
580
|
+
|
|
581
|
+
_METRICS_SCHEMAS: dict[str, dict[str, tuple[str, ...]]] = {
|
|
582
|
+
"unitTest": {"required": ("total", "passed", "failed"), "optional": ("errors", "skipped")},
|
|
583
|
+
"unitTestFull": {"required": ("total", "passed", "failed"), "optional": ("errors", "skipped")},
|
|
584
|
+
"apiContract": {"required": ("scenariosTotal", "passed", "failed"), "optional": ("blocked",)},
|
|
585
|
+
"browserE2E": {"required": ("total", "passed", "failed"), "optional": ("skipped", "retries")},
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
|
|
589
|
+
def validate_metrics(verification: str, metrics: Any) -> list[str]:
|
|
590
|
+
"""Typed metrics schema check; unknown verification types pass through."""
|
|
591
|
+
problems: list[str] = []
|
|
592
|
+
if not isinstance(metrics, dict):
|
|
593
|
+
return ["metrics must be an object"]
|
|
594
|
+
if verification == "dbCompatibility":
|
|
595
|
+
applicability = metrics.get("applicability")
|
|
596
|
+
if applicability not in ("APPLICABLE", "NOT_APPLICABLE"):
|
|
597
|
+
return ["dbCompatibility.applicability must be APPLICABLE|NOT_APPLICABLE"]
|
|
598
|
+
if applicability == "NOT_APPLICABLE":
|
|
599
|
+
reason = metrics.get("reason")
|
|
600
|
+
if not isinstance(reason, str) or not reason.strip():
|
|
601
|
+
problems.append("dbCompatibility.reason required for NOT_APPLICABLE")
|
|
602
|
+
else:
|
|
603
|
+
if not isinstance(metrics.get("status"), str) or not metrics["status"]:
|
|
604
|
+
problems.append("dbCompatibility.status required for APPLICABLE")
|
|
605
|
+
return problems
|
|
606
|
+
schema = _METRICS_SCHEMAS.get(verification)
|
|
607
|
+
if schema is None:
|
|
608
|
+
return problems
|
|
609
|
+
allowed = set(schema["required"]) | set(schema["optional"])
|
|
610
|
+
for field in schema["required"]:
|
|
611
|
+
value = metrics.get(field)
|
|
612
|
+
if not isinstance(value, int) or isinstance(value, bool) or value < 0:
|
|
613
|
+
problems.append(f"metrics.{field} must be a non-negative int")
|
|
614
|
+
for field, value in metrics.items():
|
|
615
|
+
if field not in allowed:
|
|
616
|
+
problems.append(f"metrics.{field} is not a {verification} field")
|
|
617
|
+
elif not isinstance(value, int) or isinstance(value, bool) or value < 0:
|
|
618
|
+
problems.append(f"metrics.{field} must be a non-negative int")
|
|
619
|
+
return problems
|
|
620
|
+
|
|
621
|
+
|
|
622
|
+
def build_applicability_entry(value: str, reason: str | None = None) -> dict[str, Any]:
|
|
623
|
+
if value not in ("APPLICABLE", "NOT_APPLICABLE"):
|
|
624
|
+
raise ValueError("applicability must be APPLICABLE|NOT_APPLICABLE")
|
|
625
|
+
if value == "NOT_APPLICABLE" and not (isinstance(reason, str) and reason.strip()):
|
|
626
|
+
raise ValueError("NOT_APPLICABLE requires a scope reason")
|
|
627
|
+
entry: dict[str, Any] = {"applicability": value}
|
|
628
|
+
if reason and reason.strip():
|
|
629
|
+
entry["reason"] = reason.strip()
|
|
630
|
+
return entry
|
|
631
|
+
|
|
632
|
+
|
|
633
|
+
def applicability_counts_as_success(entry: dict[str, Any]) -> bool:
|
|
634
|
+
"""Applicability never contributes to success counters (RET-24)."""
|
|
635
|
+
return False
|
|
636
|
+
|
|
637
|
+
|
|
638
|
+
def applicability_counts_as_failure(entry: dict[str, Any]) -> bool:
|
|
639
|
+
"""NOT_APPLICABLE is not a failure; status decides for APPLICABLE."""
|
|
640
|
+
return False
|
|
641
|
+
|
|
642
|
+
|
|
643
|
+
_DYNAMIC_OWN_DIRS = ("events.ndjson", "logs", "evidence", "reports", "runtime", "backups")
|
|
644
|
+
|
|
645
|
+
|
|
646
|
+
def _matches_ownership_path(rel: str, declared: Any) -> bool:
|
|
647
|
+
scope = str(declared).replace("\\", "/").strip("/")
|
|
648
|
+
if not scope:
|
|
649
|
+
return False
|
|
650
|
+
normalized = rel.replace("\\", "/").strip("/")
|
|
651
|
+
return normalized == scope or normalized.startswith(scope + "/")
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
def _classify_ownership_path(
|
|
655
|
+
rel: str, change_name: str, ownership: dict[str, Any]
|
|
656
|
+
) -> str:
|
|
657
|
+
"""owned | staticEvidence | excludedRuntime | foreign."""
|
|
658
|
+
normalized = rel.replace("\\", "/")
|
|
659
|
+
if normalized.startswith(".harness/state/changes/"):
|
|
660
|
+
owner = normalized.split("/")[3] if len(normalized.split("/")) > 3 else ""
|
|
661
|
+
return "excludedRuntime" if owner == change_name else "foreign"
|
|
662
|
+
if normalized.startswith(".harness/state/"):
|
|
663
|
+
return "excludedRuntime"
|
|
664
|
+
if normalized.startswith(".harness/changes/"):
|
|
665
|
+
parts = normalized.split("/")
|
|
666
|
+
owner = parts[2] if len(parts) > 2 else ""
|
|
667
|
+
if owner and owner != change_name:
|
|
668
|
+
return "foreign"
|
|
669
|
+
remainder = "/".join(parts[3:]) if len(parts) > 3 else ""
|
|
670
|
+
head = remainder.split("/")[0] if remainder else ""
|
|
671
|
+
if remainder in _DYNAMIC_OWN_DIRS or head in _DYNAMIC_OWN_DIRS:
|
|
672
|
+
return "excludedRuntime"
|
|
673
|
+
for excluded in ownership.get("excludedPaths") or []:
|
|
674
|
+
if _matches_ownership_path(normalized, excluded):
|
|
675
|
+
return "excludedRuntime"
|
|
676
|
+
for static_path in ownership.get("staticEvidencePaths") or []:
|
|
677
|
+
if _matches_ownership_path(normalized, static_path):
|
|
678
|
+
return "staticEvidence"
|
|
679
|
+
for product_path in ownership.get("productPaths") or []:
|
|
680
|
+
if _matches_ownership_path(normalized, product_path):
|
|
681
|
+
return "owned"
|
|
682
|
+
return "foreign"
|
|
683
|
+
|
|
684
|
+
|
|
685
|
+
def compute_ownership_diff(
|
|
686
|
+
repo_root: Path, *, base: str, change_dir: Path, head: str | None = None
|
|
687
|
+
) -> dict[str, Any]:
|
|
688
|
+
"""diffHash over the change's ownership scope only (RET-18).
|
|
689
|
+
|
|
690
|
+
Excludes .harness/state/** and dynamic evidence; reports foreign change
|
|
691
|
+
paths separately instead of folding them into the hash.
|
|
692
|
+
"""
|
|
693
|
+
repo_root = Path(repo_root).resolve()
|
|
694
|
+
change_dir = Path(change_dir).resolve()
|
|
695
|
+
try:
|
|
696
|
+
contract = harness_paths.load_change_contract(change_dir)
|
|
697
|
+
except (OSError, ValueError):
|
|
698
|
+
contract = {}
|
|
699
|
+
ownership = contract.get("ownership") or {}
|
|
700
|
+
diff_args = ["diff", "--name-only", base]
|
|
701
|
+
if head:
|
|
702
|
+
diff_args.append(head)
|
|
703
|
+
raw = _git_text(repo_root, *diff_args) or ""
|
|
704
|
+
owned: list[str] = []
|
|
705
|
+
static_evidence: list[str] = []
|
|
706
|
+
foreign: list[str] = []
|
|
707
|
+
excluded_runtime = 0
|
|
708
|
+
for line in raw.splitlines():
|
|
709
|
+
rel = line.strip()
|
|
710
|
+
if not rel:
|
|
711
|
+
continue
|
|
712
|
+
verdict = _classify_ownership_path(rel, change_dir.name, ownership)
|
|
713
|
+
if verdict == "foreign":
|
|
714
|
+
foreign.append(rel)
|
|
715
|
+
elif verdict == "staticEvidence":
|
|
716
|
+
static_evidence.append(rel)
|
|
717
|
+
elif verdict == "excludedRuntime":
|
|
718
|
+
excluded_runtime += 1
|
|
719
|
+
else:
|
|
720
|
+
owned.append(rel)
|
|
721
|
+
owned.sort()
|
|
722
|
+
static_evidence.sort()
|
|
723
|
+
foreign.sort()
|
|
724
|
+
|
|
725
|
+
hasher = hashlib.sha256()
|
|
726
|
+
for rel in owned:
|
|
727
|
+
hasher.update(rel.encode("utf-8"))
|
|
728
|
+
hasher.update(b"\x00")
|
|
729
|
+
if head:
|
|
730
|
+
try:
|
|
731
|
+
content = _git_bytes(["show", f"{head}:{rel}"], repo_root)
|
|
732
|
+
except RuntimeError:
|
|
733
|
+
content = None
|
|
734
|
+
hasher.update(
|
|
735
|
+
hashlib.sha256(content).hexdigest().encode("ascii")
|
|
736
|
+
if content is not None
|
|
737
|
+
else b"<deleted>"
|
|
738
|
+
)
|
|
739
|
+
else:
|
|
740
|
+
content_path = (repo_root / rel).resolve()
|
|
741
|
+
if content_path.is_file() and _inside(content_path, repo_root):
|
|
742
|
+
hasher.update(hashlib.sha256(content_path.read_bytes()).hexdigest().encode("ascii"))
|
|
743
|
+
else:
|
|
744
|
+
hasher.update(b"<deleted>")
|
|
745
|
+
hasher.update(b"\x00")
|
|
746
|
+
return {
|
|
747
|
+
"diffHash": "sha256:" + hasher.hexdigest(),
|
|
748
|
+
"files": owned,
|
|
749
|
+
"staticEvidenceFiles": static_evidence,
|
|
750
|
+
"foreignPaths": foreign,
|
|
751
|
+
"excludedRuntimeCount": excluded_runtime,
|
|
752
|
+
"ownedFileCount": len(owned),
|
|
753
|
+
"ownershipHash": ownership_hash(contract),
|
|
754
|
+
}
|
|
408
755
|
|
|
409
756
|
|
|
410
757
|
def normalize_status(raw: str) -> str:
|
|
@@ -816,6 +1163,35 @@ def cmd_record(args: argparse.Namespace) -> int:
|
|
|
816
1163
|
"finishedAt": now_iso(),
|
|
817
1164
|
}
|
|
818
1165
|
)
|
|
1166
|
+
metrics_raw = getattr(args, "metrics_json", None)
|
|
1167
|
+
if metrics_raw is not None and str(metrics_raw).strip() != "":
|
|
1168
|
+
try:
|
|
1169
|
+
metrics_obj = json.loads(str(metrics_raw))
|
|
1170
|
+
except json.JSONDecodeError:
|
|
1171
|
+
return emit_error("invalid --metrics-json", as_json=as_json)
|
|
1172
|
+
if not isinstance(metrics_obj, dict):
|
|
1173
|
+
return emit_error("invalid --metrics-json", as_json=as_json)
|
|
1174
|
+
if _contract_is_v2(change_dir):
|
|
1175
|
+
# Typed metrics schemas are ledger v3 (contract-gated); legacy
|
|
1176
|
+
# changes keep the loose run/failures shape (zero regression).
|
|
1177
|
+
problems = validate_metrics(verification, metrics_obj)
|
|
1178
|
+
if problems:
|
|
1179
|
+
return emit_error(
|
|
1180
|
+
"invalid metrics: " + "; ".join(problems), as_json=as_json
|
|
1181
|
+
)
|
|
1182
|
+
entry["metrics"] = metrics_obj
|
|
1183
|
+
elif "metrics" in entry:
|
|
1184
|
+
# Fresh record without metrics must not keep stale metrics from prev.
|
|
1185
|
+
entry.pop("metrics", None)
|
|
1186
|
+
applicability_raw = getattr(args, "applicability", None)
|
|
1187
|
+
if applicability_raw:
|
|
1188
|
+
try:
|
|
1189
|
+
entry["applicability"] = build_applicability_entry(
|
|
1190
|
+
str(applicability_raw).strip(),
|
|
1191
|
+
reason=getattr(args, "applicability_reason", None),
|
|
1192
|
+
)
|
|
1193
|
+
except ValueError as exc:
|
|
1194
|
+
return emit_error(str(exc), as_json=as_json)
|
|
819
1195
|
if args.scope is not None and str(args.scope).strip():
|
|
820
1196
|
entry["scope"] = str(args.scope).strip()
|
|
821
1197
|
# No default scope: recording an incremental run as broad "module" scope
|
|
@@ -855,6 +1231,51 @@ def cmd_record(args: argparse.Namespace) -> int:
|
|
|
855
1231
|
ledger["stateDir"] = str(change_dir)
|
|
856
1232
|
|
|
857
1233
|
out_path = preferred_write_path(change_dir)
|
|
1234
|
+
if _contract_is_v2(change_dir):
|
|
1235
|
+
# Ledger v3: forced top-level identity (RET-15/16, COM-002).
|
|
1236
|
+
repo_root_raw = _git_text(change_dir, "rev-parse", "--show-toplevel")
|
|
1237
|
+
if not repo_root_raw:
|
|
1238
|
+
return emit_error(
|
|
1239
|
+
"record failed: change dir is not inside a git repository",
|
|
1240
|
+
as_json=as_json,
|
|
1241
|
+
)
|
|
1242
|
+
repo_root = Path(repo_root_raw).resolve()
|
|
1243
|
+
try:
|
|
1244
|
+
contract = harness_paths.load_change_contract(change_dir)
|
|
1245
|
+
except (OSError, ValueError) as exc:
|
|
1246
|
+
return emit_error(f"record failed: {exc}", as_json=as_json)
|
|
1247
|
+
base_commit = getattr(args, "base_commit", None)
|
|
1248
|
+
if not _nonempty_str(base_commit):
|
|
1249
|
+
base_commit = ledger.get("baseCommit")
|
|
1250
|
+
if not _nonempty_str(base_commit):
|
|
1251
|
+
base_commit = _git_text(repo_root, "rev-parse", "--verify", "HEAD")
|
|
1252
|
+
current_head = _git_text(repo_root, "rev-parse", "--verify", "HEAD")
|
|
1253
|
+
diff_hash = getattr(args, "diff_hash", None)
|
|
1254
|
+
if not _nonempty_str(diff_hash):
|
|
1255
|
+
diff_hash = ledger.get("diffHash")
|
|
1256
|
+
if not _nonempty_str(diff_hash) and _nonempty_str(base_commit):
|
|
1257
|
+
try:
|
|
1258
|
+
diff_hash = compute_ownership_diff(
|
|
1259
|
+
repo_root, base=str(base_commit).strip(), change_dir=change_dir
|
|
1260
|
+
)["diffHash"]
|
|
1261
|
+
except (OSError, ValueError, RuntimeError):
|
|
1262
|
+
diff_hash = None
|
|
1263
|
+
ledger["schemaVersion"] = LEDGER_SCHEMA_VERSION
|
|
1264
|
+
ledger["repositoryId"] = harness_paths.repository_identity(repo_root)
|
|
1265
|
+
ledger["changeName"] = change_dir.name
|
|
1266
|
+
if _nonempty_str(base_commit):
|
|
1267
|
+
ledger["baseCommit"] = str(base_commit).strip()
|
|
1268
|
+
if _nonempty_str(current_head):
|
|
1269
|
+
ledger["currentHead"] = str(current_head).strip()
|
|
1270
|
+
if _nonempty_str(diff_hash):
|
|
1271
|
+
ledger["diffHash"] = str(diff_hash).strip()
|
|
1272
|
+
ledger["ownershipHash"] = ownership_hash(contract)
|
|
1273
|
+
missing = validate_ledger_identity(ledger)
|
|
1274
|
+
if missing:
|
|
1275
|
+
return emit_error(
|
|
1276
|
+
"ledger identity incomplete: " + ", ".join(missing),
|
|
1277
|
+
as_json=as_json,
|
|
1278
|
+
)
|
|
858
1279
|
write_ledger(out_path, ledger)
|
|
859
1280
|
except (OSError, ValueError, FileNotFoundError, json.JSONDecodeError) as exc:
|
|
860
1281
|
return emit_error(f"record failed: {exc}", as_json=as_json)
|
|
@@ -1014,6 +1435,32 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
1014
1435
|
default=None,
|
|
1015
1436
|
help="package: prior verifications reused when testsExecuted=false",
|
|
1016
1437
|
)
|
|
1438
|
+
p_record.add_argument(
|
|
1439
|
+
"--metrics-json",
|
|
1440
|
+
default=None,
|
|
1441
|
+
help='structured counts, e.g. \'{"run":155,"failures":0}\' or \'{"total":3,"passed":3}\'',
|
|
1442
|
+
)
|
|
1443
|
+
p_record.add_argument(
|
|
1444
|
+
"--base-commit",
|
|
1445
|
+
default=None,
|
|
1446
|
+
help="ledger v3: base commit for identity (default: existing ledger value, else HEAD)",
|
|
1447
|
+
)
|
|
1448
|
+
p_record.add_argument(
|
|
1449
|
+
"--diff-hash",
|
|
1450
|
+
default=None,
|
|
1451
|
+
help="ledger v3: precomputed ownership diff hash (default: existing, else computed)",
|
|
1452
|
+
)
|
|
1453
|
+
p_record.add_argument(
|
|
1454
|
+
"--applicability",
|
|
1455
|
+
choices=("APPLICABLE", "NOT_APPLICABLE"),
|
|
1456
|
+
default=None,
|
|
1457
|
+
help="ledger v3: applicability of this verification to the change",
|
|
1458
|
+
)
|
|
1459
|
+
p_record.add_argument(
|
|
1460
|
+
"--applicability-reason",
|
|
1461
|
+
default=None,
|
|
1462
|
+
help="ledger v3: scope reason (required when --applicability NOT_APPLICABLE)",
|
|
1463
|
+
)
|
|
1017
1464
|
p_record.set_defaults(func=cmd_record)
|
|
1018
1465
|
|
|
1019
1466
|
p_diff = sub.add_parser(
|