@hunter-harness/workflow-harness 0.2.0 → 0.2.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-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 +35 -21
- package/harness/bundles/general/claude-code/harness-knowledge-ingest/reference.md +53 -7
- package/harness/bundles/general/claude-code/harness-knowledge-ingest/scripts/harness_knowledge.py +521 -125
- package/harness/bundles/general/claude-code/harness-knowledge-ingest/scripts/harness_knowledge_mcp.py +2 -2
- 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 +2 -2
- 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 +103 -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 +35 -21
- package/harness/bundles/general/codebuddy/harness-knowledge-ingest/reference.md +53 -7
- package/harness/bundles/general/codebuddy/harness-knowledge-ingest/scripts/harness_knowledge.py +521 -125
- package/harness/bundles/general/codebuddy/harness-knowledge-ingest/scripts/harness_knowledge_mcp.py +2 -2
- 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 +2 -2
- 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 +103 -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 +35 -21
- package/harness/bundles/general/codex/harness-knowledge-ingest/reference.md +53 -7
- package/harness/bundles/general/codex/harness-knowledge-ingest/scripts/harness_knowledge.py +521 -125
- package/harness/bundles/general/codex/harness-knowledge-ingest/scripts/harness_knowledge_mcp.py +2 -2
- 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 +2 -2
- 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 +103 -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 +35 -21
- package/harness/bundles/general/cursor/harness-knowledge-ingest/reference.md +53 -7
- package/harness/bundles/general/cursor/harness-knowledge-ingest/scripts/harness_knowledge.py +521 -125
- package/harness/bundles/general/cursor/harness-knowledge-ingest/scripts/harness_knowledge_mcp.py +2 -2
- 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 +2 -2
- 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 +103 -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 +35 -21
- package/harness/bundles/java/claude-code/harness-knowledge-ingest/reference.md +53 -7
- package/harness/bundles/java/claude-code/harness-knowledge-ingest/scripts/harness_knowledge.py +521 -125
- package/harness/bundles/java/claude-code/harness-knowledge-ingest/scripts/harness_knowledge_mcp.py +2 -2
- 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 +1 -1
- 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 +2 -2
- package/harness/bundles/java/claude-code/harness-test/SKILL.md +5 -5
- package/harness/bundles/java/claude-code/harness-test/pitfalls.md +16 -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 +103 -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 +35 -21
- package/harness/bundles/java/codebuddy/harness-knowledge-ingest/reference.md +53 -7
- package/harness/bundles/java/codebuddy/harness-knowledge-ingest/scripts/harness_knowledge.py +521 -125
- package/harness/bundles/java/codebuddy/harness-knowledge-ingest/scripts/harness_knowledge_mcp.py +2 -2
- 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 +1 -1
- 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 +2 -2
- package/harness/bundles/java/codebuddy/harness-test/SKILL.md +5 -5
- package/harness/bundles/java/codebuddy/harness-test/pitfalls.md +16 -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 +103 -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 +35 -21
- package/harness/bundles/java/codex/harness-knowledge-ingest/reference.md +53 -7
- package/harness/bundles/java/codex/harness-knowledge-ingest/scripts/harness_knowledge.py +521 -125
- package/harness/bundles/java/codex/harness-knowledge-ingest/scripts/harness_knowledge_mcp.py +2 -2
- 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 +1 -1
- 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 +2 -2
- package/harness/bundles/java/codex/harness-test/SKILL.md +5 -5
- package/harness/bundles/java/codex/harness-test/pitfalls.md +16 -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 +103 -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 +35 -21
- package/harness/bundles/java/cursor/harness-knowledge-ingest/reference.md +53 -7
- package/harness/bundles/java/cursor/harness-knowledge-ingest/scripts/harness_knowledge.py +521 -125
- package/harness/bundles/java/cursor/harness-knowledge-ingest/scripts/harness_knowledge_mcp.py +2 -2
- 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 +1 -1
- 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 +2 -2
- package/harness/bundles/java/cursor/harness-test/SKILL.md +5 -5
- package/harness/bundles/java/cursor/harness-test/pitfalls.md +16 -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 +103 -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 +46 -30
- package/harness/manifests/general/codebuddy.json +46 -30
- package/harness/manifests/general/codex.json +46 -30
- package/harness/manifests/general/cursor.json +46 -30
- package/harness/manifests/java/claude-code.json +47 -31
- package/harness/manifests/java/codebuddy.json +47 -31
- package/harness/manifests/java/codex.json +47 -31
- package/harness/manifests/java/cursor.json +47 -31
- package/hunter-workflow-family.json +2 -2
- package/package.json +1 -1
|
@@ -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,103 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Pre-push gate: skip `npm run check` when a recent check-ok marker matches
|
|
3
|
+
the current HEAD.
|
|
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` == marker `commitHash`
|
|
9
|
+
|
|
10
|
+
The marker is written by harness-submit M5 right after a green `npm run check`.
|
|
11
|
+
Any mismatch (no marker, stale, different command, HEAD moved since the check)
|
|
12
|
+
forces a full re-run — the safe default. This is a local convenience only;
|
|
13
|
+
non-harness pushes have no marker and always run the check.
|
|
14
|
+
|
|
15
|
+
Exit 0 = skip (recent verified), exit 1 = run npm run check.
|
|
16
|
+
"""
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import json
|
|
20
|
+
import subprocess
|
|
21
|
+
import sys
|
|
22
|
+
import time
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
|
|
25
|
+
ROOT = Path(__file__).resolve().parents[2] # harness/scripts/../.. = repo root
|
|
26
|
+
MARKER = ROOT / ".harness" / "check-ok.marker"
|
|
27
|
+
MAX_AGE_S = 600 # 10 minutes
|
|
28
|
+
EXPECTED_CMD = "npm run check"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _current_head() -> str | None:
|
|
32
|
+
try:
|
|
33
|
+
r = subprocess.run(
|
|
34
|
+
["git", "rev-parse", "HEAD"],
|
|
35
|
+
cwd=str(ROOT),
|
|
36
|
+
capture_output=True,
|
|
37
|
+
text=True,
|
|
38
|
+
timeout=5,
|
|
39
|
+
)
|
|
40
|
+
except (OSError, subprocess.SubprocessError):
|
|
41
|
+
return None
|
|
42
|
+
return r.stdout.strip() if r.returncode == 0 else None
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def write_marker() -> int:
|
|
46
|
+
"""Write a check-ok marker for the current HEAD (called by harness-submit M5
|
|
47
|
+
after a green npm run check). Pre-push gate reads it to skip a re-run."""
|
|
48
|
+
head = _current_head()
|
|
49
|
+
if head is None:
|
|
50
|
+
return 1
|
|
51
|
+
MARKER.parent.mkdir(parents=True, exist_ok=True)
|
|
52
|
+
payload = {"ts": time.time(), "commitHash": head, "command": EXPECTED_CMD}
|
|
53
|
+
MARKER.write_text(
|
|
54
|
+
json.dumps(payload, ensure_ascii=False) + "\n",
|
|
55
|
+
encoding="utf-8",
|
|
56
|
+
newline="\n",
|
|
57
|
+
)
|
|
58
|
+
print(f"check-ok marker written for HEAD {head[:7]}")
|
|
59
|
+
return 0
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def main(argv: list[str] | None = None) -> int:
|
|
63
|
+
import argparse
|
|
64
|
+
|
|
65
|
+
ap = argparse.ArgumentParser(description="pre-push check gate")
|
|
66
|
+
ap.add_argument(
|
|
67
|
+
"--write",
|
|
68
|
+
action="store_true",
|
|
69
|
+
help="write a check-ok marker for current HEAD (harness-submit M5)",
|
|
70
|
+
)
|
|
71
|
+
args = ap.parse_args(argv)
|
|
72
|
+
if args.write:
|
|
73
|
+
return write_marker()
|
|
74
|
+
if not MARKER.is_file():
|
|
75
|
+
return 1
|
|
76
|
+
try:
|
|
77
|
+
data = json.loads(MARKER.read_text(encoding="utf-8-sig"))
|
|
78
|
+
except (OSError, json.JSONDecodeError):
|
|
79
|
+
return 1
|
|
80
|
+
if not isinstance(data, dict):
|
|
81
|
+
return 1
|
|
82
|
+
ts = data.get("ts")
|
|
83
|
+
if not isinstance(ts, (int, float)) or time.time() - ts > MAX_AGE_S:
|
|
84
|
+
return 1
|
|
85
|
+
if data.get("command") != EXPECTED_CMD:
|
|
86
|
+
return 1
|
|
87
|
+
marker_commit = data.get("commitHash")
|
|
88
|
+
if not isinstance(marker_commit, str):
|
|
89
|
+
return 1
|
|
90
|
+
head = _current_head()
|
|
91
|
+
if head is None or head != marker_commit:
|
|
92
|
+
return 1
|
|
93
|
+
# All three checks passed: a green npm run check ran on this exact HEAD
|
|
94
|
+
# within the last 10 minutes. Safe to skip.
|
|
95
|
+
print(
|
|
96
|
+
f"pre-push: skipping npm run check (verified {int(time.time() - ts)}s ago "
|
|
97
|
+
f"at HEAD {head[:7]})"
|
|
98
|
+
)
|
|
99
|
+
return 0
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
if __name__ == "__main__":
|
|
103
|
+
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)
|