@hunter-harness/workflow-harness 0.4.0 → 0.4.2
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-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 +29 -6
- package/harness/bundles/general/claude-code/scripts/harness_deploy.py +1 -1
- package/harness/bundles/general/claude-code/scripts/harness_knowledge_candidates.py +398 -0
- 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-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 +29 -6
- package/harness/bundles/general/codebuddy/scripts/harness_deploy.py +1 -1
- package/harness/bundles/general/codebuddy/scripts/harness_knowledge_candidates.py +398 -0
- 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-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 +29 -6
- package/harness/bundles/general/codex/scripts/harness_deploy.py +1 -1
- package/harness/bundles/general/codex/scripts/harness_knowledge_candidates.py +398 -0
- 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-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 +29 -6
- package/harness/bundles/general/cursor/scripts/harness_deploy.py +1 -1
- package/harness/bundles/general/cursor/scripts/harness_knowledge_candidates.py +398 -0
- 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 +157 -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 +166 -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-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 +29 -6
- package/harness/bundles/java/claude-code/scripts/harness_deploy.py +1 -1
- package/harness/bundles/java/claude-code/scripts/harness_knowledge_candidates.py +398 -0
- 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-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 +29 -6
- package/harness/bundles/java/codebuddy/scripts/harness_deploy.py +1 -1
- package/harness/bundles/java/codebuddy/scripts/harness_knowledge_candidates.py +398 -0
- 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-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 +29 -6
- package/harness/bundles/java/codex/scripts/harness_deploy.py +1 -1
- package/harness/bundles/java/codex/scripts/harness_knowledge_candidates.py +398 -0
- 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-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 +29 -6
- package/harness/bundles/java/cursor/scripts/harness_deploy.py +1 -1
- package/harness/bundles/java/cursor/scripts/harness_knowledge_candidates.py +398 -0
- 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 +157 -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 +166 -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 +17 -17
- package/harness/manifests/general/codebuddy.json +17 -17
- package/harness/manifests/general/codex.json +17 -17
- package/harness/manifests/general/cursor.json +17 -17
- package/harness/manifests/general/pi.json +475 -0
- package/harness/manifests/java/claude-code.json +19 -19
- package/harness/manifests/java/codebuddy.json +19 -19
- package/harness/manifests/java/codex.json +19 -19
- package/harness/manifests/java/cursor.json +19 -19
- package/harness/manifests/java/pi.json +499 -0
- package/hunter-workflow-family.json +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
"""harness_sync — 受管 sync runtime 清理器(变更簇 D / task 13,RET-36/37/38/39)。
|
|
2
|
+
|
|
3
|
+
每次 sync 使用 `.harness/runtime/sync/<run-id>/deploy/<agent>/` 并写
|
|
4
|
+
`owner.json`(runId/pid/processStart/startedAt/agent/purpose/expiresAt)。
|
|
5
|
+
正常、失败与异常退出均在 finally 清理本 run(`SyncRun` 上下文管理器 /
|
|
6
|
+
`finalize_run`)。启动时 `reap_stale_runs` 只回收同时满足以下条件的目录:
|
|
7
|
+
|
|
8
|
+
1. owner 进程已死亡(pid liveness + create-time 匹配,可注入);
|
|
9
|
+
2. 已超过 TTL(expiresAt < now);
|
|
10
|
+
3. 删除目标 resolved 后仍位于精确 sync runtime root 内;
|
|
11
|
+
4. 不是当前活跃 run。
|
|
12
|
+
|
|
13
|
+
owner 缺失的 legacy 目录默认只报告 `unverifiable`,不删除;显式
|
|
14
|
+
`--diagnose` 模式才处理。删除失败只产生带精确路径的 warning。
|
|
15
|
+
|
|
16
|
+
本模块不做任何 `.gitignore` / `git check-ignore` 判断(RET-34/COM-004),
|
|
17
|
+
不修改也不建议修改项目 Git 策略。
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import argparse
|
|
23
|
+
import json
|
|
24
|
+
import os
|
|
25
|
+
import re
|
|
26
|
+
import shutil
|
|
27
|
+
import sys
|
|
28
|
+
import uuid
|
|
29
|
+
from dataclasses import dataclass, field
|
|
30
|
+
from datetime import datetime, timedelta, timezone
|
|
31
|
+
from pathlib import Path
|
|
32
|
+
from typing import Any, Callable
|
|
33
|
+
|
|
34
|
+
SYNC_RUNTIME_REL = Path(".harness") / "runtime" / "sync"
|
|
35
|
+
OWNER_FILE = "owner.json"
|
|
36
|
+
OWNER_SCHEMA_VERSION = 1
|
|
37
|
+
DEFAULT_TTL_SECONDS = 3600
|
|
38
|
+
|
|
39
|
+
_RUN_ID_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]*$")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _now() -> datetime:
|
|
43
|
+
return datetime.now(timezone.utc)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _iso(value: datetime) -> str:
|
|
47
|
+
return value.astimezone(timezone.utc).isoformat()
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _parse_iso(raw: Any) -> datetime | None:
|
|
51
|
+
if not isinstance(raw, str) or not raw.strip():
|
|
52
|
+
return None
|
|
53
|
+
text = raw.strip()
|
|
54
|
+
if text.endswith("Z"):
|
|
55
|
+
text = text[:-1] + "+00:00"
|
|
56
|
+
try:
|
|
57
|
+
parsed = datetime.fromisoformat(text)
|
|
58
|
+
except ValueError:
|
|
59
|
+
return None
|
|
60
|
+
if parsed.tzinfo is None:
|
|
61
|
+
parsed = parsed.replace(tzinfo=timezone.utc)
|
|
62
|
+
return parsed.astimezone(timezone.utc)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def sync_runtime_root(project_root: Path | str) -> Path:
|
|
66
|
+
return Path(project_root) / SYNC_RUNTIME_REL
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _validate_run_id(run_id: str) -> str:
|
|
70
|
+
if not isinstance(run_id, str) or not _RUN_ID_RE.match(run_id):
|
|
71
|
+
raise ValueError(f"invalid run id: {run_id!r}")
|
|
72
|
+
if run_id in {".", ".."} or "/" in run_id or "\\" in run_id:
|
|
73
|
+
raise ValueError(f"run id must not escape sync root: {run_id!r}")
|
|
74
|
+
return run_id
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _process_start_marker() -> str:
|
|
78
|
+
"""Best-effort process create-time marker for owner liveness matching.
|
|
79
|
+
|
|
80
|
+
跨平台保守实现:boot 时间 + pid 足以区分同 pid 的不同世代进程。
|
|
81
|
+
"""
|
|
82
|
+
try:
|
|
83
|
+
with open("/proc/stat", "r", encoding="utf-8") as handle:
|
|
84
|
+
for line in handle:
|
|
85
|
+
if line.startswith("btime"):
|
|
86
|
+
return f"boot:{line.split()[1]}"
|
|
87
|
+
except OSError:
|
|
88
|
+
pass
|
|
89
|
+
return f"pid:{os.getpid()} boot:unknown"
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _default_pid_alive(pid: int, process_start: str | None) -> bool:
|
|
93
|
+
if pid <= 0:
|
|
94
|
+
return False
|
|
95
|
+
try:
|
|
96
|
+
os.kill(pid, 0)
|
|
97
|
+
except ProcessLookupError:
|
|
98
|
+
return False
|
|
99
|
+
except PermissionError:
|
|
100
|
+
return True
|
|
101
|
+
except OSError:
|
|
102
|
+
return False
|
|
103
|
+
return True
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@dataclass
|
|
107
|
+
class SyncRun:
|
|
108
|
+
"""Handle for one sync run; use as context manager for finally cleanup."""
|
|
109
|
+
|
|
110
|
+
project_root: Path
|
|
111
|
+
run_id: str
|
|
112
|
+
purpose: str
|
|
113
|
+
ttl_seconds: int = DEFAULT_TTL_SECONDS
|
|
114
|
+
keep_temp: bool = False
|
|
115
|
+
agents: list[str] = field(default_factory=list)
|
|
116
|
+
_finalized: bool = False
|
|
117
|
+
|
|
118
|
+
@property
|
|
119
|
+
def run_dir(self) -> Path:
|
|
120
|
+
return sync_runtime_root(self.project_root) / self.run_id
|
|
121
|
+
|
|
122
|
+
def workspace(self, agent: str) -> Path:
|
|
123
|
+
return self.run_dir / "deploy" / agent
|
|
124
|
+
|
|
125
|
+
def __enter__(self) -> "SyncRun":
|
|
126
|
+
return self
|
|
127
|
+
|
|
128
|
+
def __exit__(self, exc_type, exc, tb) -> bool:
|
|
129
|
+
finalize_run(self)
|
|
130
|
+
return False
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _write_json(path: Path, payload: dict[str, Any]) -> None:
|
|
134
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
135
|
+
path.write_text(
|
|
136
|
+
json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8"
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _read_json(path: Path) -> dict[str, Any] | None:
|
|
141
|
+
try:
|
|
142
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
143
|
+
except (OSError, json.JSONDecodeError):
|
|
144
|
+
return None
|
|
145
|
+
return data if isinstance(data, dict) else None
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def begin_run(
|
|
149
|
+
project_root: Path | str,
|
|
150
|
+
*,
|
|
151
|
+
run_id: str | None = None,
|
|
152
|
+
agent: str,
|
|
153
|
+
purpose: str,
|
|
154
|
+
ttl_seconds: int = DEFAULT_TTL_SECONDS,
|
|
155
|
+
keep_temp: bool = False,
|
|
156
|
+
) -> SyncRun:
|
|
157
|
+
"""Create `.harness/runtime/sync/<run-id>/deploy/<agent>/` + owner.json."""
|
|
158
|
+
root = Path(project_root)
|
|
159
|
+
run_id = _validate_run_id(run_id or f"sync-{uuid.uuid4().hex[:12]}")
|
|
160
|
+
run = SyncRun(
|
|
161
|
+
project_root=root,
|
|
162
|
+
run_id=run_id,
|
|
163
|
+
purpose=purpose,
|
|
164
|
+
ttl_seconds=ttl_seconds,
|
|
165
|
+
keep_temp=keep_temp,
|
|
166
|
+
agents=[agent],
|
|
167
|
+
)
|
|
168
|
+
workspace = run.workspace(agent)
|
|
169
|
+
workspace.mkdir(parents=True, exist_ok=True)
|
|
170
|
+
started = _now()
|
|
171
|
+
owner = {
|
|
172
|
+
"schemaVersion": OWNER_SCHEMA_VERSION,
|
|
173
|
+
"runId": run_id,
|
|
174
|
+
"pid": os.getpid(),
|
|
175
|
+
"processStart": _process_start_marker(),
|
|
176
|
+
"startedAt": _iso(started),
|
|
177
|
+
"agent": agent,
|
|
178
|
+
"agents": [agent],
|
|
179
|
+
"purpose": purpose,
|
|
180
|
+
"expiresAt": _iso(started + timedelta(seconds=ttl_seconds)),
|
|
181
|
+
}
|
|
182
|
+
_write_json(run.run_dir / OWNER_FILE, owner)
|
|
183
|
+
return run
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def register_agent_workspace(run: SyncRun, agent: str) -> Path:
|
|
187
|
+
"""Add another agent workspace under the same run-id (INT-008)."""
|
|
188
|
+
workspace = run.workspace(agent)
|
|
189
|
+
workspace.mkdir(parents=True, exist_ok=True)
|
|
190
|
+
if agent not in run.agents:
|
|
191
|
+
run.agents.append(agent)
|
|
192
|
+
owner_path = run.run_dir / OWNER_FILE
|
|
193
|
+
owner = _read_json(owner_path)
|
|
194
|
+
if owner is not None:
|
|
195
|
+
agents = owner.get("agents")
|
|
196
|
+
if not isinstance(agents, list):
|
|
197
|
+
agents = [owner.get("agent")]
|
|
198
|
+
if agent not in agents:
|
|
199
|
+
agents.append(agent)
|
|
200
|
+
owner["agents"] = agents
|
|
201
|
+
_write_json(owner_path, owner)
|
|
202
|
+
return workspace
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def finalize_run(run: SyncRun) -> None:
|
|
206
|
+
"""Remove this run's workspace unless keep_temp diagnostics requested it."""
|
|
207
|
+
if run._finalized:
|
|
208
|
+
return
|
|
209
|
+
run._finalized = True
|
|
210
|
+
if run.keep_temp:
|
|
211
|
+
return
|
|
212
|
+
run_dir = run.run_dir
|
|
213
|
+
if run_dir.exists():
|
|
214
|
+
shutil.rmtree(run_dir, ignore_errors=True)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def finalize_run_id(
|
|
218
|
+
project_root: Path | str, run_id: str, *, keep_temp: bool = False
|
|
219
|
+
) -> dict[str, Any]:
|
|
220
|
+
"""Finalize one exact CLI-managed run with the same boundary checks."""
|
|
221
|
+
run_id = _validate_run_id(run_id)
|
|
222
|
+
root = sync_runtime_root(project_root).resolve()
|
|
223
|
+
run_dir = (root / run_id).resolve()
|
|
224
|
+
if not _inside_resolved(run_dir, root) or run_dir == root:
|
|
225
|
+
raise ValueError(f"run path escapes sync root: {run_dir}")
|
|
226
|
+
existed = run_dir.is_dir()
|
|
227
|
+
if existed and not keep_temp:
|
|
228
|
+
shutil.rmtree(run_dir)
|
|
229
|
+
return {
|
|
230
|
+
"ok": True,
|
|
231
|
+
"action": "finalize",
|
|
232
|
+
"runId": run_id,
|
|
233
|
+
"deleted": existed and not keep_temp,
|
|
234
|
+
"kept": bool(existed and keep_temp),
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def _inside_resolved(child: Path, parent: Path) -> bool:
|
|
239
|
+
try:
|
|
240
|
+
resolved_child = child.resolve()
|
|
241
|
+
resolved_parent = parent.resolve()
|
|
242
|
+
except OSError:
|
|
243
|
+
return False
|
|
244
|
+
return resolved_child == resolved_parent or resolved_parent in resolved_child.parents
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def reap_stale_runs(
|
|
248
|
+
project_root: Path | str,
|
|
249
|
+
*,
|
|
250
|
+
now: datetime | None = None,
|
|
251
|
+
pid_alive: Callable[[int, str | None], bool] | None = None,
|
|
252
|
+
active_run_ids: set[str] | None = None,
|
|
253
|
+
diagnose: bool = False,
|
|
254
|
+
) -> dict[str, Any]:
|
|
255
|
+
"""Reap only dead-owner + past-TTL dirs inside the exact sync root.
|
|
256
|
+
|
|
257
|
+
Ownerless legacy dirs are reported as ``unverifiable`` and left alone
|
|
258
|
+
unless ``diagnose=True`` explicitly handles them.
|
|
259
|
+
"""
|
|
260
|
+
now = now or _now()
|
|
261
|
+
alive = pid_alive or _default_pid_alive
|
|
262
|
+
active = active_run_ids or set()
|
|
263
|
+
result: dict[str, Any] = {
|
|
264
|
+
"ok": True,
|
|
265
|
+
"reaped": [],
|
|
266
|
+
"skipped": [],
|
|
267
|
+
"unverifiable": [],
|
|
268
|
+
"warnings": [],
|
|
269
|
+
}
|
|
270
|
+
root = sync_runtime_root(project_root)
|
|
271
|
+
if not root.is_dir():
|
|
272
|
+
return result
|
|
273
|
+
|
|
274
|
+
for entry in sorted(root.iterdir()):
|
|
275
|
+
if not entry.is_dir():
|
|
276
|
+
continue
|
|
277
|
+
run_id = entry.name
|
|
278
|
+
# 删除目标必须 resolved 后仍位于精确 sync root 内(RET-39)。
|
|
279
|
+
if not _inside_resolved(entry, root):
|
|
280
|
+
result["warnings"].append(f"path escapes sync root, untouched: {entry}")
|
|
281
|
+
continue
|
|
282
|
+
if run_id in active:
|
|
283
|
+
result["skipped"].append(run_id)
|
|
284
|
+
continue
|
|
285
|
+
owner_path = entry / OWNER_FILE
|
|
286
|
+
owner = _read_json(owner_path) if owner_path.is_file() else None
|
|
287
|
+
if owner is None:
|
|
288
|
+
# owner 缺失的 legacy 目录:默认只报告 UNVERIFIABLE,不删除。
|
|
289
|
+
result["unverifiable"].append(run_id)
|
|
290
|
+
if diagnose:
|
|
291
|
+
try:
|
|
292
|
+
shutil.rmtree(entry)
|
|
293
|
+
result["reaped"].append(run_id)
|
|
294
|
+
except OSError as exc:
|
|
295
|
+
result["warnings"].append(f"failed to remove {entry}: {exc}")
|
|
296
|
+
continue
|
|
297
|
+
pid = owner.get("pid")
|
|
298
|
+
process_start = owner.get("processStart")
|
|
299
|
+
expires = _parse_iso(owner.get("expiresAt"))
|
|
300
|
+
owner_dead = not alive(int(pid) if isinstance(pid, int) else -1, process_start)
|
|
301
|
+
past_ttl = expires is not None and expires < now
|
|
302
|
+
if owner_dead and past_ttl:
|
|
303
|
+
try:
|
|
304
|
+
shutil.rmtree(entry)
|
|
305
|
+
result["reaped"].append(run_id)
|
|
306
|
+
except OSError as exc:
|
|
307
|
+
result["warnings"].append(f"failed to remove {entry}: {exc}")
|
|
308
|
+
else:
|
|
309
|
+
result["skipped"].append(run_id)
|
|
310
|
+
return result
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def cmd_reap(args: argparse.Namespace) -> int:
|
|
314
|
+
result = reap_stale_runs(
|
|
315
|
+
Path(args.project),
|
|
316
|
+
active_run_ids=set(args.active_run or []),
|
|
317
|
+
diagnose=bool(args.diagnose),
|
|
318
|
+
)
|
|
319
|
+
payload = {"action": "reap", **result}
|
|
320
|
+
if args.json:
|
|
321
|
+
print(json.dumps(payload, ensure_ascii=False))
|
|
322
|
+
else:
|
|
323
|
+
reaped = ", ".join(result["reaped"]) or "-"
|
|
324
|
+
skipped = ", ".join(result["skipped"]) or "-"
|
|
325
|
+
unverifiable = ", ".join(result["unverifiable"]) or "-"
|
|
326
|
+
print(f"reaped: {reaped}")
|
|
327
|
+
print(f"skipped: {skipped}")
|
|
328
|
+
print(f"unverifiable: {unverifiable}")
|
|
329
|
+
for warning in result["warnings"]:
|
|
330
|
+
print(f"warning: {warning}", file=sys.stderr)
|
|
331
|
+
return 0
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def cmd_begin(args: argparse.Namespace) -> int:
|
|
335
|
+
run = begin_run(
|
|
336
|
+
Path(args.project),
|
|
337
|
+
run_id=args.run_id,
|
|
338
|
+
agent=args.agent,
|
|
339
|
+
purpose=args.purpose,
|
|
340
|
+
ttl_seconds=args.ttl_seconds,
|
|
341
|
+
keep_temp=bool(args.keep_temp),
|
|
342
|
+
)
|
|
343
|
+
payload = {
|
|
344
|
+
"ok": True,
|
|
345
|
+
"action": "begin",
|
|
346
|
+
"runId": run.run_id,
|
|
347
|
+
"runDir": str(run.run_dir.resolve()),
|
|
348
|
+
"workspace": str(run.workspace(args.agent).resolve()),
|
|
349
|
+
}
|
|
350
|
+
print(json.dumps(payload, ensure_ascii=False) if args.json else payload["workspace"])
|
|
351
|
+
return 0
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def cmd_finalize(args: argparse.Namespace) -> int:
|
|
355
|
+
payload = finalize_run_id(
|
|
356
|
+
Path(args.project), args.run_id, keep_temp=bool(args.keep_temp)
|
|
357
|
+
)
|
|
358
|
+
print(json.dumps(payload, ensure_ascii=False) if args.json else payload["runId"])
|
|
359
|
+
return 0
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
363
|
+
parser = argparse.ArgumentParser(
|
|
364
|
+
prog="harness_sync",
|
|
365
|
+
description="managed sync runtime owner/TTL/finally cleaner",
|
|
366
|
+
)
|
|
367
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
368
|
+
p_begin = sub.add_parser("begin", help="create one owned sync runtime")
|
|
369
|
+
p_begin.add_argument("--project", required=True)
|
|
370
|
+
p_begin.add_argument("--run-id")
|
|
371
|
+
p_begin.add_argument("--agent", required=True)
|
|
372
|
+
p_begin.add_argument("--purpose", required=True)
|
|
373
|
+
p_begin.add_argument("--ttl-seconds", type=int, default=DEFAULT_TTL_SECONDS)
|
|
374
|
+
p_begin.add_argument("--keep-temp", action="store_true")
|
|
375
|
+
p_begin.add_argument("--json", action="store_true")
|
|
376
|
+
p_begin.set_defaults(func=cmd_begin)
|
|
377
|
+
p_finalize = sub.add_parser("finalize", help="remove one exact owned sync runtime")
|
|
378
|
+
p_finalize.add_argument("--project", required=True)
|
|
379
|
+
p_finalize.add_argument("--run-id", required=True)
|
|
380
|
+
p_finalize.add_argument("--keep-temp", action="store_true")
|
|
381
|
+
p_finalize.add_argument("--json", action="store_true")
|
|
382
|
+
p_finalize.set_defaults(func=cmd_finalize)
|
|
383
|
+
p_reap = sub.add_parser("reap", help="reap dead-owner past-TTL sync run dirs")
|
|
384
|
+
p_reap.add_argument("--project", required=True, help="project root path")
|
|
385
|
+
p_reap.add_argument("--json", action="store_true", help="emit JSON payload")
|
|
386
|
+
p_reap.add_argument(
|
|
387
|
+
"--diagnose",
|
|
388
|
+
action="store_true",
|
|
389
|
+
help="explicit diagnostics mode: also remove ownerless legacy dirs",
|
|
390
|
+
)
|
|
391
|
+
p_reap.add_argument(
|
|
392
|
+
"--active-run",
|
|
393
|
+
action="append",
|
|
394
|
+
default=[],
|
|
395
|
+
help="run id still active (never reaped); repeatable",
|
|
396
|
+
)
|
|
397
|
+
p_reap.set_defaults(func=cmd_reap)
|
|
398
|
+
return parser
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def main(argv: list[str] | None = None) -> int:
|
|
402
|
+
args = build_parser().parse_args(argv)
|
|
403
|
+
return int(args.func(args))
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
if __name__ == "__main__":
|
|
407
|
+
sys.exit(main())
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Controlled cleanup helper for test fixtures (retro §5.23).
|
|
3
|
+
|
|
4
|
+
Provides a host-policy-friendly entry point for removing gitignored test
|
|
5
|
+
fixtures (e.g. .pytest_data) within an execution root. Performs realpath
|
|
6
|
+
containment, rejects symlink/reparse escapes, lists exact counts before
|
|
7
|
+
deletion, and outputs a structured receipt.
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import argparse
|
|
12
|
+
import json
|
|
13
|
+
import os
|
|
14
|
+
import shutil
|
|
15
|
+
import sys
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
if hasattr(sys.stdout, "reconfigure"):
|
|
20
|
+
sys.stdout.reconfigure(encoding="utf-8")
|
|
21
|
+
if hasattr(sys.stderr, "reconfigure"):
|
|
22
|
+
sys.stderr.reconfigure(encoding="utf-8")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _resolve_within(execution_root: Path, rel: str) -> Path | None:
|
|
26
|
+
"""Resolve rel under execution_root, rejecting escapes and symlinks."""
|
|
27
|
+
if not rel or rel.startswith("/"):
|
|
28
|
+
return None
|
|
29
|
+
# Reject absolute paths (Windows drive letter or POSIX).
|
|
30
|
+
if len(rel) >= 2 and rel[1] == ":":
|
|
31
|
+
return None
|
|
32
|
+
parts = rel.replace("\\", "/").split("/")
|
|
33
|
+
if ".." in parts:
|
|
34
|
+
return None
|
|
35
|
+
candidate = (execution_root / rel).resolve(strict=False)
|
|
36
|
+
# Containment check: candidate must be under execution_root.
|
|
37
|
+
try:
|
|
38
|
+
candidate.relative_to(execution_root.resolve(strict=False))
|
|
39
|
+
except ValueError:
|
|
40
|
+
return None
|
|
41
|
+
# Reject symlink/reparse escape: if the path is a symlink, check its target.
|
|
42
|
+
if candidate.is_symlink():
|
|
43
|
+
target = Path(os.readlink(candidate)).resolve(strict=False)
|
|
44
|
+
try:
|
|
45
|
+
target.relative_to(execution_root.resolve(strict=False))
|
|
46
|
+
except ValueError:
|
|
47
|
+
return None
|
|
48
|
+
# Even if target is inside, a symlinked cleanup root is suspicious;
|
|
49
|
+
# reject to avoid following links into unexpected locations.
|
|
50
|
+
return None
|
|
51
|
+
return candidate
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _count_files(root: Path) -> tuple[int, int]:
|
|
55
|
+
"""Count files and total bytes under root."""
|
|
56
|
+
count = 0
|
|
57
|
+
total = 0
|
|
58
|
+
for dirpath, _dirs, files in os.walk(root):
|
|
59
|
+
for name in files:
|
|
60
|
+
fp = Path(dirpath) / name
|
|
61
|
+
try:
|
|
62
|
+
total += fp.stat().st_size
|
|
63
|
+
count += 1
|
|
64
|
+
except OSError:
|
|
65
|
+
continue
|
|
66
|
+
return count, total
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def cleanup(execution_root: Path, cleanup_roots: list[str]) -> dict[str, Any]:
|
|
70
|
+
"""Remove allowlisted cleanup roots within execution_root.
|
|
71
|
+
|
|
72
|
+
Returns a structured receipt. Any path escape, symlink escape, or
|
|
73
|
+
unallowed root results in ok=false with zero files removed.
|
|
74
|
+
"""
|
|
75
|
+
execution_root = Path(execution_root).resolve(strict=False)
|
|
76
|
+
if not execution_root.is_dir():
|
|
77
|
+
return {
|
|
78
|
+
"ok": False,
|
|
79
|
+
"code": "EXECUTION_ROOT_MISSING",
|
|
80
|
+
"executionRoot": str(execution_root),
|
|
81
|
+
"removedFiles": 0,
|
|
82
|
+
"removedBytes": 0,
|
|
83
|
+
"rejectedEscapes": [],
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
rejected: list[dict[str, str]] = []
|
|
87
|
+
removed_files = 0
|
|
88
|
+
removed_bytes = 0
|
|
89
|
+
cleaned: list[str] = []
|
|
90
|
+
|
|
91
|
+
for rel in cleanup_roots:
|
|
92
|
+
target = _resolve_within(execution_root, rel)
|
|
93
|
+
if target is None:
|
|
94
|
+
rejected.append({"path": rel, "reason": "PATH_ESCAPE_REJECTED"})
|
|
95
|
+
continue
|
|
96
|
+
if not target.exists():
|
|
97
|
+
# Already absent — idempotent reentry.
|
|
98
|
+
continue
|
|
99
|
+
# Re-check symlink after resolution (the parent might be a symlink).
|
|
100
|
+
if target.is_symlink():
|
|
101
|
+
rejected.append({"path": rel, "reason": "SYMLINK_ESCAPE_REJECTED"})
|
|
102
|
+
continue
|
|
103
|
+
# Count before deletion for the receipt.
|
|
104
|
+
try:
|
|
105
|
+
count, total = _count_files(target)
|
|
106
|
+
except OSError:
|
|
107
|
+
count, total = 0, 0
|
|
108
|
+
# Delete.
|
|
109
|
+
try:
|
|
110
|
+
shutil.rmtree(target)
|
|
111
|
+
except OSError as exc:
|
|
112
|
+
rejected.append({"path": rel, "reason": f"DELETE_FAILED: {exc}"})
|
|
113
|
+
continue
|
|
114
|
+
removed_files += count
|
|
115
|
+
removed_bytes += total
|
|
116
|
+
cleaned.append(rel)
|
|
117
|
+
|
|
118
|
+
if rejected:
|
|
119
|
+
return {
|
|
120
|
+
"ok": False,
|
|
121
|
+
"code": rejected[0]["reason"],
|
|
122
|
+
"executionRoot": str(execution_root),
|
|
123
|
+
"removedFiles": removed_files,
|
|
124
|
+
"removedBytes": removed_bytes,
|
|
125
|
+
"rejectedEscapes": rejected,
|
|
126
|
+
"cleaned": cleaned,
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if removed_files == 0 and not cleaned:
|
|
130
|
+
return {
|
|
131
|
+
"ok": True,
|
|
132
|
+
"code": "ALREADY_ABSENT",
|
|
133
|
+
"executionRoot": str(execution_root),
|
|
134
|
+
"removedFiles": 0,
|
|
135
|
+
"removedBytes": 0,
|
|
136
|
+
"rejectedEscapes": [],
|
|
137
|
+
"cleaned": [],
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
"ok": True,
|
|
142
|
+
"code": "CLEANUP_COMPLETE",
|
|
143
|
+
"executionRoot": str(execution_root),
|
|
144
|
+
"removedFiles": removed_files,
|
|
145
|
+
"removedBytes": removed_bytes,
|
|
146
|
+
"rejectedEscapes": [],
|
|
147
|
+
"cleaned": cleaned,
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def main(argv: list[str] | None = None) -> int:
|
|
152
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
153
|
+
sub = parser.add_subparsers(dest="action", required=True)
|
|
154
|
+
clean_parser = sub.add_parser("cleanup")
|
|
155
|
+
clean_parser.add_argument("--execution-root", required=True)
|
|
156
|
+
clean_parser.add_argument("--cleanup-roots", required=True, help="JSON array of relative paths")
|
|
157
|
+
clean_parser.add_argument("--json", action="store_true")
|
|
158
|
+
args = parser.parse_args(argv)
|
|
159
|
+
|
|
160
|
+
try:
|
|
161
|
+
roots = json.loads(args.cleanup_roots)
|
|
162
|
+
if not isinstance(roots, list):
|
|
163
|
+
raise ValueError("cleanup-roots must be a JSON array")
|
|
164
|
+
except (json.JSONDecodeError, ValueError) as exc:
|
|
165
|
+
result = {"ok": False, "code": "INVALID_CLEANUP_ROOTS", "error": str(exc)}
|
|
166
|
+
print(json.dumps(result, ensure_ascii=False, indent=2))
|
|
167
|
+
return 1
|
|
168
|
+
|
|
169
|
+
result = cleanup(Path(args.execution_root), roots)
|
|
170
|
+
print(json.dumps(result, ensure_ascii=False, indent=2 if args.json else None))
|
|
171
|
+
return 0 if result["ok"] else 1
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
if __name__ == "__main__":
|
|
175
|
+
raise SystemExit(main())
|