@hunter-harness/workflow-harness 0.2.1 → 0.2.3
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 +2 -8
- package/harness/bundles/general/claude-code/harness-codebase-map/SKILL.md +2 -1
- package/harness/bundles/general/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-knowledge-ingest/scripts/harness_knowledge.py +474 -122
- package/harness/bundles/general/claude-code/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-plan/SKILL.md +2 -1
- package/harness/bundles/general/claude-code/harness-review/SKILL.md +5 -1
- package/harness/bundles/general/claude-code/harness-run/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-run/checklist.md +1 -1
- package/harness/bundles/general/claude-code/harness-submit/SKILL.md +2 -2
- package/harness/bundles/general/claude-code/harness-submit/checklist.md +30 -6
- package/harness/bundles/general/claude-code/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-test/SKILL.md +2 -2
- package/harness/bundles/general/claude-code/harness-test/checklist.md +1 -1
- package/harness/bundles/general/claude-code/harness-test/pitfalls.md +16 -0
- package/harness/bundles/general/claude-code/harness-test/scripts/runtime-helpers.mjs +216 -0
- package/harness/bundles/general/claude-code/protocols/archive-report-protocol.md +1 -1
- package/harness/bundles/general/claude-code/protocols/ledger-protocol.md +55 -2
- package/harness/bundles/general/claude-code/protocols/report-pipeline-protocol.md +1 -8
- package/harness/bundles/general/claude-code/protocols/sensitive-info-protocol.md +13 -0
- package/harness/bundles/general/claude-code/protocols/state-layout-protocol.md +24 -0
- package/harness/bundles/general/claude-code/scripts/harness_archive.py +31 -14
- package/harness/bundles/general/claude-code/scripts/harness_check_gate.py +111 -0
- package/harness/bundles/general/claude-code/scripts/harness_deploy.py +179 -4
- package/harness/bundles/general/claude-code/scripts/harness_ledger.py +269 -3
- package/harness/bundles/general/claude-code/scripts/harness_preflight.py +72 -382
- package/harness/bundles/general/claude-code/scripts/harness_profile.py +742 -0
- package/harness/bundles/general/claude-code/scripts/harness_service.py +99 -2
- package/harness/bundles/general/claude-code/scripts/harness_state.py +179 -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-archive/reference.md +2 -8
- package/harness/bundles/general/codebuddy/harness-codebase-map/SKILL.md +2 -1
- package/harness/bundles/general/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-knowledge-ingest/scripts/harness_knowledge.py +474 -122
- package/harness/bundles/general/codebuddy/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-plan/SKILL.md +2 -1
- package/harness/bundles/general/codebuddy/harness-review/SKILL.md +5 -1
- package/harness/bundles/general/codebuddy/harness-run/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-run/checklist.md +1 -1
- package/harness/bundles/general/codebuddy/harness-submit/SKILL.md +2 -2
- package/harness/bundles/general/codebuddy/harness-submit/checklist.md +30 -6
- package/harness/bundles/general/codebuddy/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-test/SKILL.md +2 -2
- package/harness/bundles/general/codebuddy/harness-test/checklist.md +1 -1
- package/harness/bundles/general/codebuddy/harness-test/pitfalls.md +16 -0
- package/harness/bundles/general/codebuddy/harness-test/scripts/runtime-helpers.mjs +216 -0
- package/harness/bundles/general/codebuddy/protocols/archive-report-protocol.md +1 -1
- package/harness/bundles/general/codebuddy/protocols/ledger-protocol.md +55 -2
- package/harness/bundles/general/codebuddy/protocols/report-pipeline-protocol.md +1 -8
- package/harness/bundles/general/codebuddy/protocols/sensitive-info-protocol.md +13 -0
- package/harness/bundles/general/codebuddy/protocols/state-layout-protocol.md +24 -0
- package/harness/bundles/general/codebuddy/scripts/harness_archive.py +31 -14
- package/harness/bundles/general/codebuddy/scripts/harness_check_gate.py +111 -0
- package/harness/bundles/general/codebuddy/scripts/harness_deploy.py +179 -4
- package/harness/bundles/general/codebuddy/scripts/harness_ledger.py +269 -3
- package/harness/bundles/general/codebuddy/scripts/harness_preflight.py +72 -382
- package/harness/bundles/general/codebuddy/scripts/harness_profile.py +742 -0
- package/harness/bundles/general/codebuddy/scripts/harness_service.py +99 -2
- package/harness/bundles/general/codebuddy/scripts/harness_state.py +179 -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-archive/reference.md +2 -8
- package/harness/bundles/general/codex/harness-codebase-map/SKILL.md +2 -1
- package/harness/bundles/general/codex/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-knowledge-ingest/scripts/harness_knowledge.py +474 -122
- package/harness/bundles/general/codex/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-plan/SKILL.md +2 -1
- package/harness/bundles/general/codex/harness-review/SKILL.md +5 -1
- package/harness/bundles/general/codex/harness-run/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-run/checklist.md +1 -1
- package/harness/bundles/general/codex/harness-submit/SKILL.md +2 -2
- package/harness/bundles/general/codex/harness-submit/checklist.md +30 -6
- package/harness/bundles/general/codex/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-test/SKILL.md +2 -2
- package/harness/bundles/general/codex/harness-test/checklist.md +1 -1
- package/harness/bundles/general/codex/harness-test/pitfalls.md +16 -0
- package/harness/bundles/general/codex/harness-test/scripts/runtime-helpers.mjs +216 -0
- package/harness/bundles/general/codex/protocols/archive-report-protocol.md +1 -1
- package/harness/bundles/general/codex/protocols/ledger-protocol.md +55 -2
- package/harness/bundles/general/codex/protocols/report-pipeline-protocol.md +1 -8
- package/harness/bundles/general/codex/protocols/sensitive-info-protocol.md +13 -0
- package/harness/bundles/general/codex/protocols/state-layout-protocol.md +24 -0
- package/harness/bundles/general/codex/scripts/harness_archive.py +31 -14
- package/harness/bundles/general/codex/scripts/harness_check_gate.py +111 -0
- package/harness/bundles/general/codex/scripts/harness_deploy.py +179 -4
- package/harness/bundles/general/codex/scripts/harness_ledger.py +269 -3
- package/harness/bundles/general/codex/scripts/harness_preflight.py +72 -382
- package/harness/bundles/general/codex/scripts/harness_profile.py +742 -0
- package/harness/bundles/general/codex/scripts/harness_service.py +99 -2
- package/harness/bundles/general/codex/scripts/harness_state.py +179 -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-archive/reference.md +2 -8
- package/harness/bundles/general/cursor/harness-codebase-map/SKILL.md +2 -1
- package/harness/bundles/general/cursor/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-knowledge-ingest/scripts/harness_knowledge.py +474 -122
- package/harness/bundles/general/cursor/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-plan/SKILL.md +2 -1
- package/harness/bundles/general/cursor/harness-review/SKILL.md +5 -1
- package/harness/bundles/general/cursor/harness-run/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-run/checklist.md +1 -1
- package/harness/bundles/general/cursor/harness-submit/SKILL.md +2 -2
- package/harness/bundles/general/cursor/harness-submit/checklist.md +30 -6
- package/harness/bundles/general/cursor/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-test/SKILL.md +2 -2
- package/harness/bundles/general/cursor/harness-test/checklist.md +1 -1
- package/harness/bundles/general/cursor/harness-test/pitfalls.md +16 -0
- package/harness/bundles/general/cursor/harness-test/scripts/runtime-helpers.mjs +216 -0
- package/harness/bundles/general/cursor/protocols/archive-report-protocol.md +1 -1
- package/harness/bundles/general/cursor/protocols/ledger-protocol.md +55 -2
- package/harness/bundles/general/cursor/protocols/report-pipeline-protocol.md +1 -8
- package/harness/bundles/general/cursor/protocols/sensitive-info-protocol.md +13 -0
- package/harness/bundles/general/cursor/protocols/state-layout-protocol.md +24 -0
- package/harness/bundles/general/cursor/scripts/harness_archive.py +31 -14
- package/harness/bundles/general/cursor/scripts/harness_check_gate.py +111 -0
- package/harness/bundles/general/cursor/scripts/harness_deploy.py +179 -4
- package/harness/bundles/general/cursor/scripts/harness_ledger.py +269 -3
- package/harness/bundles/general/cursor/scripts/harness_preflight.py +72 -382
- package/harness/bundles/general/cursor/scripts/harness_profile.py +742 -0
- package/harness/bundles/general/cursor/scripts/harness_service.py +99 -2
- package/harness/bundles/general/cursor/scripts/harness_state.py +179 -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 +2 -8
- package/harness/bundles/java/claude-code/harness-codebase-map/SKILL.md +2 -1
- package/harness/bundles/java/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-knowledge-ingest/scripts/harness_knowledge.py +474 -122
- package/harness/bundles/java/claude-code/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-package/SKILL.md +3 -3
- package/harness/bundles/java/claude-code/harness-plan/SKILL.md +2 -1
- package/harness/bundles/java/claude-code/harness-review/SKILL.md +5 -1
- package/harness/bundles/java/claude-code/harness-run/SKILL.md +4 -4
- package/harness/bundles/java/claude-code/harness-run/checklist.md +3 -1
- package/harness/bundles/java/claude-code/harness-run/reference.md +6 -0
- package/harness/bundles/java/claude-code/harness-submit/SKILL.md +3 -3
- package/harness/bundles/java/claude-code/harness-submit/checklist.md +30 -6
- package/harness/bundles/java/claude-code/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-test/SKILL.md +5 -5
- package/harness/bundles/java/claude-code/harness-test/checklist.md +3 -1
- package/harness/bundles/java/claude-code/harness-test/pitfalls.md +16 -0
- package/harness/bundles/java/claude-code/harness-test/reference.md +2 -0
- package/harness/bundles/java/claude-code/harness-test/scripts/runtime-helpers.mjs +216 -0
- package/harness/bundles/java/claude-code/protocols/archive-report-protocol.md +1 -1
- package/harness/bundles/java/claude-code/protocols/ledger-protocol.md +55 -2
- package/harness/bundles/java/claude-code/protocols/report-pipeline-protocol.md +1 -8
- package/harness/bundles/java/claude-code/protocols/sensitive-info-protocol.md +13 -0
- package/harness/bundles/java/claude-code/protocols/state-layout-protocol.md +24 -0
- package/harness/bundles/java/claude-code/scripts/harness_archive.py +31 -14
- package/harness/bundles/java/claude-code/scripts/harness_check_gate.py +111 -0
- package/harness/bundles/java/claude-code/scripts/harness_deploy.py +179 -4
- package/harness/bundles/java/claude-code/scripts/harness_ledger.py +269 -3
- package/harness/bundles/java/claude-code/scripts/harness_preflight.py +72 -382
- package/harness/bundles/java/claude-code/scripts/harness_profile.py +742 -0
- package/harness/bundles/java/claude-code/scripts/harness_service.py +99 -2
- package/harness/bundles/java/claude-code/scripts/harness_state.py +179 -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-archive/reference.md +2 -8
- package/harness/bundles/java/codebuddy/harness-codebase-map/SKILL.md +2 -1
- package/harness/bundles/java/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-knowledge-ingest/scripts/harness_knowledge.py +474 -122
- package/harness/bundles/java/codebuddy/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-package/SKILL.md +3 -3
- package/harness/bundles/java/codebuddy/harness-plan/SKILL.md +2 -1
- package/harness/bundles/java/codebuddy/harness-review/SKILL.md +5 -1
- package/harness/bundles/java/codebuddy/harness-run/SKILL.md +4 -4
- package/harness/bundles/java/codebuddy/harness-run/checklist.md +3 -1
- package/harness/bundles/java/codebuddy/harness-run/reference.md +6 -0
- package/harness/bundles/java/codebuddy/harness-submit/SKILL.md +3 -3
- package/harness/bundles/java/codebuddy/harness-submit/checklist.md +30 -6
- package/harness/bundles/java/codebuddy/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-test/SKILL.md +5 -5
- package/harness/bundles/java/codebuddy/harness-test/checklist.md +3 -1
- package/harness/bundles/java/codebuddy/harness-test/pitfalls.md +16 -0
- package/harness/bundles/java/codebuddy/harness-test/reference.md +2 -0
- package/harness/bundles/java/codebuddy/harness-test/scripts/runtime-helpers.mjs +216 -0
- package/harness/bundles/java/codebuddy/protocols/archive-report-protocol.md +1 -1
- package/harness/bundles/java/codebuddy/protocols/ledger-protocol.md +55 -2
- package/harness/bundles/java/codebuddy/protocols/report-pipeline-protocol.md +1 -8
- package/harness/bundles/java/codebuddy/protocols/sensitive-info-protocol.md +13 -0
- package/harness/bundles/java/codebuddy/protocols/state-layout-protocol.md +24 -0
- package/harness/bundles/java/codebuddy/scripts/harness_archive.py +31 -14
- package/harness/bundles/java/codebuddy/scripts/harness_check_gate.py +111 -0
- package/harness/bundles/java/codebuddy/scripts/harness_deploy.py +179 -4
- package/harness/bundles/java/codebuddy/scripts/harness_ledger.py +269 -3
- package/harness/bundles/java/codebuddy/scripts/harness_preflight.py +72 -382
- package/harness/bundles/java/codebuddy/scripts/harness_profile.py +742 -0
- package/harness/bundles/java/codebuddy/scripts/harness_service.py +99 -2
- package/harness/bundles/java/codebuddy/scripts/harness_state.py +179 -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-archive/reference.md +2 -8
- package/harness/bundles/java/codex/harness-codebase-map/SKILL.md +2 -1
- package/harness/bundles/java/codex/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-knowledge-ingest/scripts/harness_knowledge.py +474 -122
- package/harness/bundles/java/codex/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-package/SKILL.md +3 -3
- package/harness/bundles/java/codex/harness-plan/SKILL.md +2 -1
- package/harness/bundles/java/codex/harness-review/SKILL.md +5 -1
- package/harness/bundles/java/codex/harness-run/SKILL.md +4 -4
- package/harness/bundles/java/codex/harness-run/checklist.md +3 -1
- package/harness/bundles/java/codex/harness-run/reference.md +6 -0
- package/harness/bundles/java/codex/harness-submit/SKILL.md +3 -3
- package/harness/bundles/java/codex/harness-submit/checklist.md +30 -6
- package/harness/bundles/java/codex/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-test/SKILL.md +5 -5
- package/harness/bundles/java/codex/harness-test/checklist.md +3 -1
- package/harness/bundles/java/codex/harness-test/pitfalls.md +16 -0
- package/harness/bundles/java/codex/harness-test/reference.md +2 -0
- package/harness/bundles/java/codex/harness-test/scripts/runtime-helpers.mjs +216 -0
- package/harness/bundles/java/codex/protocols/archive-report-protocol.md +1 -1
- package/harness/bundles/java/codex/protocols/ledger-protocol.md +55 -2
- package/harness/bundles/java/codex/protocols/report-pipeline-protocol.md +1 -8
- package/harness/bundles/java/codex/protocols/sensitive-info-protocol.md +13 -0
- package/harness/bundles/java/codex/protocols/state-layout-protocol.md +24 -0
- package/harness/bundles/java/codex/scripts/harness_archive.py +31 -14
- package/harness/bundles/java/codex/scripts/harness_check_gate.py +111 -0
- package/harness/bundles/java/codex/scripts/harness_deploy.py +179 -4
- package/harness/bundles/java/codex/scripts/harness_ledger.py +269 -3
- package/harness/bundles/java/codex/scripts/harness_preflight.py +72 -382
- package/harness/bundles/java/codex/scripts/harness_profile.py +742 -0
- package/harness/bundles/java/codex/scripts/harness_service.py +99 -2
- package/harness/bundles/java/codex/scripts/harness_state.py +179 -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-archive/reference.md +2 -8
- package/harness/bundles/java/cursor/harness-codebase-map/SKILL.md +2 -1
- package/harness/bundles/java/cursor/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-knowledge-ingest/scripts/harness_knowledge.py +474 -122
- package/harness/bundles/java/cursor/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-package/SKILL.md +3 -3
- package/harness/bundles/java/cursor/harness-plan/SKILL.md +2 -1
- package/harness/bundles/java/cursor/harness-review/SKILL.md +5 -1
- package/harness/bundles/java/cursor/harness-run/SKILL.md +4 -4
- package/harness/bundles/java/cursor/harness-run/checklist.md +3 -1
- package/harness/bundles/java/cursor/harness-run/reference.md +6 -0
- package/harness/bundles/java/cursor/harness-submit/SKILL.md +3 -3
- package/harness/bundles/java/cursor/harness-submit/checklist.md +30 -6
- package/harness/bundles/java/cursor/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-test/SKILL.md +5 -5
- package/harness/bundles/java/cursor/harness-test/checklist.md +3 -1
- package/harness/bundles/java/cursor/harness-test/pitfalls.md +16 -0
- package/harness/bundles/java/cursor/harness-test/reference.md +2 -0
- package/harness/bundles/java/cursor/harness-test/scripts/runtime-helpers.mjs +216 -0
- package/harness/bundles/java/cursor/protocols/archive-report-protocol.md +1 -1
- package/harness/bundles/java/cursor/protocols/ledger-protocol.md +55 -2
- package/harness/bundles/java/cursor/protocols/report-pipeline-protocol.md +1 -8
- package/harness/bundles/java/cursor/protocols/sensitive-info-protocol.md +13 -0
- package/harness/bundles/java/cursor/protocols/state-layout-protocol.md +24 -0
- package/harness/bundles/java/cursor/scripts/harness_archive.py +31 -14
- package/harness/bundles/java/cursor/scripts/harness_check_gate.py +111 -0
- package/harness/bundles/java/cursor/scripts/harness_deploy.py +179 -4
- package/harness/bundles/java/cursor/scripts/harness_ledger.py +269 -3
- package/harness/bundles/java/cursor/scripts/harness_preflight.py +72 -382
- package/harness/bundles/java/cursor/scripts/harness_profile.py +742 -0
- package/harness/bundles/java/cursor/scripts/harness_service.py +99 -2
- package/harness/bundles/java/cursor/scripts/harness_state.py +179 -0
- package/harness/manifests/general/claude-code.json +44 -28
- package/harness/manifests/general/codebuddy.json +44 -28
- package/harness/manifests/general/codex.json +44 -28
- package/harness/manifests/general/cursor.json +44 -28
- package/harness/manifests/java/claude-code.json +48 -32
- package/harness/manifests/java/codebuddy.json +48 -32
- package/harness/manifests/java/codex.json +48 -32
- package/harness/manifests/java/cursor.json +48 -32
- package/hunter-workflow-family.json +2 -2
- package/package.json +1 -1
|
@@ -24,14 +24,7 @@ python <skills-root>/scripts/harness_archive.py replay --archive-dir ".harness/a
|
|
|
24
24
|
python <skills-root>/scripts/harness_events.py append --change-dir ".harness/changes/<change-name>" --phase "<phase>" --type <type> [--note "..."]
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
```powershell
|
|
30
|
-
powershell.exe -NoProfile -Command "npx hunter-harness report collect --change-id '<change-name>' --json"
|
|
31
|
-
powershell.exe -NoProfile -Command "npx hunter-harness report validate --change-id '<change-name>' --json"
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
CLI 不可用时,agent 仍必须按本协议生成等价的 `summary-data.json` 并执行一致性校验,不得退回手写汇总。
|
|
27
|
+
归档报告流水线只有两个入口,均由 `harness_archive.py` 提供:`collect`/`render`/`validate` 是 `finalize` 的内嵌同进程步骤,**不再作为独立 CLI 暴露**(不存在 `report collect`/`report validate` 子命令)。`summary-data.json` 只能由 `finalize`(归档时)或 `replay`(回放时)生成;**禁止 agent 临场手写或拼装等价的 `summary-data.json`**。`harness_archive.py` 不可用时归档失败退出,不得退回手写汇总。
|
|
35
28
|
|
|
36
29
|
## events.ndjson
|
|
37
30
|
|
|
@@ -90,3 +90,16 @@ spring:
|
|
|
90
90
|
- API 测试报告的持久化记录中
|
|
91
91
|
|
|
92
92
|
> 对于 API 测试中获取的 token,在测试执行过程中可临时使用(如 Playwright 请求),但测试报告中必须用 `<TOKEN_REDACTED>` 替换。
|
|
93
|
+
|
|
94
|
+
## 6. profile / 规则 / Markdown 凭据扫描
|
|
95
|
+
|
|
96
|
+
profile `build-profile.json`、规则文档、Skill Markdown **不得包含凭据明文值**(spec §3.4 凭据边界:配置只含 env key、cache path、角色,不含值)。发布前用 `harness-test/scripts/runtime-helpers.mjs` 的 `findCredentialValues(text)` 扫描:
|
|
97
|
+
|
|
98
|
+
- 命中 `password/token/secret/accessKey/Authorization: Bearer/jdbc password=` 等明文值 → ❌FAIL,必须改为占位符 `<*_REDACTED>` 或 env 引用 `${ENV}` / `$ENV`。
|
|
99
|
+
- 占位符 `<TOKEN_REDACTED>` 与 env 引用 `${TEST_TOKEN}` / `$DB_PASSWORD` 不报(credential 配置允许 env key)。
|
|
100
|
+
|
|
101
|
+
```js
|
|
102
|
+
import { findCredentialValues } from '<skills-root>/harness-test/scripts/runtime-helpers.mjs';
|
|
103
|
+
const findings = findCredentialValues(fs.readFileSync('build-profile.json', 'utf8'));
|
|
104
|
+
if (findings.length > 0) { /* ❌FAIL: 列出 line/code/snippet,改为 env key 或占位符 */ }
|
|
105
|
+
```
|
|
@@ -98,6 +98,30 @@ description: harness .harness 状态目录分层协议。用于减少根目录
|
|
|
98
98
|
|
|
99
99
|
后续阶段应优先从该文件读取路径,避免手拼 change-name 导致路径拼写错误。
|
|
100
100
|
|
|
101
|
+
## state-snapshot.json(cluster 3 §3.6)
|
|
102
|
+
|
|
103
|
+
`meta/state-snapshot.json`(由 `harness_state.py` 写入)集中记录 project/worktree root、HEAD/base、profile/rules/map/knowledge/diff 各段指纹与相关文件。plan/run/test/review/submit 读取该快照;失效时由脚本刷新,**不得仅凭缓存跳过代码或验证门禁**。
|
|
104
|
+
|
|
105
|
+
schema:
|
|
106
|
+
|
|
107
|
+
```json
|
|
108
|
+
{
|
|
109
|
+
"schemaVersion": 1,
|
|
110
|
+
"generatedAt": "<iso>",
|
|
111
|
+
"changeName": "<change-name>",
|
|
112
|
+
"project": {"root": "<abs>"},
|
|
113
|
+
"worktree": {"root": "<abs>"},
|
|
114
|
+
"git": {"base": "<sha>", "head": "<sha>"},
|
|
115
|
+
"segments": {
|
|
116
|
+
"<segment>": {"fingerprint": "sha256:...", "files": ["..."], "capturedAt": "<iso>"}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
各段独立失效:`is_segment_stale(snapshot, segment, current_fingerprint)` 比较单段指纹;段不存在 → stale(需采集)。`refresh_segments(..., segments=["profile"])` 只重采受影响段,其他段保留原 capturedAt/fingerprint(缓存失效只重采受影响段)。
|
|
122
|
+
|
|
123
|
+
segment 的文件集由调用方(各 skill)决定:`capture_snapshot(..., segment_files={"profile": [...], "rules": [...]})`。snapshot 只负责采集 + 比对 + 失效,不负责发现文件。git 段记录 base/head;diff 段指纹由调用方按需用 `harness_ledger.compute_diff_hash` 采集后填入 segment_files。
|
|
124
|
+
|
|
101
125
|
## archive 结构
|
|
102
126
|
|
|
103
127
|
archive 后保持同样分层结构,不把 `.bak`、脚本、manifest、HTML 全部放在 archive 根目录。
|
|
@@ -102,11 +102,15 @@ def read_json(path: Path) -> Any:
|
|
|
102
102
|
|
|
103
103
|
def write_json(path: Path, data: Any) -> None:
|
|
104
104
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
105
|
+
text = json.dumps(data, ensure_ascii=False, indent=2) + "\n"
|
|
106
|
+
# 强制 LF,UTF-8 无 BOM;原子写 temp+os.replace(与 runtime-helpers.mjs 一致)。
|
|
107
|
+
tmp = path.with_name(f".{path.name}.{os.getpid()}.tmp")
|
|
108
|
+
try:
|
|
109
|
+
tmp.write_text(text, encoding="utf-8", newline="\n")
|
|
110
|
+
os.replace(tmp, path)
|
|
111
|
+
except BaseException:
|
|
112
|
+
tmp.unlink(missing_ok=True)
|
|
113
|
+
raise
|
|
110
114
|
|
|
111
115
|
|
|
112
116
|
def sha256_file(path: Path) -> str:
|
|
@@ -490,16 +494,29 @@ def check_status(change_dir: Path) -> dict[str, Any]:
|
|
|
490
494
|
if head_hash.startswith(expected_hash) or expected_hash.startswith(head_hash[:7]):
|
|
491
495
|
checks["final_hash_match"] = True
|
|
492
496
|
else:
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
),
|
|
501
|
-
}
|
|
497
|
+
# main may have advanced since this change merged (a later change
|
|
498
|
+
# merged on top); the change's final hash is still pushed as long as
|
|
499
|
+
# it is an ancestor of HEAD. Strict == would block archiving whenever
|
|
500
|
+
# the repo moved on, which is the normal case in a multi-change
|
|
501
|
+
# workflow.
|
|
502
|
+
anc_code, _, _ = git_run(
|
|
503
|
+
project, "merge-base", "--is-ancestor", expected_hash, head_hash
|
|
502
504
|
)
|
|
505
|
+
if anc_code == 0:
|
|
506
|
+
checks["final_hash_match"] = True
|
|
507
|
+
checks["final_hash_ancestor"] = True
|
|
508
|
+
else:
|
|
509
|
+
checks["final_hash_match"] = False
|
|
510
|
+
blockers.append(
|
|
511
|
+
{
|
|
512
|
+
"code": "final-hash-mismatch",
|
|
513
|
+
"message": (
|
|
514
|
+
f"HEAD={head_hash} != expected={expected_hash} "
|
|
515
|
+
f"(source={hash_source}) and expected is not an ancestor "
|
|
516
|
+
f"of HEAD (not pushed?)"
|
|
517
|
+
),
|
|
518
|
+
}
|
|
519
|
+
)
|
|
503
520
|
elif head_hash and expected_hash is None:
|
|
504
521
|
warnings.append(
|
|
505
522
|
{
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Pre-push gate: skip `npm run check` when a recent check-ok marker matches
|
|
3
|
+
the tree of the commit being pushed.
|
|
4
|
+
|
|
5
|
+
Triple check (all must pass to skip, else exit 1 → hook runs npm run check):
|
|
6
|
+
1. marker exists and its `ts` is within MAX_AGE_S (10 min) of now
|
|
7
|
+
2. marker `command` == EXPECTED_CMD ("npm run check")
|
|
8
|
+
3. current `git rev-parse HEAD^{tree}` == marker `treeHash`
|
|
9
|
+
|
|
10
|
+
The marker is written by harness-submit M5 after a green `npm run check` and
|
|
11
|
+
records `git write-tree`. This remains valid after commit because the new HEAD
|
|
12
|
+
has the same tree. Any mismatch (no marker, stale, command or tree changed)
|
|
13
|
+
forces a full re-run — the safe default. This is a local convenience only;
|
|
14
|
+
non-harness pushes have no marker and always run the check.
|
|
15
|
+
|
|
16
|
+
Exit 0 = skip (recent verified), exit 1 = run npm run check.
|
|
17
|
+
"""
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import json
|
|
21
|
+
import subprocess
|
|
22
|
+
import sys
|
|
23
|
+
import time
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
|
|
26
|
+
ROOT = Path(__file__).resolve().parents[2] # harness/scripts/../.. = repo root
|
|
27
|
+
MARKER = ROOT / ".harness" / "check-ok.marker"
|
|
28
|
+
MAX_AGE_S = 600 # 10 minutes
|
|
29
|
+
EXPECTED_CMD = "npm run check"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _git_output(args: list[str]) -> str | None:
|
|
33
|
+
try:
|
|
34
|
+
r = subprocess.run(
|
|
35
|
+
["git", *args],
|
|
36
|
+
cwd=str(ROOT),
|
|
37
|
+
capture_output=True,
|
|
38
|
+
text=True,
|
|
39
|
+
timeout=5,
|
|
40
|
+
)
|
|
41
|
+
except (OSError, subprocess.SubprocessError):
|
|
42
|
+
return None
|
|
43
|
+
return r.stdout.strip() if r.returncode == 0 else None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _current_index_tree() -> str | None:
|
|
47
|
+
return _git_output(["write-tree"])
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _current_head_tree() -> str | None:
|
|
51
|
+
return _git_output(["rev-parse", "HEAD^{tree}"])
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def write_marker() -> int:
|
|
55
|
+
"""Record the verified index tree so the marker survives the next commit."""
|
|
56
|
+
tree = _current_index_tree()
|
|
57
|
+
if tree is None:
|
|
58
|
+
return 1
|
|
59
|
+
MARKER.parent.mkdir(parents=True, exist_ok=True)
|
|
60
|
+
payload = {"ts": time.time(), "treeHash": tree, "command": EXPECTED_CMD}
|
|
61
|
+
MARKER.write_text(
|
|
62
|
+
json.dumps(payload, ensure_ascii=False) + "\n",
|
|
63
|
+
encoding="utf-8",
|
|
64
|
+
newline="\n",
|
|
65
|
+
)
|
|
66
|
+
print(f"check-ok marker written for tree {tree[:7]}")
|
|
67
|
+
return 0
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def main(argv: list[str] | None = None) -> int:
|
|
71
|
+
import argparse
|
|
72
|
+
|
|
73
|
+
ap = argparse.ArgumentParser(description="pre-push check gate")
|
|
74
|
+
ap.add_argument(
|
|
75
|
+
"--write",
|
|
76
|
+
action="store_true",
|
|
77
|
+
help="write a check-ok marker for current HEAD (harness-submit M5)",
|
|
78
|
+
)
|
|
79
|
+
args = ap.parse_args(argv)
|
|
80
|
+
if args.write:
|
|
81
|
+
return write_marker()
|
|
82
|
+
if not MARKER.is_file():
|
|
83
|
+
return 1
|
|
84
|
+
try:
|
|
85
|
+
data = json.loads(MARKER.read_text(encoding="utf-8-sig"))
|
|
86
|
+
except (OSError, json.JSONDecodeError):
|
|
87
|
+
return 1
|
|
88
|
+
if not isinstance(data, dict):
|
|
89
|
+
return 1
|
|
90
|
+
ts = data.get("ts")
|
|
91
|
+
if not isinstance(ts, (int, float)) or time.time() - ts > MAX_AGE_S:
|
|
92
|
+
return 1
|
|
93
|
+
if data.get("command") != EXPECTED_CMD:
|
|
94
|
+
return 1
|
|
95
|
+
marker_tree = data.get("treeHash")
|
|
96
|
+
if not isinstance(marker_tree, str):
|
|
97
|
+
return 1
|
|
98
|
+
tree = _current_head_tree()
|
|
99
|
+
if tree is None or tree != marker_tree:
|
|
100
|
+
return 1
|
|
101
|
+
# All three checks passed: a green npm run check ran on this exact HEAD
|
|
102
|
+
# within the last 10 minutes. Safe to skip.
|
|
103
|
+
print(
|
|
104
|
+
f"pre-push: skipping npm run check (verified {int(time.time() - ts)}s ago "
|
|
105
|
+
f"for tree {tree[:7]})"
|
|
106
|
+
)
|
|
107
|
+
return 0
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
if __name__ == "__main__":
|
|
111
|
+
sys.exit(main())
|
|
@@ -14,6 +14,7 @@ import json
|
|
|
14
14
|
import os
|
|
15
15
|
import re
|
|
16
16
|
import shutil
|
|
17
|
+
import subprocess
|
|
17
18
|
import sys
|
|
18
19
|
import uuid
|
|
19
20
|
from pathlib import Path
|
|
@@ -33,6 +34,25 @@ HEADING_RE = re.compile(r"^(#{2,6})\s+(.+?)\s*$")
|
|
|
33
34
|
FRAGMENT_HINT_RE = re.compile(r"^>\s*片段:\[\[shared/[^\]]+\]\]\s*.*$")
|
|
34
35
|
|
|
35
36
|
BUILD_MARKER = ".harness-build.json"
|
|
37
|
+
# Install-side managed manifests record which files a prior install owned, so a
|
|
38
|
+
# later conservative install can delete removed managed files while preserving
|
|
39
|
+
# user-owned files (design §3.8 要点4). Skills manifest lives in the skills
|
|
40
|
+
# target dir; agents manifest lives in .claude/agents.
|
|
41
|
+
MANIFEST_NAME = ".harness-managed.json"
|
|
42
|
+
AGENTS_MANAGED_NAME = ".harness-managed-agents.json"
|
|
43
|
+
INSTALL_METADATA = frozenset({MANIFEST_NAME, AGENTS_MANAGED_NAME})
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _read_managed_set(path: Path) -> set[str]:
|
|
47
|
+
"""Return the ``managedFiles`` set from a prior install manifest, or empty."""
|
|
48
|
+
if not path.is_file():
|
|
49
|
+
return set()
|
|
50
|
+
try:
|
|
51
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
52
|
+
except (json.JSONDecodeError, ValueError):
|
|
53
|
+
return set()
|
|
54
|
+
files = data.get("managedFiles") if isinstance(data, dict) else None
|
|
55
|
+
return set(files) if isinstance(files, list) else set()
|
|
36
56
|
|
|
37
57
|
SKIP_DIR_NAMES = {
|
|
38
58
|
"__pycache__",
|
|
@@ -270,6 +290,48 @@ def validate_build_paths(skills_root: Path, out_dir: Path) -> tuple[Path, Path]:
|
|
|
270
290
|
return skills_root, out_dir
|
|
271
291
|
|
|
272
292
|
|
|
293
|
+
def _git_repo_root(start: Path) -> Path | None:
|
|
294
|
+
"""Return the git toplevel containing ``start``, or None if not a git repo."""
|
|
295
|
+
try:
|
|
296
|
+
r = subprocess.run(
|
|
297
|
+
["git", "rev-parse", "--show-toplevel"],
|
|
298
|
+
cwd=str(start),
|
|
299
|
+
capture_output=True,
|
|
300
|
+
text=True,
|
|
301
|
+
timeout=5,
|
|
302
|
+
)
|
|
303
|
+
except (OSError, subprocess.SubprocessError):
|
|
304
|
+
return None
|
|
305
|
+
if r.returncode != 0 or not r.stdout.strip():
|
|
306
|
+
return None
|
|
307
|
+
return Path(r.stdout.strip())
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def _git_blob_sha(path: Path, repo_root: Path) -> str | None:
|
|
311
|
+
"""Return the git blob sha for a tracked file (CRLF-normalized by .gitattributes),
|
|
312
|
+
or None if untracked / git unavailable. Caller falls back to working-copy sha256
|
|
313
|
+
for untracked build inputs (deterministic enough outside git's normalization)."""
|
|
314
|
+
try:
|
|
315
|
+
rel = path.resolve().relative_to(repo_root.resolve())
|
|
316
|
+
except ValueError:
|
|
317
|
+
return None
|
|
318
|
+
try:
|
|
319
|
+
r = subprocess.run(
|
|
320
|
+
["git", "ls-files", "-s", "--", rel.as_posix()],
|
|
321
|
+
cwd=str(repo_root),
|
|
322
|
+
capture_output=True,
|
|
323
|
+
text=True,
|
|
324
|
+
timeout=5,
|
|
325
|
+
)
|
|
326
|
+
except (OSError, subprocess.SubprocessError):
|
|
327
|
+
return None
|
|
328
|
+
if r.returncode != 0 or not r.stdout.strip():
|
|
329
|
+
return None
|
|
330
|
+
# format: "<mode> <sha> <stage>\t<path>"
|
|
331
|
+
parts = r.stdout.strip().splitlines()[0].split()
|
|
332
|
+
return parts[1] if len(parts) >= 2 else None
|
|
333
|
+
|
|
334
|
+
|
|
273
335
|
def core_content_hash(
|
|
274
336
|
skills_root: Path,
|
|
275
337
|
overlay_dir: Path | None,
|
|
@@ -298,11 +360,18 @@ def core_content_hash(
|
|
|
298
360
|
files.extend(p for p in overlay_dir.rglob("*") if p.is_file())
|
|
299
361
|
if agent_overlay_dir and agent_overlay_dir.is_dir():
|
|
300
362
|
files.extend(p for p in agent_overlay_dir.rglob("*") if p.is_file())
|
|
363
|
+
skills_root_resolved = skills_root.resolve()
|
|
364
|
+
repo_root = _git_repo_root(skills_root_resolved)
|
|
301
365
|
for f in sorted(set(files)):
|
|
302
|
-
rel = f.resolve().relative_to(
|
|
366
|
+
rel = f.resolve().relative_to(skills_root_resolved).as_posix()
|
|
303
367
|
h.update(rel.encode("utf-8"))
|
|
304
368
|
h.update(b"\0")
|
|
305
|
-
|
|
369
|
+
# Prefer the git blob sha: .gitattributes eol=lf normalization makes the
|
|
370
|
+
# hash independent of working-copy CRLF state (the CRLF working-copy drift
|
|
371
|
+
# that previously caused core hash mismatches across main/worktree). Fall
|
|
372
|
+
# back to working-tree bytes for untracked build inputs (templates, etc.).
|
|
373
|
+
blob = _git_blob_sha(f, repo_root) if repo_root else None
|
|
374
|
+
h.update((blob or sha256_file(f)).encode("ascii"))
|
|
306
375
|
h.update(b"\0")
|
|
307
376
|
return h.hexdigest()[:16]
|
|
308
377
|
|
|
@@ -560,10 +629,37 @@ def cmd_install(build_out: Path, project: Path, target: Path | None) -> dict[str
|
|
|
560
629
|
dest = (target or project / ".claude" / "skills").resolve()
|
|
561
630
|
if not (build_out / BUILD_MARKER).is_file():
|
|
562
631
|
raise ValueError(f"install source is not a marked harness build: {build_out}")
|
|
632
|
+
|
|
633
|
+
# Managed set = real files in the build (exclude build marker). Recorded so a
|
|
634
|
+
# later conservative install can delete removed managed files and preserve
|
|
635
|
+
# user-owned files (design §3.8 要点4). Paths are relative to dest, which
|
|
636
|
+
# mirrors build_out (agents/ included because copy_tree keeps it there).
|
|
637
|
+
managed_new = {p for p in collect_files(build_out) if p != BUILD_MARKER}
|
|
638
|
+
managed_agents_new = {
|
|
639
|
+
p[len("agents/"):]
|
|
640
|
+
for p in managed_new
|
|
641
|
+
if p.startswith("agents/") and "/" not in p[len("agents/"):]
|
|
642
|
+
}
|
|
643
|
+
|
|
563
644
|
backup: str | None = None
|
|
564
645
|
staging = dest.parent / f".{dest.name}.staging-{uuid.uuid4().hex[:8]}"
|
|
565
646
|
shutil.copytree(build_out, staging)
|
|
566
647
|
if dest.exists() and any(dest.iterdir()):
|
|
648
|
+
managed_old = _read_managed_set(dest / MANIFEST_NAME)
|
|
649
|
+
# Preserve user-owned files: anything in dest that was NOT managed by a
|
|
650
|
+
# prior install is copied into staging (never overwriting managed files
|
|
651
|
+
# that staging already carries from build_out). Removed managed files
|
|
652
|
+
# (in managed_old but not managed_new) are simply not carried over.
|
|
653
|
+
for rel in collect_files(dest):
|
|
654
|
+
if rel in (BUILD_MARKER, MANIFEST_NAME) or rel in INSTALL_METADATA:
|
|
655
|
+
continue
|
|
656
|
+
if rel in managed_old:
|
|
657
|
+
continue
|
|
658
|
+
dst = staging / rel
|
|
659
|
+
if dst.exists():
|
|
660
|
+
continue
|
|
661
|
+
dst.parent.mkdir(parents=True, exist_ok=True)
|
|
662
|
+
shutil.copy2(dest / rel, dst)
|
|
567
663
|
stamp = dt.datetime.now().strftime("%Y%m%d-%H%M%S")
|
|
568
664
|
backup_path = dest.parent / f"skills-backup-{stamp}"
|
|
569
665
|
os.replace(dest, backup_path)
|
|
@@ -576,12 +672,41 @@ def cmd_install(build_out: Path, project: Path, target: Path | None) -> dict[str
|
|
|
576
672
|
os.replace(Path(backup), dest)
|
|
577
673
|
raise
|
|
578
674
|
|
|
675
|
+
# Record the managed set so the next conservative install can distinguish
|
|
676
|
+
# managed vs user-owned files.
|
|
677
|
+
(dest / MANIFEST_NAME).write_text(
|
|
678
|
+
json.dumps(
|
|
679
|
+
{"schemaVersion": 1, "managedFiles": sorted(managed_new)},
|
|
680
|
+
ensure_ascii=False,
|
|
681
|
+
indent=2,
|
|
682
|
+
)
|
|
683
|
+
+ "\n",
|
|
684
|
+
encoding="utf-8",
|
|
685
|
+
newline="\n",
|
|
686
|
+
)
|
|
687
|
+
|
|
579
688
|
agents_src = build_out / "agents"
|
|
580
689
|
agents_dest = project / ".claude" / "agents"
|
|
581
690
|
if agents_src.is_dir():
|
|
582
691
|
agents_dest.mkdir(parents=True, exist_ok=True)
|
|
692
|
+
agents_old = _read_managed_set(agents_dest / AGENTS_MANAGED_NAME)
|
|
693
|
+
# Delete managed agents removed since the prior install; user agents
|
|
694
|
+
# (never in a managed set) are left untouched.
|
|
695
|
+
for name in list(agents_old):
|
|
696
|
+
if name not in managed_agents_new:
|
|
697
|
+
(agents_dest / name).unlink(missing_ok=True)
|
|
583
698
|
for agent in agents_src.glob("*.md"):
|
|
584
699
|
shutil.copy2(agent, agents_dest / agent.name)
|
|
700
|
+
(agents_dest / AGENTS_MANAGED_NAME).write_text(
|
|
701
|
+
json.dumps(
|
|
702
|
+
{"schemaVersion": 1, "managedFiles": sorted(managed_agents_new)},
|
|
703
|
+
ensure_ascii=False,
|
|
704
|
+
indent=2,
|
|
705
|
+
)
|
|
706
|
+
+ "\n",
|
|
707
|
+
encoding="utf-8",
|
|
708
|
+
newline="\n",
|
|
709
|
+
)
|
|
585
710
|
|
|
586
711
|
return {
|
|
587
712
|
"ok": True,
|
|
@@ -613,11 +738,14 @@ def cmd_diff(build_out: Path, project: Path, target: Path | None) -> dict[str, A
|
|
|
613
738
|
current = collect_files(installed)
|
|
614
739
|
outdated = [p for p, h in built.items() if p in current and current[p] != h]
|
|
615
740
|
missing = [p for p in built if p not in current]
|
|
616
|
-
extra
|
|
741
|
+
# extra includes install-side managed manifests only as metadata, so they
|
|
742
|
+
# are excluded from the extra set; every other untracked file is a real
|
|
743
|
+
# extra and must mark the install stale (design §3.8 要点3).
|
|
744
|
+
extra = [p for p in current if p not in built and p not in INSTALL_METADATA]
|
|
617
745
|
return {
|
|
618
746
|
"ok": True,
|
|
619
747
|
"action": "diff",
|
|
620
|
-
"stale": bool(outdated or missing),
|
|
748
|
+
"stale": bool(outdated or missing or extra),
|
|
621
749
|
"outdated": sorted(outdated),
|
|
622
750
|
"missing": sorted(missing),
|
|
623
751
|
"extra": sorted(extra),
|
|
@@ -625,6 +753,35 @@ def cmd_diff(build_out: Path, project: Path, target: Path | None) -> dict[str, A
|
|
|
625
753
|
}
|
|
626
754
|
|
|
627
755
|
|
|
756
|
+
def cmd_validate_manifest(
|
|
757
|
+
bundle_dir: Path, manifest_entries: list[dict[str, Any]]
|
|
758
|
+
) -> dict[str, Any]:
|
|
759
|
+
"""Compare a bundle directory's actual file set against the manifest's
|
|
760
|
+
declared set. Both missing (declared but absent) and extra (present but
|
|
761
|
+
undeclared) fail validation (design §3.8 要点2)."""
|
|
762
|
+
bundle_dir = bundle_dir.resolve()
|
|
763
|
+
actual = collect_files(bundle_dir)
|
|
764
|
+
declared: dict[str, str] = {}
|
|
765
|
+
for entry in manifest_entries:
|
|
766
|
+
path = entry.get("path")
|
|
767
|
+
sha = entry.get("sha256")
|
|
768
|
+
if isinstance(path, str) and isinstance(sha, str):
|
|
769
|
+
declared[path] = sha
|
|
770
|
+
missing = sorted(p for p in declared if p not in actual)
|
|
771
|
+
extra = sorted(p for p in actual if p not in declared)
|
|
772
|
+
hash_mismatch = sorted(
|
|
773
|
+
p for p in declared if p in actual and actual[p] != declared[p]
|
|
774
|
+
)
|
|
775
|
+
return {
|
|
776
|
+
"ok": not (missing or extra or hash_mismatch),
|
|
777
|
+
"action": "validate-manifest",
|
|
778
|
+
"missing": missing,
|
|
779
|
+
"extra": extra,
|
|
780
|
+
"hashMismatch": hash_mismatch,
|
|
781
|
+
"comparedAt": now_iso(),
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
|
|
628
785
|
def build_parser() -> argparse.ArgumentParser:
|
|
629
786
|
p = argparse.ArgumentParser(description="Harness skills deploy (build/install/diff)")
|
|
630
787
|
sub = p.add_subparsers(dest="command", required=True)
|
|
@@ -647,6 +804,13 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
647
804
|
d.add_argument("--project", type=Path, required=True)
|
|
648
805
|
d.add_argument("--target", type=Path)
|
|
649
806
|
d.add_argument("--json", action="store_true")
|
|
807
|
+
|
|
808
|
+
v = sub.add_parser(
|
|
809
|
+
"validate-manifest", help="Validate bundle dir vs manifest (missing/extra/hash)"
|
|
810
|
+
)
|
|
811
|
+
v.add_argument("--bundle", type=Path, required=True)
|
|
812
|
+
v.add_argument("--manifest", type=Path, required=True)
|
|
813
|
+
v.add_argument("--json", action="store_true")
|
|
650
814
|
return p
|
|
651
815
|
|
|
652
816
|
|
|
@@ -662,6 +826,17 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
662
826
|
if args.command == "diff":
|
|
663
827
|
result = cmd_diff(args.from_dir, args.project, args.target)
|
|
664
828
|
return emit_json(result) if args.json else 0
|
|
829
|
+
if args.command == "validate-manifest":
|
|
830
|
+
manifest_data = json.loads(args.manifest.read_text(encoding="utf-8"))
|
|
831
|
+
entries = (
|
|
832
|
+
manifest_data.get("files", [])
|
|
833
|
+
if isinstance(manifest_data, dict)
|
|
834
|
+
else []
|
|
835
|
+
)
|
|
836
|
+
result = cmd_validate_manifest(args.bundle, entries)
|
|
837
|
+
if args.json:
|
|
838
|
+
return emit_json(result)
|
|
839
|
+
return 0 if result["ok"] else 1
|
|
665
840
|
except (FileNotFoundError, KeyError, RuntimeError, ValueError) as exc:
|
|
666
841
|
if getattr(args, "json", False):
|
|
667
842
|
return emit_json({"ok": False, "error": str(exc)}, ok=False)
|