@hunter-harness/workflow-harness 0.3.1 → 0.4.1
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/harness-archive/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-archive/reference.md +30 -1
- package/harness/bundles/general/claude-code/harness-archive/templates/summary-data-template.json +1 -0
- package/harness/bundles/general/claude-code/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-execute/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
- 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-pull/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-push/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-review/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-run/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-submit/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-test/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/scripts/harness_archive.py +146 -8
- package/harness/bundles/general/claude-code/scripts/harness_deploy.py +1 -1
- package/harness/bundles/general/claude-code/scripts/harness_knowledge_candidates.py +538 -4
- package/harness/bundles/general/codebuddy/.harness-build.json +1 -1
- package/harness/bundles/general/codebuddy/harness-archive/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-archive/reference.md +30 -1
- package/harness/bundles/general/codebuddy/harness-archive/templates/summary-data-template.json +1 -0
- package/harness/bundles/general/codebuddy/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-execute/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
- 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-pull/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-push/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-review/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-run/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-submit/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-test/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/scripts/harness_archive.py +146 -8
- package/harness/bundles/general/codebuddy/scripts/harness_deploy.py +1 -1
- package/harness/bundles/general/codebuddy/scripts/harness_knowledge_candidates.py +538 -4
- package/harness/bundles/general/codex/.harness-build.json +1 -1
- package/harness/bundles/general/codex/harness-archive/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-archive/reference.md +30 -1
- package/harness/bundles/general/codex/harness-archive/templates/summary-data-template.json +1 -0
- package/harness/bundles/general/codex/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-execute/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-knowledge-ingest/SKILL.md +1 -1
- 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-pull/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-push/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-review/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-run/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-submit/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-test/SKILL.md +1 -1
- package/harness/bundles/general/codex/scripts/harness_archive.py +146 -8
- package/harness/bundles/general/codex/scripts/harness_deploy.py +1 -1
- package/harness/bundles/general/codex/scripts/harness_knowledge_candidates.py +538 -4
- package/harness/bundles/general/cursor/.harness-build.json +1 -1
- package/harness/bundles/general/cursor/harness-archive/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-archive/reference.md +30 -1
- package/harness/bundles/general/cursor/harness-archive/templates/summary-data-template.json +1 -0
- package/harness/bundles/general/cursor/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-execute/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-knowledge-ingest/SKILL.md +1 -1
- 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-pull/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-push/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-review/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-run/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-submit/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-test/SKILL.md +1 -1
- package/harness/bundles/general/cursor/scripts/harness_archive.py +146 -8
- package/harness/bundles/general/cursor/scripts/harness_deploy.py +1 -1
- package/harness/bundles/general/cursor/scripts/harness_knowledge_candidates.py +538 -4
- package/harness/bundles/general/pi/.harness-build.json +6 -0
- package/harness/bundles/general/pi/CONTEXT.md +98 -0
- package/harness/bundles/general/pi/README.md +478 -0
- package/harness/bundles/general/pi/contracts/build-profile-v3.schema.json +302 -0
- package/harness/bundles/general/pi/contracts/ci-metrics.schema.json +36 -0
- package/harness/bundles/general/pi/contracts/doctor-capability.schema.json +43 -0
- package/harness/bundles/general/pi/contracts/external-platform-observation.schema.json +62 -0
- package/harness/bundles/general/pi/contracts/fixtures/managed-execution.json +341 -0
- package/harness/bundles/general/pi/contracts/process-identity.schema.json +132 -0
- package/harness/bundles/general/pi/contracts/run-session.schema.json +142 -0
- package/harness/bundles/general/pi/contracts/service-retirement-receipt.schema.json +85 -0
- package/harness/bundles/general/pi/contracts/service-session.schema.json +147 -0
- package/harness/bundles/general/pi/contracts/workflow-policy.json +574 -0
- package/harness/bundles/general/pi/harness-archive/SKILL.md +322 -0
- package/harness/bundles/general/pi/harness-archive/checklist.md +71 -0
- package/harness/bundles/general/pi/harness-archive/reference.md +150 -0
- package/harness/bundles/general/pi/harness-archive/scripts/gen-manifest.ps1 +31 -0
- package/harness/bundles/general/pi/harness-archive/templates/summary-data-template.json +218 -0
- package/harness/bundles/general/pi/harness-codebase-map/SKILL.md +116 -0
- package/harness/bundles/general/pi/harness-codebase-map/checklist.md +126 -0
- package/harness/bundles/general/pi/harness-codebase-map/reference.md +364 -0
- package/harness/bundles/general/pi/harness-codebase-map/templates/ARCHITECTURE.md +57 -0
- package/harness/bundles/general/pi/harness-codebase-map/templates/CONCERNS.md +49 -0
- package/harness/bundles/general/pi/harness-codebase-map/templates/CONVENTIONS.md +57 -0
- package/harness/bundles/general/pi/harness-codebase-map/templates/INTEGRATIONS.md +52 -0
- package/harness/bundles/general/pi/harness-codebase-map/templates/STACK.md +55 -0
- package/harness/bundles/general/pi/harness-codebase-map/templates/STRUCTURE.md +69 -0
- package/harness/bundles/general/pi/harness-codebase-map/templates/TESTING.md +64 -0
- package/harness/bundles/general/pi/harness-codebase-map/templates/map-manifest.schema.json +73 -0
- package/harness/bundles/general/pi/harness-codebase-map/templates/map-summary.md +64 -0
- package/harness/bundles/general/pi/harness-execute/SKILL.md +170 -0
- package/harness/bundles/general/pi/harness-knowledge-ingest/SKILL.md +103 -0
- package/harness/bundles/general/pi/harness-knowledge-query/SKILL.md +97 -0
- package/harness/bundles/general/pi/harness-plan/SKILL.md +158 -0
- package/harness/bundles/general/pi/harness-plan/checklist.md +243 -0
- package/harness/bundles/general/pi/harness-plan/protocols.md +133 -0
- package/harness/bundles/general/pi/harness-plan/reference.md +468 -0
- package/harness/bundles/general/pi/harness-pull/SKILL.md +55 -0
- package/harness/bundles/general/pi/harness-push/SKILL.md +83 -0
- package/harness/bundles/general/pi/harness-review/SKILL.md +175 -0
- package/harness/bundles/general/pi/harness-review/checklist.md +124 -0
- package/harness/bundles/general/pi/harness-review/protocols.md +69 -0
- package/harness/bundles/general/pi/harness-review/reference.md +114 -0
- package/harness/bundles/general/pi/harness-run/SKILL.md +188 -0
- package/harness/bundles/general/pi/harness-run/checklist.md +279 -0
- package/harness/bundles/general/pi/harness-run/protocols.md +141 -0
- package/harness/bundles/general/pi/harness-run/reference.md +886 -0
- package/harness/bundles/general/pi/harness-submit/SKILL.md +182 -0
- package/harness/bundles/general/pi/harness-submit/checklist.md +452 -0
- package/harness/bundles/general/pi/harness-submit/reference.md +152 -0
- package/harness/bundles/general/pi/harness-sync/SKILL.md +72 -0
- package/harness/bundles/general/pi/harness-sync/reference.md +176 -0
- package/harness/bundles/general/pi/harness-test/SKILL.md +250 -0
- package/harness/bundles/general/pi/harness-test/checklist.md +359 -0
- package/harness/bundles/general/pi/harness-test/pitfalls-java.md +14 -0
- package/harness/bundles/general/pi/harness-test/pitfalls.md +307 -0
- package/harness/bundles/general/pi/harness-test/reference.md +880 -0
- package/harness/bundles/general/pi/harness-test/scripts/runtime-helpers.mjs +216 -0
- package/harness/bundles/general/pi/protocols/archive-report-protocol.md +196 -0
- package/harness/bundles/general/pi/protocols/ci-layering-protocol.md +22 -0
- package/harness/bundles/general/pi/protocols/evidence-based-reporting-protocol.md +134 -0
- package/harness/bundles/general/pi/protocols/execution-session-protocol.md +52 -0
- package/harness/bundles/general/pi/protocols/external-platform-protocol.md +27 -0
- package/harness/bundles/general/pi/protocols/ledger-protocol.md +365 -0
- package/harness/bundles/general/pi/protocols/memory-closeout-protocol.md +21 -0
- package/harness/bundles/general/pi/protocols/model-routing-protocol.md +22 -0
- package/harness/bundles/general/pi/protocols/powershell-protocol.md +238 -0
- package/harness/bundles/general/pi/protocols/registry-governance-protocol.md +31 -0
- package/harness/bundles/general/pi/protocols/report-pipeline-protocol.md +172 -0
- package/harness/bundles/general/pi/protocols/sensitive-info-protocol.md +110 -0
- package/harness/bundles/general/pi/protocols/state-layout-protocol.md +209 -0
- package/harness/bundles/general/pi/protocols/submit-protocol.md +78 -0
- package/harness/bundles/general/pi/protocols/verification-graph-protocol.md +32 -0
- package/harness/bundles/general/pi/scripts/harness_adoption_metrics.py +391 -0
- package/harness/bundles/general/pi/scripts/harness_apidoc.py +53 -0
- package/harness/bundles/general/pi/scripts/harness_archive.py +11345 -0
- package/harness/bundles/general/pi/scripts/harness_change.py +1626 -0
- package/harness/bundles/general/pi/scripts/harness_check_gate.py +113 -0
- package/harness/bundles/general/pi/scripts/harness_context.py +1774 -0
- package/harness/bundles/general/pi/scripts/harness_deploy.py +968 -0
- package/harness/bundles/general/pi/scripts/harness_efficiency.py +443 -0
- package/harness/bundles/general/pi/scripts/harness_environment.py +1690 -0
- package/harness/bundles/general/pi/scripts/harness_events.py +2234 -0
- package/harness/bundles/general/pi/scripts/harness_events_sync.py +1425 -0
- package/harness/bundles/general/pi/scripts/harness_execution_contracts.py +596 -0
- package/harness/bundles/general/pi/scripts/harness_fixback.py +1975 -0
- package/harness/bundles/general/pi/scripts/harness_gate.py +3816 -0
- package/harness/bundles/general/pi/scripts/harness_headless.py +37 -0
- package/harness/bundles/general/pi/scripts/harness_integration.py +2564 -0
- package/harness/bundles/general/pi/scripts/harness_knowledge_candidates.py +796 -0
- package/harness/bundles/general/pi/scripts/harness_ledger.py +3556 -0
- package/harness/bundles/general/pi/scripts/harness_migration_head.py +220 -0
- package/harness/bundles/general/pi/scripts/harness_orchestration.py +344 -0
- package/harness/bundles/general/pi/scripts/harness_paths.py +676 -0
- package/harness/bundles/general/pi/scripts/harness_phase.py +1726 -0
- package/harness/bundles/general/pi/scripts/harness_plan_aggregate.py +249 -0
- package/harness/bundles/general/pi/scripts/harness_plan_finalize.py +1016 -0
- package/harness/bundles/general/pi/scripts/harness_preflight.py +650 -0
- package/harness/bundles/general/pi/scripts/harness_process.py +1065 -0
- package/harness/bundles/general/pi/scripts/harness_profile.py +1817 -0
- package/harness/bundles/general/pi/scripts/harness_report_model.py +442 -0
- package/harness/bundles/general/pi/scripts/harness_retry.py +175 -0
- package/harness/bundles/general/pi/scripts/harness_review.py +631 -0
- package/harness/bundles/general/pi/scripts/harness_runtime.py +2243 -0
- package/harness/bundles/general/pi/scripts/harness_service.py +3355 -0
- package/harness/bundles/general/pi/scripts/harness_state.py +365 -0
- package/harness/bundles/general/pi/scripts/harness_sync.py +407 -0
- package/harness/bundles/general/pi/scripts/harness_test_cleanup.py +175 -0
- package/harness/bundles/general/pi/scripts/harness_test_guard.py +1899 -0
- package/harness/bundles/general/pi/scripts/harness_test_runner.py +1586 -0
- package/harness/bundles/general/pi/scripts/harness_verification.py +593 -0
- package/harness/bundles/general/pi/scripts/harness_workflow_policy.py +266 -0
- package/harness/bundles/java/claude-code/.harness-build.json +1 -1
- package/harness/bundles/java/claude-code/harness-apidoc/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-archive/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-archive/reference.md +30 -1
- package/harness/bundles/java/claude-code/harness-archive/templates/summary-data-template.json +1 -0
- package/harness/bundles/java/claude-code/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-execute/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
- 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-pull/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-push/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-review/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-run/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-submit/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-test/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/scripts/harness_archive.py +146 -8
- package/harness/bundles/java/claude-code/scripts/harness_deploy.py +1 -1
- package/harness/bundles/java/claude-code/scripts/harness_knowledge_candidates.py +538 -4
- package/harness/bundles/java/codebuddy/.harness-build.json +1 -1
- package/harness/bundles/java/codebuddy/harness-apidoc/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-archive/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-archive/reference.md +30 -1
- package/harness/bundles/java/codebuddy/harness-archive/templates/summary-data-template.json +1 -0
- package/harness/bundles/java/codebuddy/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-execute/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
- 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-pull/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-push/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-review/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-run/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-submit/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-test/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/scripts/harness_archive.py +146 -8
- package/harness/bundles/java/codebuddy/scripts/harness_deploy.py +1 -1
- package/harness/bundles/java/codebuddy/scripts/harness_knowledge_candidates.py +538 -4
- package/harness/bundles/java/codex/.harness-build.json +1 -1
- package/harness/bundles/java/codex/harness-apidoc/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-archive/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-archive/reference.md +30 -1
- package/harness/bundles/java/codex/harness-archive/templates/summary-data-template.json +1 -0
- package/harness/bundles/java/codex/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-execute/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-knowledge-ingest/SKILL.md +1 -1
- 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-pull/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-push/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-review/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-run/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-submit/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-test/SKILL.md +1 -1
- package/harness/bundles/java/codex/scripts/harness_archive.py +146 -8
- package/harness/bundles/java/codex/scripts/harness_deploy.py +1 -1
- package/harness/bundles/java/codex/scripts/harness_knowledge_candidates.py +538 -4
- package/harness/bundles/java/cursor/.harness-build.json +1 -1
- package/harness/bundles/java/cursor/harness-apidoc/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-archive/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-archive/reference.md +30 -1
- package/harness/bundles/java/cursor/harness-archive/templates/summary-data-template.json +1 -0
- package/harness/bundles/java/cursor/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-execute/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-knowledge-ingest/SKILL.md +1 -1
- 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-pull/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-push/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-review/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-run/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-submit/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-test/SKILL.md +1 -1
- package/harness/bundles/java/cursor/scripts/harness_archive.py +146 -8
- package/harness/bundles/java/cursor/scripts/harness_deploy.py +1 -1
- package/harness/bundles/java/cursor/scripts/harness_knowledge_candidates.py +538 -4
- package/harness/bundles/java/pi/.harness-build.json +6 -0
- package/harness/bundles/java/pi/CONTEXT.md +98 -0
- package/harness/bundles/java/pi/README.md +478 -0
- package/harness/bundles/java/pi/contracts/build-profile-v3.schema.json +302 -0
- package/harness/bundles/java/pi/contracts/ci-metrics.schema.json +36 -0
- package/harness/bundles/java/pi/contracts/doctor-capability.schema.json +43 -0
- package/harness/bundles/java/pi/contracts/external-platform-observation.schema.json +62 -0
- package/harness/bundles/java/pi/contracts/fixtures/managed-execution.json +341 -0
- package/harness/bundles/java/pi/contracts/process-identity.schema.json +132 -0
- package/harness/bundles/java/pi/contracts/run-session.schema.json +142 -0
- package/harness/bundles/java/pi/contracts/service-retirement-receipt.schema.json +85 -0
- package/harness/bundles/java/pi/contracts/service-session.schema.json +147 -0
- package/harness/bundles/java/pi/contracts/workflow-policy.json +574 -0
- package/harness/bundles/java/pi/harness-apidoc/SKILL.md +97 -0
- package/harness/bundles/java/pi/harness-apidoc/checklist.md +142 -0
- package/harness/bundles/java/pi/harness-apidoc/reference.md +233 -0
- package/harness/bundles/java/pi/harness-archive/SKILL.md +322 -0
- package/harness/bundles/java/pi/harness-archive/checklist.md +71 -0
- package/harness/bundles/java/pi/harness-archive/reference.md +150 -0
- package/harness/bundles/java/pi/harness-archive/scripts/gen-manifest.ps1 +31 -0
- package/harness/bundles/java/pi/harness-archive/templates/summary-data-template.json +218 -0
- package/harness/bundles/java/pi/harness-codebase-map/SKILL.md +116 -0
- package/harness/bundles/java/pi/harness-codebase-map/checklist.md +126 -0
- package/harness/bundles/java/pi/harness-codebase-map/reference.md +364 -0
- package/harness/bundles/java/pi/harness-codebase-map/templates/ARCHITECTURE.md +57 -0
- package/harness/bundles/java/pi/harness-codebase-map/templates/CONCERNS.md +49 -0
- package/harness/bundles/java/pi/harness-codebase-map/templates/CONVENTIONS.md +57 -0
- package/harness/bundles/java/pi/harness-codebase-map/templates/INTEGRATIONS.md +52 -0
- package/harness/bundles/java/pi/harness-codebase-map/templates/STACK.md +55 -0
- package/harness/bundles/java/pi/harness-codebase-map/templates/STRUCTURE.md +69 -0
- package/harness/bundles/java/pi/harness-codebase-map/templates/TESTING.md +64 -0
- package/harness/bundles/java/pi/harness-codebase-map/templates/map-manifest.schema.json +73 -0
- package/harness/bundles/java/pi/harness-codebase-map/templates/map-summary.md +64 -0
- package/harness/bundles/java/pi/harness-execute/SKILL.md +170 -0
- package/harness/bundles/java/pi/harness-knowledge-ingest/SKILL.md +103 -0
- package/harness/bundles/java/pi/harness-knowledge-query/SKILL.md +97 -0
- package/harness/bundles/java/pi/harness-package/SKILL.md +94 -0
- package/harness/bundles/java/pi/harness-package/checklist.md +322 -0
- package/harness/bundles/java/pi/harness-package/reference.md +210 -0
- package/harness/bundles/java/pi/harness-plan/SKILL.md +158 -0
- package/harness/bundles/java/pi/harness-plan/checklist.md +243 -0
- package/harness/bundles/java/pi/harness-plan/protocols.md +133 -0
- package/harness/bundles/java/pi/harness-plan/reference.md +468 -0
- package/harness/bundles/java/pi/harness-pull/SKILL.md +55 -0
- package/harness/bundles/java/pi/harness-push/SKILL.md +83 -0
- package/harness/bundles/java/pi/harness-review/SKILL.md +175 -0
- package/harness/bundles/java/pi/harness-review/checklist.md +124 -0
- package/harness/bundles/java/pi/harness-review/protocols.md +69 -0
- package/harness/bundles/java/pi/harness-review/reference.md +114 -0
- package/harness/bundles/java/pi/harness-run/SKILL.md +206 -0
- package/harness/bundles/java/pi/harness-run/checklist.md +252 -0
- package/harness/bundles/java/pi/harness-run/protocols.md +141 -0
- package/harness/bundles/java/pi/harness-run/reference.md +820 -0
- package/harness/bundles/java/pi/harness-submit/SKILL.md +189 -0
- package/harness/bundles/java/pi/harness-submit/checklist.md +452 -0
- package/harness/bundles/java/pi/harness-submit/reference.md +152 -0
- package/harness/bundles/java/pi/harness-sync/SKILL.md +72 -0
- package/harness/bundles/java/pi/harness-sync/reference.md +176 -0
- package/harness/bundles/java/pi/harness-test/SKILL.md +264 -0
- package/harness/bundles/java/pi/harness-test/checklist.md +320 -0
- package/harness/bundles/java/pi/harness-test/pitfalls-java.md +16 -0
- package/harness/bundles/java/pi/harness-test/pitfalls.md +307 -0
- package/harness/bundles/java/pi/harness-test/reference.md +792 -0
- package/harness/bundles/java/pi/harness-test/scripts/runtime-helpers.mjs +216 -0
- package/harness/bundles/java/pi/protocols/archive-report-protocol.md +196 -0
- package/harness/bundles/java/pi/protocols/ci-layering-protocol.md +22 -0
- package/harness/bundles/java/pi/protocols/evidence-based-reporting-protocol.md +134 -0
- package/harness/bundles/java/pi/protocols/execution-session-protocol.md +52 -0
- package/harness/bundles/java/pi/protocols/external-platform-protocol.md +27 -0
- package/harness/bundles/java/pi/protocols/ledger-protocol.md +365 -0
- package/harness/bundles/java/pi/protocols/memory-closeout-protocol.md +21 -0
- package/harness/bundles/java/pi/protocols/model-routing-protocol.md +22 -0
- package/harness/bundles/java/pi/protocols/powershell-protocol.md +238 -0
- package/harness/bundles/java/pi/protocols/registry-governance-protocol.md +31 -0
- package/harness/bundles/java/pi/protocols/report-pipeline-protocol.md +172 -0
- package/harness/bundles/java/pi/protocols/sensitive-info-protocol.md +110 -0
- package/harness/bundles/java/pi/protocols/state-layout-protocol.md +209 -0
- package/harness/bundles/java/pi/protocols/submit-protocol.md +78 -0
- package/harness/bundles/java/pi/protocols/verification-graph-protocol.md +32 -0
- package/harness/bundles/java/pi/scripts/harness_adoption_metrics.py +391 -0
- package/harness/bundles/java/pi/scripts/harness_apidoc.py +53 -0
- package/harness/bundles/java/pi/scripts/harness_archive.py +11345 -0
- package/harness/bundles/java/pi/scripts/harness_change.py +1626 -0
- package/harness/bundles/java/pi/scripts/harness_check_gate.py +113 -0
- package/harness/bundles/java/pi/scripts/harness_context.py +1774 -0
- package/harness/bundles/java/pi/scripts/harness_deploy.py +968 -0
- package/harness/bundles/java/pi/scripts/harness_efficiency.py +443 -0
- package/harness/bundles/java/pi/scripts/harness_environment.py +1690 -0
- package/harness/bundles/java/pi/scripts/harness_events.py +2234 -0
- package/harness/bundles/java/pi/scripts/harness_events_sync.py +1425 -0
- package/harness/bundles/java/pi/scripts/harness_execution_contracts.py +596 -0
- package/harness/bundles/java/pi/scripts/harness_fixback.py +1975 -0
- package/harness/bundles/java/pi/scripts/harness_gate.py +3816 -0
- package/harness/bundles/java/pi/scripts/harness_headless.py +37 -0
- package/harness/bundles/java/pi/scripts/harness_integration.py +2564 -0
- package/harness/bundles/java/pi/scripts/harness_knowledge_candidates.py +796 -0
- package/harness/bundles/java/pi/scripts/harness_ledger.py +3556 -0
- package/harness/bundles/java/pi/scripts/harness_migration_head.py +220 -0
- package/harness/bundles/java/pi/scripts/harness_orchestration.py +344 -0
- package/harness/bundles/java/pi/scripts/harness_paths.py +676 -0
- package/harness/bundles/java/pi/scripts/harness_phase.py +1726 -0
- package/harness/bundles/java/pi/scripts/harness_plan_aggregate.py +249 -0
- package/harness/bundles/java/pi/scripts/harness_plan_finalize.py +1016 -0
- package/harness/bundles/java/pi/scripts/harness_preflight.py +650 -0
- package/harness/bundles/java/pi/scripts/harness_process.py +1065 -0
- package/harness/bundles/java/pi/scripts/harness_profile.py +1817 -0
- package/harness/bundles/java/pi/scripts/harness_report_model.py +442 -0
- package/harness/bundles/java/pi/scripts/harness_retry.py +175 -0
- package/harness/bundles/java/pi/scripts/harness_review.py +631 -0
- package/harness/bundles/java/pi/scripts/harness_runtime.py +2243 -0
- package/harness/bundles/java/pi/scripts/harness_service.py +3355 -0
- package/harness/bundles/java/pi/scripts/harness_state.py +365 -0
- package/harness/bundles/java/pi/scripts/harness_sync.py +407 -0
- package/harness/bundles/java/pi/scripts/harness_test_cleanup.py +175 -0
- package/harness/bundles/java/pi/scripts/harness_test_guard.py +1899 -0
- package/harness/bundles/java/pi/scripts/harness_test_runner.py +1586 -0
- package/harness/bundles/java/pi/scripts/harness_verification.py +593 -0
- package/harness/bundles/java/pi/scripts/harness_workflow_policy.py +266 -0
- package/harness/manifests/general/claude-code.json +19 -19
- package/harness/manifests/general/codebuddy.json +19 -19
- package/harness/manifests/general/codex.json +19 -19
- package/harness/manifests/general/cursor.json +19 -19
- package/harness/manifests/general/pi.json +475 -0
- package/harness/manifests/java/claude-code.json +21 -21
- package/harness/manifests/java/codebuddy.json +21 -21
- package/harness/manifests/java/codex.json +21 -21
- package/harness/manifests/java/cursor.json +21 -21
- package/harness/manifests/java/pi.json +499 -0
- package/hunter-workflow-family.json +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,676 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Harness path and identity resolution.
|
|
3
|
+
|
|
4
|
+
Single owner for:
|
|
5
|
+
- repository identity (stable across worktrees of one repo);
|
|
6
|
+
- Change contract loading (``meta/change-context.json``);
|
|
7
|
+
- dual-root layout resolution (static contract root vs dynamic state root);
|
|
8
|
+
- path boundary assertions used by cleanup/integration code.
|
|
9
|
+
|
|
10
|
+
Python 3.10+, stdlib only.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import argparse
|
|
16
|
+
import hashlib
|
|
17
|
+
import json
|
|
18
|
+
import os
|
|
19
|
+
import re
|
|
20
|
+
import subprocess
|
|
21
|
+
import sys
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from typing import Any, Sequence
|
|
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
|
+
if hasattr(sys.stdout, "reconfigure"):
|
|
30
|
+
sys.stdout.reconfigure(encoding="utf-8")
|
|
31
|
+
if hasattr(sys.stderr, "reconfigure"):
|
|
32
|
+
sys.stderr.reconfigure(encoding="utf-8")
|
|
33
|
+
|
|
34
|
+
LIFECYCLE_STATUSES = {"draft", "active", "superseded", "archived", "cancelled"}
|
|
35
|
+
CHANGE_CONTEXT_REL = Path("meta") / "change-context.json"
|
|
36
|
+
|
|
37
|
+
# 工作流阶段的唯一权威清单。这里是叶子模块(不 import 任何 harness_*),所以
|
|
38
|
+
# harness_context 与 harness_phase 都能引它而不成环。
|
|
39
|
+
#
|
|
40
|
+
# 此前这份清单在两个模块各写一遍,靠约定保持同步——直到 harness_phase 少了一个
|
|
41
|
+
# merge,worktree 变更走到 merge 阶段就在 target_required_dag 里直接 raise
|
|
42
|
+
# "unsupported reconcile target phase"。同一份事实不该有两个副本。
|
|
43
|
+
WORKFLOW_PHASES = (
|
|
44
|
+
"plan",
|
|
45
|
+
"execute",
|
|
46
|
+
"review",
|
|
47
|
+
"package",
|
|
48
|
+
"apidoc",
|
|
49
|
+
"submit",
|
|
50
|
+
"merge",
|
|
51
|
+
"archive",
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
# 出现在阶段位置、但不是工作流阶段的名字。它们是真实存在的:
|
|
55
|
+
# workflow-policy.json 的 skills.*.phase 用 sync/codebase-map/knowledge-query 标注
|
|
56
|
+
# 技能归属,harness_gate.GATE_RELEASE_PHASES 含 release/deploy。把它们与"拼错的
|
|
57
|
+
# 阶段名"区分开,调用方才能既不误判也不硬崩。
|
|
58
|
+
KNOWN_NON_WORKFLOW_PHASES = frozenset({
|
|
59
|
+
"release",
|
|
60
|
+
"deploy",
|
|
61
|
+
"sync",
|
|
62
|
+
"codebase-map",
|
|
63
|
+
"knowledge-query",
|
|
64
|
+
"knowledge-ingest",
|
|
65
|
+
"push",
|
|
66
|
+
"pull",
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
# 旧阶段名 → 现阶段名。阶段合并/改名时在这里登记,读时映射即可让历史
|
|
70
|
+
# gate-policy.json、plannedPhases 与已落盘 transitions 继续可读,不必迁移
|
|
71
|
+
# 已落盘的 change。注意:映射只能用在哈希校验之后的业务比较层——对已哈希
|
|
72
|
+
# 的 receipt 先映射再重算会断 _payload_hash 链。
|
|
73
|
+
# 2026-08 run+test 合并为 execute(方案 c,review 保留独立阶段)。
|
|
74
|
+
LEGACY_PHASE_ALIASES: dict[str, str] = {"run": "execute", "test": "execute"}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def resolve_phase_name(phase: Any) -> str | None:
|
|
78
|
+
"""把可能是旧名的阶段解析成当前的工作流阶段名;不是工作流阶段则返回 None。"""
|
|
79
|
+
name = str(phase or "").strip()
|
|
80
|
+
if name in WORKFLOW_PHASES:
|
|
81
|
+
return name
|
|
82
|
+
mapped = LEGACY_PHASE_ALIASES.get(name)
|
|
83
|
+
return mapped if mapped in WORKFLOW_PHASES else None
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def classify_phase_name(phase: Any) -> str:
|
|
87
|
+
"""三态判定:``workflow`` | ``non_workflow`` | ``unknown``。
|
|
88
|
+
|
|
89
|
+
调用方需要区分这三者:``workflow`` 照常处理;``non_workflow`` 是别的子系统
|
|
90
|
+
的标注,跳过而不是报错;只有 ``unknown`` 才是真的有问题,值得让人看见。
|
|
91
|
+
"""
|
|
92
|
+
if resolve_phase_name(phase) is not None:
|
|
93
|
+
return "workflow"
|
|
94
|
+
if str(phase or "").strip() in KNOWN_NON_WORKFLOW_PHASES:
|
|
95
|
+
return "non_workflow"
|
|
96
|
+
return "unknown"
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _git_text(cwd: Path, *args: str) -> str | None:
|
|
100
|
+
proc = subprocess.run(
|
|
101
|
+
["git", *args],
|
|
102
|
+
cwd=str(cwd),
|
|
103
|
+
capture_output=True,
|
|
104
|
+
text=True,
|
|
105
|
+
encoding="utf-8",
|
|
106
|
+
errors="replace",
|
|
107
|
+
check=False,
|
|
108
|
+
)
|
|
109
|
+
if proc.returncode != 0:
|
|
110
|
+
return None
|
|
111
|
+
return proc.stdout.strip()
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def resolve_main_project_root(cwd: Path | None = None) -> Path:
|
|
115
|
+
"""Locate the main project root from a worktree or the main checkout."""
|
|
116
|
+
start = (cwd or Path.cwd()).resolve()
|
|
117
|
+
common_raw = _git_text(start, "rev-parse", "--git-common-dir")
|
|
118
|
+
if not common_raw:
|
|
119
|
+
return start
|
|
120
|
+
common = Path(common_raw)
|
|
121
|
+
if not common.is_absolute():
|
|
122
|
+
common = (start / common).resolve()
|
|
123
|
+
if common.name == ".git":
|
|
124
|
+
return common.parent
|
|
125
|
+
return start
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def resolve_worktree_root(cwd: Path | None = None) -> Path:
|
|
129
|
+
"""Top-level of the checkout containing ``cwd`` (worktree-aware)."""
|
|
130
|
+
start = (cwd or Path.cwd()).resolve()
|
|
131
|
+
top = _git_text(start, "rev-parse", "--show-toplevel")
|
|
132
|
+
if not top:
|
|
133
|
+
return start
|
|
134
|
+
return Path(top).resolve()
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _git_common_dir(cwd: Path) -> Path | None:
|
|
138
|
+
raw = _git_text(cwd, "rev-parse", "--git-common-dir")
|
|
139
|
+
if not raw:
|
|
140
|
+
return None
|
|
141
|
+
common = Path(raw)
|
|
142
|
+
if not common.is_absolute():
|
|
143
|
+
common = (cwd / common).resolve()
|
|
144
|
+
return common
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def common_root(project_root: Path) -> Path:
|
|
148
|
+
"""Resolve the shared "common" root for a project.
|
|
149
|
+
|
|
150
|
+
For a linked worktree, ``git rev-parse --git-common-dir`` points at the
|
|
151
|
+
main repository's ``.git`` directory; its parent is the main project root.
|
|
152
|
+
For the main worktree, the common dir is ``<root>/.git`` so the parent is
|
|
153
|
+
the project root itself. Falls back to the resolved input when not in a
|
|
154
|
+
git repo (e.g. exploratory scratch dirs).
|
|
155
|
+
"""
|
|
156
|
+
cwd = Path(project_root).resolve()
|
|
157
|
+
common = _git_common_dir(cwd)
|
|
158
|
+
if common is None:
|
|
159
|
+
return cwd
|
|
160
|
+
# common dir is <root>/.git — its parent is the main project root.
|
|
161
|
+
return common.parent.resolve()
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _root_commit(cwd: Path) -> str | None:
|
|
165
|
+
raw = _git_text(cwd, "rev-list", "--max-parents=0", "HEAD")
|
|
166
|
+
if not raw:
|
|
167
|
+
return None
|
|
168
|
+
return raw.splitlines()[0].strip()
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _normalize_remote(url: str) -> str:
|
|
172
|
+
"""Normalize a git remote URL to ``host/path`` lowercase form."""
|
|
173
|
+
text = url.strip()
|
|
174
|
+
text = re.sub(r"^git@", "", text)
|
|
175
|
+
text = text.replace(":", "/", 1) if "://" not in text else text
|
|
176
|
+
text = re.sub(r"^[a-zA-Z]+://", "", text)
|
|
177
|
+
text = re.sub(r"^[^/@]+@", "", text) # strip userinfo
|
|
178
|
+
text = text.lower()
|
|
179
|
+
if text.endswith(".git"):
|
|
180
|
+
text = text[: -len(".git")]
|
|
181
|
+
return text.rstrip("/")
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def _primary_remote_url(cwd: Path) -> str | None:
|
|
185
|
+
url = _git_text(cwd, "config", "--get", "remote.origin.url")
|
|
186
|
+
if url:
|
|
187
|
+
return url
|
|
188
|
+
remotes = _git_text(cwd, "remote")
|
|
189
|
+
if not remotes:
|
|
190
|
+
return None
|
|
191
|
+
first = remotes.splitlines()[0].strip()
|
|
192
|
+
return _git_text(cwd, "config", "--get", f"remote.{first}.url")
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def repository_identity(cwd: Path) -> str:
|
|
196
|
+
"""Stable repository identity.
|
|
197
|
+
|
|
198
|
+
Preferred: normalized primary remote identity + root commit.
|
|
199
|
+
Fallback (no remote): resolved git common-dir identity + root commit.
|
|
200
|
+
The identity never embeds a worktree absolute path, so linked worktrees of
|
|
201
|
+
one repository share the same id (RET-09).
|
|
202
|
+
"""
|
|
203
|
+
cwd = Path(cwd).resolve()
|
|
204
|
+
root_commit = _root_commit(cwd) or "no-root-commit"
|
|
205
|
+
remote = _primary_remote_url(cwd)
|
|
206
|
+
if remote:
|
|
207
|
+
basis = f"remote:{_normalize_remote(remote)}\n{root_commit}"
|
|
208
|
+
else:
|
|
209
|
+
common = _git_common_dir(cwd)
|
|
210
|
+
basis = f"local:{common}\n{root_commit}"
|
|
211
|
+
return "sha256:" + hashlib.sha256(basis.encode("utf-8")).hexdigest()
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def load_change_contract(contract_dir: Path) -> dict[str, Any]:
|
|
215
|
+
"""Load and validate ``meta/change-context.json`` from a contract dir."""
|
|
216
|
+
contract_dir = Path(contract_dir)
|
|
217
|
+
context_path = contract_dir / CHANGE_CONTEXT_REL
|
|
218
|
+
if not context_path.is_file():
|
|
219
|
+
raise FileNotFoundError(f"change context not found: {context_path}")
|
|
220
|
+
data = json.loads(context_path.read_text(encoding="utf-8-sig"))
|
|
221
|
+
if not isinstance(data, dict):
|
|
222
|
+
raise ValueError(f"change context must be an object: {context_path}")
|
|
223
|
+
lifecycle = data.get("lifecycle")
|
|
224
|
+
if isinstance(lifecycle, dict):
|
|
225
|
+
status = lifecycle.get("status")
|
|
226
|
+
if status is not None and status not in LIFECYCLE_STATUSES:
|
|
227
|
+
raise ValueError(
|
|
228
|
+
f"invalid lifecycle.status {status!r}; expected one of "
|
|
229
|
+
f"{sorted(LIFECYCLE_STATUSES)}"
|
|
230
|
+
)
|
|
231
|
+
return data
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def _unpack_v2_gate_policy(wrapper: Any) -> dict[str, Any] | None:
|
|
235
|
+
"""meta/plan-profile.json(v2 包装体)→ Python schemaVersion:1 形状。
|
|
236
|
+
|
|
237
|
+
门禁权威所需的四个字段(mode/planned_phases/required_gate_dag/
|
|
238
|
+
required_validations_by_phase)缺一即视为不完整快照(0.2.92-era 的发布
|
|
239
|
+
没有门禁 overlay),调用方回退工作副本。返回 None 表示不可用作权威。
|
|
240
|
+
"""
|
|
241
|
+
if not isinstance(wrapper, dict):
|
|
242
|
+
return None
|
|
243
|
+
if wrapper.get("artifact_type") != "gate_policy":
|
|
244
|
+
return None
|
|
245
|
+
content = wrapper.get("content")
|
|
246
|
+
if not isinstance(content, dict):
|
|
247
|
+
return None
|
|
248
|
+
planned = content.get("planned_phases")
|
|
249
|
+
dag = content.get("required_gate_dag")
|
|
250
|
+
by_phase = content.get("required_validations_by_phase")
|
|
251
|
+
if (
|
|
252
|
+
not isinstance(content.get("mode"), str)
|
|
253
|
+
or not (isinstance(planned, list) and all(isinstance(p, str) for p in planned))
|
|
254
|
+
or not isinstance(dag, dict)
|
|
255
|
+
or not isinstance(by_phase, dict)
|
|
256
|
+
):
|
|
257
|
+
return None
|
|
258
|
+
policy: dict[str, Any] = {
|
|
259
|
+
"schemaVersion": 1,
|
|
260
|
+
"tier": content.get("tier"),
|
|
261
|
+
"source": content.get("source"),
|
|
262
|
+
"plannedPhases": list(planned),
|
|
263
|
+
"requiredValidations": list(content.get("required_validations") or []),
|
|
264
|
+
"requiredValidationsByPhase": dict(by_phase),
|
|
265
|
+
"requiredGateDag": dict(dag),
|
|
266
|
+
}
|
|
267
|
+
return policy
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def load_change_gate_policy(change_dir: Path) -> dict[str, Any]:
|
|
271
|
+
"""v2-first 的每 change 门禁策略加载(权威切换 2026-08)。
|
|
272
|
+
|
|
273
|
+
meta/plan-profile.json(v2 发布快照,门禁字段已哈希绑定)优先于
|
|
274
|
+
meta/gate-policy.json(classify 在 0.5 写的工作副本)。v2 快照不完整
|
|
275
|
+
或缺失 → 回退工作副本。两者并存且 plannedPhases(canonical 归一去重
|
|
276
|
+
后)不一致 → drift 报告,策略以 v2 为准(已审批定稿 > 工作副本;
|
|
277
|
+
发布后改写工作副本本身就是异常)。
|
|
278
|
+
|
|
279
|
+
返回 ``{"policy": <schemaVersion:1 dict 或 None>, "source":
|
|
280
|
+
"v2-plan-profile" | "gate-policy-json" | None, "drift": dict | None,
|
|
281
|
+
"working_error": bool}``——``working_error`` 区分"工作副本不存在"与
|
|
282
|
+
"存在但不可读"(`_phase_plan` 的 legacy:policy-unreadable 语义靠它)。
|
|
283
|
+
"""
|
|
284
|
+
change_dir = Path(change_dir)
|
|
285
|
+
v2_policy: dict[str, Any] | None = None
|
|
286
|
+
v2_path = change_dir / "meta" / "plan-profile.json"
|
|
287
|
+
if v2_path.is_file():
|
|
288
|
+
try:
|
|
289
|
+
v2_policy = _unpack_v2_gate_policy(
|
|
290
|
+
json.loads(v2_path.read_text(encoding="utf-8-sig"))
|
|
291
|
+
)
|
|
292
|
+
except (OSError, ValueError, json.JSONDecodeError):
|
|
293
|
+
v2_policy = None
|
|
294
|
+
|
|
295
|
+
working: dict[str, Any] | None = None
|
|
296
|
+
working_error = False
|
|
297
|
+
working_path = change_dir / "meta" / "gate-policy.json"
|
|
298
|
+
if working_path.is_file():
|
|
299
|
+
try:
|
|
300
|
+
candidate = json.loads(working_path.read_text(encoding="utf-8-sig"))
|
|
301
|
+
# 工作副本保持与历史一致的宽松接受:任何 dict 都交给下游各自的
|
|
302
|
+
# 形状校验(effective_workflow_policy 查 schemaVersion,reconcile
|
|
303
|
+
# 查 requiredGateDag),加载器不提前收紧。
|
|
304
|
+
if isinstance(candidate, dict):
|
|
305
|
+
working = candidate
|
|
306
|
+
else:
|
|
307
|
+
working_error = True
|
|
308
|
+
except (OSError, ValueError, json.JSONDecodeError):
|
|
309
|
+
working_error = True
|
|
310
|
+
|
|
311
|
+
drift: dict[str, Any] | None = None
|
|
312
|
+
if v2_policy is not None and working is not None:
|
|
313
|
+
canonical = lambda names: sorted(
|
|
314
|
+
{name for raw in (names or []) if (name := resolve_phase_name(raw))}
|
|
315
|
+
)
|
|
316
|
+
v2_phases = canonical(v2_policy.get("plannedPhases"))
|
|
317
|
+
working_phases = canonical(working.get("plannedPhases"))
|
|
318
|
+
if v2_phases != working_phases:
|
|
319
|
+
drift = {
|
|
320
|
+
"kind": "plannedPhases",
|
|
321
|
+
"v2": v2_phases,
|
|
322
|
+
"gate_policy_json": working_phases,
|
|
323
|
+
"resolution": "v2-plan-profile wins (approved publication snapshot)",
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if v2_policy is not None:
|
|
327
|
+
return {
|
|
328
|
+
"policy": v2_policy,
|
|
329
|
+
"source": "v2-plan-profile",
|
|
330
|
+
"drift": drift,
|
|
331
|
+
"working_error": working_error,
|
|
332
|
+
}
|
|
333
|
+
if working is not None:
|
|
334
|
+
return {
|
|
335
|
+
"policy": working,
|
|
336
|
+
"source": "gate-policy-json",
|
|
337
|
+
"drift": None,
|
|
338
|
+
"working_error": False,
|
|
339
|
+
}
|
|
340
|
+
return {
|
|
341
|
+
"policy": None,
|
|
342
|
+
"source": None,
|
|
343
|
+
"drift": None,
|
|
344
|
+
"working_error": working_error,
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def contract_layout_kind(contract: dict[str, Any]) -> str:
|
|
349
|
+
"""``split-v1`` when the contract declares a separate runtime root."""
|
|
350
|
+
ownership = contract.get("stateOwnership")
|
|
351
|
+
if isinstance(ownership, dict) and ownership.get("runtimeRoot"):
|
|
352
|
+
return "split-v1"
|
|
353
|
+
return "legacy-colocated"
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def _resolve_runtime_root(main_root: Path, change_name: str, runtime_root: Any) -> Path:
|
|
357
|
+
raw = Path(str(runtime_root or ""))
|
|
358
|
+
if not str(runtime_root or "").strip() or raw.is_absolute():
|
|
359
|
+
raise ValueError("stateOwnership.runtimeRoot must be a project-relative path")
|
|
360
|
+
state_parent = (main_root / ".harness" / "state" / "changes").resolve()
|
|
361
|
+
resolved = (main_root / raw).resolve()
|
|
362
|
+
assert_path_within(resolved, state_parent)
|
|
363
|
+
expected = (state_parent / change_name).resolve()
|
|
364
|
+
if resolved != expected:
|
|
365
|
+
raise ValueError(
|
|
366
|
+
"stateOwnership.runtimeRoot must equal "
|
|
367
|
+
f".harness/state/changes/{change_name}"
|
|
368
|
+
)
|
|
369
|
+
return resolved
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def resolve_change_layout(
|
|
373
|
+
cwd_hint: Path, change_id_or_dir: str | Path
|
|
374
|
+
) -> dict[str, Any]:
|
|
375
|
+
"""Resolve the static/dynamic roots for a change.
|
|
376
|
+
|
|
377
|
+
``cwd_hint`` is any directory inside the checkout (main or linked
|
|
378
|
+
worktree); state always anchors at the main project root. Resolution is
|
|
379
|
+
read-only: legacy files are never moved here.
|
|
380
|
+
"""
|
|
381
|
+
hint = Path(cwd_hint).resolve()
|
|
382
|
+
main_root = resolve_main_project_root(hint)
|
|
383
|
+
worktree_root = resolve_worktree_root(hint)
|
|
384
|
+
|
|
385
|
+
if isinstance(change_id_or_dir, Path):
|
|
386
|
+
contract_dir = change_id_or_dir.resolve()
|
|
387
|
+
change_name = contract_dir.name
|
|
388
|
+
if not contract_dir.is_dir():
|
|
389
|
+
raise FileNotFoundError(f"change contract dir not found: {contract_dir}")
|
|
390
|
+
else:
|
|
391
|
+
change_name = str(change_id_or_dir)
|
|
392
|
+
contract_dir = (main_root / ".harness" / "changes" / change_name).resolve()
|
|
393
|
+
if not contract_dir.is_dir():
|
|
394
|
+
raise FileNotFoundError(f"change not found: {change_name}")
|
|
395
|
+
|
|
396
|
+
contract: dict[str, Any] = {}
|
|
397
|
+
if (contract_dir / CHANGE_CONTEXT_REL).is_file():
|
|
398
|
+
contract = load_change_contract(contract_dir)
|
|
399
|
+
|
|
400
|
+
layout_kind = contract_layout_kind(contract)
|
|
401
|
+
if layout_kind == "split-v1":
|
|
402
|
+
runtime_rel = contract["stateOwnership"]["runtimeRoot"]
|
|
403
|
+
state_root = _resolve_runtime_root(main_root, change_name, runtime_rel)
|
|
404
|
+
else:
|
|
405
|
+
state_root = contract_dir
|
|
406
|
+
|
|
407
|
+
return {
|
|
408
|
+
"schemaVersion": 1,
|
|
409
|
+
"changeName": change_name,
|
|
410
|
+
"repositoryId": repository_identity(main_root),
|
|
411
|
+
"projectRoot": str(main_root),
|
|
412
|
+
"worktreeRoot": str(worktree_root),
|
|
413
|
+
"contractRoot": str(contract_dir),
|
|
414
|
+
"stateRoot": str(state_root),
|
|
415
|
+
"layout": layout_kind,
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
def resolve_state_dir_for_contract(
|
|
420
|
+
contract_dir: Path, cwd: Path | None = None
|
|
421
|
+
) -> Path:
|
|
422
|
+
"""Return the dynamic-state root for a change contract dir.
|
|
423
|
+
|
|
424
|
+
split-v1 contracts resolve to ``.harness/state/changes/<id>`` under the
|
|
425
|
+
main project root. Legacy contracts, missing/invalid context files and
|
|
426
|
+
non-standard locations return the contract dir unchanged, so colocated
|
|
427
|
+
readers observe zero behaviour change.
|
|
428
|
+
"""
|
|
429
|
+
contract_dir = Path(contract_dir).resolve()
|
|
430
|
+
try:
|
|
431
|
+
contract = load_change_contract(contract_dir)
|
|
432
|
+
except (FileNotFoundError, ValueError, json.JSONDecodeError, OSError):
|
|
433
|
+
return contract_dir
|
|
434
|
+
if contract_layout_kind(contract) != "split-v1":
|
|
435
|
+
return contract_dir
|
|
436
|
+
main_root = resolve_main_project_root(cwd or contract_dir)
|
|
437
|
+
expected = (main_root / ".harness" / "changes" / contract_dir.name).resolve()
|
|
438
|
+
if contract_dir != expected:
|
|
439
|
+
return contract_dir
|
|
440
|
+
return _resolve_runtime_root(
|
|
441
|
+
main_root, contract_dir.name, contract["stateOwnership"]["runtimeRoot"]
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
def assert_path_within(
|
|
446
|
+
path: Path, allowed_root: Path, *, allow_root: bool = False
|
|
447
|
+
) -> Path:
|
|
448
|
+
"""Resolve ``path`` and require it to stay inside ``allowed_root``.
|
|
449
|
+
|
|
450
|
+
Rejects empty paths, parents, sibling-prefix attacks and symlink escapes.
|
|
451
|
+
Returns the resolved path on success.
|
|
452
|
+
"""
|
|
453
|
+
raw = str(path)
|
|
454
|
+
if not raw or not raw.strip():
|
|
455
|
+
raise ValueError("empty path is not allowed")
|
|
456
|
+
root = Path(allowed_root).resolve()
|
|
457
|
+
resolved = Path(path).resolve()
|
|
458
|
+
if resolved == root:
|
|
459
|
+
if allow_root:
|
|
460
|
+
return resolved
|
|
461
|
+
raise ValueError(f"path is the allowed root itself: {resolved}")
|
|
462
|
+
if not resolved.is_relative_to(root):
|
|
463
|
+
raise ValueError(f"path escapes allowed root: {resolved} not within {root}")
|
|
464
|
+
return resolved
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
class CleanupTopologyError(ValueError):
|
|
468
|
+
"""Raised when cleanup would delete or traverse into state/archive roots."""
|
|
469
|
+
|
|
470
|
+
code = "CLEANUP_TOPOLOGY_REFUSED"
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
# Skip descending into these when scanning for junction/symlink escapes.
|
|
474
|
+
# Matches integration HEAVY_WORKTREE_ROOTS plus .git (topology scan only).
|
|
475
|
+
CLEANUP_WALK_SKIP_DIRS = frozenset(
|
|
476
|
+
{
|
|
477
|
+
"node_modules",
|
|
478
|
+
".venv",
|
|
479
|
+
"venv",
|
|
480
|
+
"build",
|
|
481
|
+
"dist",
|
|
482
|
+
"target",
|
|
483
|
+
".cache",
|
|
484
|
+
"__pycache__",
|
|
485
|
+
".git",
|
|
486
|
+
}
|
|
487
|
+
)
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
def assert_cleanup_safe(
|
|
491
|
+
cleanup_root: Path,
|
|
492
|
+
state_roots: Sequence[Path] | None = None,
|
|
493
|
+
archive_roots: Sequence[Path] | None = None,
|
|
494
|
+
) -> Path:
|
|
495
|
+
"""Refuse cleanup when state/archive roots resolve inside the cleanup tree.
|
|
496
|
+
|
|
497
|
+
Also refuses when a junction/symlink under ``cleanup_root`` resolves into a
|
|
498
|
+
protected state/archive root (Windows junction / symlink escape).
|
|
499
|
+
"""
|
|
500
|
+
cleanup = Path(cleanup_root).resolve()
|
|
501
|
+
protected: list[Path] = []
|
|
502
|
+
for raw in [*(state_roots or ()), *(archive_roots or ())]:
|
|
503
|
+
try:
|
|
504
|
+
protected.append(Path(raw).resolve())
|
|
505
|
+
except OSError as exc:
|
|
506
|
+
raise CleanupTopologyError(
|
|
507
|
+
f"CLEANUP_TOPOLOGY_REFUSED: cannot resolve protected path {raw}: {exc}"
|
|
508
|
+
) from exc
|
|
509
|
+
|
|
510
|
+
for prot in protected:
|
|
511
|
+
if prot == cleanup or prot.is_relative_to(cleanup):
|
|
512
|
+
raise CleanupTopologyError(
|
|
513
|
+
"CLEANUP_TOPOLOGY_REFUSED: protected path "
|
|
514
|
+
f"{prot} is inside cleanup root {cleanup}"
|
|
515
|
+
)
|
|
516
|
+
|
|
517
|
+
if cleanup.is_dir():
|
|
518
|
+
for dirpath, dirnames, filenames in os.walk(cleanup, followlinks=False):
|
|
519
|
+
# Inspect this level (including heavy names) for link escapes, then
|
|
520
|
+
# prune descent so we do not walk install/cache trees.
|
|
521
|
+
entries = [*dirnames, *filenames]
|
|
522
|
+
dirnames[:] = [d for d in dirnames if d not in CLEANUP_WALK_SKIP_DIRS]
|
|
523
|
+
for name in entries:
|
|
524
|
+
child = Path(dirpath) / name
|
|
525
|
+
try:
|
|
526
|
+
is_link = child.is_symlink()
|
|
527
|
+
except OSError:
|
|
528
|
+
continue
|
|
529
|
+
# Junctions often report as directories; resolve and compare.
|
|
530
|
+
try:
|
|
531
|
+
resolved_child = child.resolve()
|
|
532
|
+
except OSError:
|
|
533
|
+
continue
|
|
534
|
+
escaped = not (
|
|
535
|
+
resolved_child == cleanup or resolved_child.is_relative_to(cleanup)
|
|
536
|
+
)
|
|
537
|
+
if not (is_link or escaped):
|
|
538
|
+
continue
|
|
539
|
+
for prot in protected:
|
|
540
|
+
if (
|
|
541
|
+
resolved_child == prot
|
|
542
|
+
or resolved_child.is_relative_to(prot)
|
|
543
|
+
or prot.is_relative_to(resolved_child)
|
|
544
|
+
):
|
|
545
|
+
raise CleanupTopologyError(
|
|
546
|
+
"CLEANUP_TOPOLOGY_REFUSED: cleanup path "
|
|
547
|
+
f"{child} resolves to protected {resolved_child}"
|
|
548
|
+
)
|
|
549
|
+
return cleanup
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
_FORMAL_SNAPSHOT_DIRS = ("plans", "spec", "evidence", "reports", "meta")
|
|
553
|
+
_FORMAL_SNAPSHOT_FILES = ("events.ndjson",)
|
|
554
|
+
|
|
555
|
+
|
|
556
|
+
def snapshot_change_formal_layer(
|
|
557
|
+
project_root: Path,
|
|
558
|
+
change_id: str,
|
|
559
|
+
*,
|
|
560
|
+
change_dir: Path | None = None,
|
|
561
|
+
) -> dict[str, Any]:
|
|
562
|
+
"""Copy change formal layer into ``.harness/cache/change-snapshots/<change>/``.
|
|
563
|
+
|
|
564
|
+
Destination is always under the main project / common harness root — never
|
|
565
|
+
under a feature worktree path alone.
|
|
566
|
+
"""
|
|
567
|
+
import shutil
|
|
568
|
+
|
|
569
|
+
project = resolve_main_project_root(Path(project_root))
|
|
570
|
+
src = Path(change_dir).resolve() if change_dir is not None else (
|
|
571
|
+
project / ".harness" / "changes" / change_id
|
|
572
|
+
).resolve()
|
|
573
|
+
if not src.is_dir():
|
|
574
|
+
raise FileNotFoundError(f"change dir not found for snapshot: {src}")
|
|
575
|
+
|
|
576
|
+
dest = (project / ".harness" / "cache" / "change-snapshots" / change_id).resolve()
|
|
577
|
+
# Hard guard: never write the snapshot inside the source tree itself.
|
|
578
|
+
if dest == src or dest.is_relative_to(src):
|
|
579
|
+
raise ValueError(
|
|
580
|
+
f"snapshot destination must not be inside change dir: {dest}"
|
|
581
|
+
)
|
|
582
|
+
dest.mkdir(parents=True, exist_ok=True)
|
|
583
|
+
|
|
584
|
+
copied: list[str] = []
|
|
585
|
+
for name in _FORMAL_SNAPSHOT_DIRS:
|
|
586
|
+
source_dir = src / name
|
|
587
|
+
if not source_dir.is_dir():
|
|
588
|
+
continue
|
|
589
|
+
target_dir = dest / name
|
|
590
|
+
shutil.copytree(source_dir, target_dir, dirs_exist_ok=True)
|
|
591
|
+
copied.append(name + "/")
|
|
592
|
+
for name in _FORMAL_SNAPSHOT_FILES:
|
|
593
|
+
source_file = src / name
|
|
594
|
+
if not source_file.is_file():
|
|
595
|
+
continue
|
|
596
|
+
shutil.copy2(source_file, dest / name)
|
|
597
|
+
copied.append(name)
|
|
598
|
+
|
|
599
|
+
file_hashes: dict[str, str] = {}
|
|
600
|
+
for path in sorted(dest.rglob("*")):
|
|
601
|
+
if not path.is_file():
|
|
602
|
+
continue
|
|
603
|
+
rel = path.relative_to(dest).as_posix()
|
|
604
|
+
if rel == "manifest.json":
|
|
605
|
+
continue
|
|
606
|
+
digest = hashlib.sha256(path.read_bytes()).hexdigest()
|
|
607
|
+
file_hashes[rel] = "sha256:" + digest
|
|
608
|
+
|
|
609
|
+
manifest = {
|
|
610
|
+
"schemaVersion": 1,
|
|
611
|
+
"changeId": change_id,
|
|
612
|
+
"source": str(src),
|
|
613
|
+
"destination": str(dest),
|
|
614
|
+
"projectRoot": str(project),
|
|
615
|
+
"copied": copied,
|
|
616
|
+
"files": file_hashes,
|
|
617
|
+
"fileCount": len(file_hashes),
|
|
618
|
+
}
|
|
619
|
+
manifest_path = dest / "manifest.json"
|
|
620
|
+
manifest_path.write_text(
|
|
621
|
+
json.dumps(manifest, ensure_ascii=False, indent=2) + "\n",
|
|
622
|
+
encoding="utf-8",
|
|
623
|
+
newline="\n",
|
|
624
|
+
)
|
|
625
|
+
return {
|
|
626
|
+
"ok": True,
|
|
627
|
+
"code": "CHANGE_SNAPSHOT_WRITTEN",
|
|
628
|
+
"changeId": change_id,
|
|
629
|
+
"destination": str(dest),
|
|
630
|
+
"fileCount": len(file_hashes),
|
|
631
|
+
"copied": copied,
|
|
632
|
+
"manifestPath": str(manifest_path),
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
|
|
636
|
+
def cmd_resolve_layout(args: argparse.Namespace) -> int:
|
|
637
|
+
layout = resolve_change_layout(Path.cwd(), args.change)
|
|
638
|
+
sys.stdout.write(json.dumps(layout, ensure_ascii=False, indent=2) + "\n")
|
|
639
|
+
return 0
|
|
640
|
+
|
|
641
|
+
|
|
642
|
+
def cmd_repository_id(args: argparse.Namespace) -> int:
|
|
643
|
+
identity = repository_identity(Path.cwd())
|
|
644
|
+
if args.json:
|
|
645
|
+
sys.stdout.write(
|
|
646
|
+
json.dumps({"ok": True, "repositoryId": identity}, ensure_ascii=False)
|
|
647
|
+
+ "\n"
|
|
648
|
+
)
|
|
649
|
+
else:
|
|
650
|
+
sys.stdout.write(identity + "\n")
|
|
651
|
+
return 0
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
655
|
+
parser = argparse.ArgumentParser(prog="harness_paths.py")
|
|
656
|
+
sub = parser.add_subparsers(dest="command_name", required=True)
|
|
657
|
+
|
|
658
|
+
p_layout = sub.add_parser("resolve-layout")
|
|
659
|
+
p_layout.add_argument("--change", required=True)
|
|
660
|
+
p_layout.set_defaults(func=cmd_resolve_layout)
|
|
661
|
+
|
|
662
|
+
p_id = sub.add_parser("repository-id")
|
|
663
|
+
p_id.add_argument("--json", action="store_true")
|
|
664
|
+
p_id.set_defaults(func=cmd_repository_id)
|
|
665
|
+
|
|
666
|
+
return parser
|
|
667
|
+
|
|
668
|
+
|
|
669
|
+
def main(argv: list[str] | None = None) -> int:
|
|
670
|
+
parser = build_parser()
|
|
671
|
+
args = parser.parse_args(argv)
|
|
672
|
+
return int(args.func(args))
|
|
673
|
+
|
|
674
|
+
|
|
675
|
+
if __name__ == "__main__":
|
|
676
|
+
raise SystemExit(main())
|