@hunter-harness/workflow-harness 0.2.1 → 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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +1 -1
- 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 +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 +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 +1 -1
- 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 +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 +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 +1 -1
- 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 +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 +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 +1 -1
- 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 +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 +45 -29
- package/harness/manifests/java/codebuddy.json +45 -29
- package/harness/manifests/java/codex.json +45 -29
- package/harness/manifests/java/cursor.json +45 -29
- package/hunter-workflow-family.json +2 -2
- package/package.json +1 -1
|
@@ -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)
|