@hunter-harness/workflow-harness 0.2.11 → 0.2.13
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/contracts/ci-metrics.schema.json +36 -0
- package/harness/bundles/general/claude-code/contracts/workflow-policy.json +30 -0
- package/harness/bundles/general/claude-code/harness-archive/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-plan/SKILL.md +4 -4
- package/harness/bundles/general/claude-code/harness-plan/checklist.md +6 -0
- package/harness/bundles/general/claude-code/harness-plan/reference.md +7 -2
- package/harness/bundles/general/claude-code/harness-review/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-run/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-run/checklist.md +3 -2
- package/harness/bundles/general/claude-code/harness-run/reference.md +6 -4
- package/harness/bundles/general/claude-code/harness-submit/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-sync/SKILL.md +2 -2
- package/harness/bundles/general/claude-code/harness-sync/reference.md +5 -4
- package/harness/bundles/general/claude-code/harness-test/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/scripts/harness_archive.py +282 -120
- package/harness/bundles/general/claude-code/scripts/harness_events.py +400 -24
- package/harness/bundles/general/claude-code/scripts/harness_gate.py +585 -21
- package/harness/bundles/general/claude-code/scripts/harness_integration.py +403 -18
- package/harness/bundles/general/claude-code/scripts/harness_ledger.py +225 -29
- package/harness/bundles/general/claude-code/scripts/harness_phase.py +926 -0
- package/harness/bundles/general/claude-code/scripts/harness_plan_finalize.py +361 -0
- package/harness/bundles/general/claude-code/scripts/harness_profile.py +50 -0
- package/harness/bundles/general/claude-code/scripts/harness_runtime.py +248 -0
- package/harness/bundles/general/claude-code/scripts/harness_state.py +15 -2
- package/harness/bundles/general/claude-code/scripts/harness_test_guard.py +188 -0
- package/harness/bundles/general/claude-code/scripts/harness_workflow_policy.py +19 -0
- package/harness/bundles/general/codebuddy/.harness-build.json +1 -1
- package/harness/bundles/general/codebuddy/contracts/ci-metrics.schema.json +36 -0
- package/harness/bundles/general/codebuddy/contracts/workflow-policy.json +30 -0
- package/harness/bundles/general/codebuddy/harness-archive/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-plan/SKILL.md +4 -4
- package/harness/bundles/general/codebuddy/harness-plan/checklist.md +6 -0
- package/harness/bundles/general/codebuddy/harness-plan/reference.md +7 -2
- package/harness/bundles/general/codebuddy/harness-review/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-run/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-run/checklist.md +3 -2
- package/harness/bundles/general/codebuddy/harness-run/reference.md +6 -4
- package/harness/bundles/general/codebuddy/harness-submit/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-sync/SKILL.md +2 -2
- package/harness/bundles/general/codebuddy/harness-sync/reference.md +5 -4
- package/harness/bundles/general/codebuddy/harness-test/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/scripts/harness_archive.py +282 -120
- package/harness/bundles/general/codebuddy/scripts/harness_events.py +400 -24
- package/harness/bundles/general/codebuddy/scripts/harness_gate.py +585 -21
- package/harness/bundles/general/codebuddy/scripts/harness_integration.py +403 -18
- package/harness/bundles/general/codebuddy/scripts/harness_ledger.py +225 -29
- package/harness/bundles/general/codebuddy/scripts/harness_phase.py +926 -0
- package/harness/bundles/general/codebuddy/scripts/harness_plan_finalize.py +361 -0
- package/harness/bundles/general/codebuddy/scripts/harness_profile.py +50 -0
- package/harness/bundles/general/codebuddy/scripts/harness_runtime.py +248 -0
- package/harness/bundles/general/codebuddy/scripts/harness_state.py +15 -2
- package/harness/bundles/general/codebuddy/scripts/harness_test_guard.py +188 -0
- package/harness/bundles/general/codebuddy/scripts/harness_workflow_policy.py +19 -0
- package/harness/bundles/general/codex/.harness-build.json +1 -1
- package/harness/bundles/general/codex/contracts/ci-metrics.schema.json +36 -0
- package/harness/bundles/general/codex/contracts/workflow-policy.json +30 -0
- package/harness/bundles/general/codex/harness-archive/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-plan/SKILL.md +4 -4
- package/harness/bundles/general/codex/harness-plan/checklist.md +6 -0
- package/harness/bundles/general/codex/harness-plan/reference.md +7 -2
- package/harness/bundles/general/codex/harness-review/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-run/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-run/checklist.md +3 -2
- package/harness/bundles/general/codex/harness-run/reference.md +6 -4
- package/harness/bundles/general/codex/harness-submit/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-sync/SKILL.md +2 -2
- package/harness/bundles/general/codex/harness-sync/reference.md +5 -4
- package/harness/bundles/general/codex/harness-test/SKILL.md +1 -1
- package/harness/bundles/general/codex/scripts/harness_archive.py +282 -120
- package/harness/bundles/general/codex/scripts/harness_events.py +400 -24
- package/harness/bundles/general/codex/scripts/harness_gate.py +585 -21
- package/harness/bundles/general/codex/scripts/harness_integration.py +403 -18
- package/harness/bundles/general/codex/scripts/harness_ledger.py +225 -29
- package/harness/bundles/general/codex/scripts/harness_phase.py +926 -0
- package/harness/bundles/general/codex/scripts/harness_plan_finalize.py +361 -0
- package/harness/bundles/general/codex/scripts/harness_profile.py +50 -0
- package/harness/bundles/general/codex/scripts/harness_runtime.py +248 -0
- package/harness/bundles/general/codex/scripts/harness_state.py +15 -2
- package/harness/bundles/general/codex/scripts/harness_test_guard.py +188 -0
- package/harness/bundles/general/codex/scripts/harness_workflow_policy.py +19 -0
- package/harness/bundles/general/cursor/.harness-build.json +1 -1
- package/harness/bundles/general/cursor/contracts/ci-metrics.schema.json +36 -0
- package/harness/bundles/general/cursor/contracts/workflow-policy.json +30 -0
- package/harness/bundles/general/cursor/harness-archive/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-plan/SKILL.md +4 -4
- package/harness/bundles/general/cursor/harness-plan/checklist.md +6 -0
- package/harness/bundles/general/cursor/harness-plan/reference.md +7 -2
- package/harness/bundles/general/cursor/harness-review/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-run/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-run/checklist.md +3 -2
- package/harness/bundles/general/cursor/harness-run/reference.md +6 -4
- package/harness/bundles/general/cursor/harness-submit/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-sync/SKILL.md +2 -2
- package/harness/bundles/general/cursor/harness-sync/reference.md +5 -4
- package/harness/bundles/general/cursor/harness-test/SKILL.md +1 -1
- package/harness/bundles/general/cursor/scripts/harness_archive.py +282 -120
- package/harness/bundles/general/cursor/scripts/harness_events.py +400 -24
- package/harness/bundles/general/cursor/scripts/harness_gate.py +585 -21
- package/harness/bundles/general/cursor/scripts/harness_integration.py +403 -18
- package/harness/bundles/general/cursor/scripts/harness_ledger.py +225 -29
- package/harness/bundles/general/cursor/scripts/harness_phase.py +926 -0
- package/harness/bundles/general/cursor/scripts/harness_plan_finalize.py +361 -0
- package/harness/bundles/general/cursor/scripts/harness_profile.py +50 -0
- package/harness/bundles/general/cursor/scripts/harness_runtime.py +248 -0
- package/harness/bundles/general/cursor/scripts/harness_state.py +15 -2
- package/harness/bundles/general/cursor/scripts/harness_test_guard.py +188 -0
- package/harness/bundles/general/cursor/scripts/harness_workflow_policy.py +19 -0
- package/harness/bundles/java/claude-code/.harness-build.json +1 -1
- package/harness/bundles/java/claude-code/contracts/ci-metrics.schema.json +36 -0
- package/harness/bundles/java/claude-code/contracts/workflow-policy.json +30 -0
- package/harness/bundles/java/claude-code/harness-apidoc/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-archive/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-package/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-plan/SKILL.md +4 -4
- package/harness/bundles/java/claude-code/harness-plan/checklist.md +6 -0
- package/harness/bundles/java/claude-code/harness-plan/reference.md +7 -2
- package/harness/bundles/java/claude-code/harness-review/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-run/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-submit/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-sync/SKILL.md +2 -2
- package/harness/bundles/java/claude-code/harness-sync/reference.md +5 -4
- package/harness/bundles/java/claude-code/harness-test/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/scripts/harness_archive.py +282 -120
- package/harness/bundles/java/claude-code/scripts/harness_events.py +400 -24
- package/harness/bundles/java/claude-code/scripts/harness_gate.py +585 -21
- package/harness/bundles/java/claude-code/scripts/harness_integration.py +403 -18
- package/harness/bundles/java/claude-code/scripts/harness_ledger.py +225 -29
- package/harness/bundles/java/claude-code/scripts/harness_phase.py +926 -0
- package/harness/bundles/java/claude-code/scripts/harness_plan_finalize.py +361 -0
- package/harness/bundles/java/claude-code/scripts/harness_profile.py +50 -0
- package/harness/bundles/java/claude-code/scripts/harness_runtime.py +248 -0
- package/harness/bundles/java/claude-code/scripts/harness_state.py +15 -2
- package/harness/bundles/java/claude-code/scripts/harness_test_guard.py +188 -0
- package/harness/bundles/java/claude-code/scripts/harness_workflow_policy.py +19 -0
- package/harness/bundles/java/codebuddy/.harness-build.json +1 -1
- package/harness/bundles/java/codebuddy/contracts/ci-metrics.schema.json +36 -0
- package/harness/bundles/java/codebuddy/contracts/workflow-policy.json +30 -0
- package/harness/bundles/java/codebuddy/harness-apidoc/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-archive/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-package/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-plan/SKILL.md +4 -4
- package/harness/bundles/java/codebuddy/harness-plan/checklist.md +6 -0
- package/harness/bundles/java/codebuddy/harness-plan/reference.md +7 -2
- package/harness/bundles/java/codebuddy/harness-review/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-run/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-submit/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-sync/SKILL.md +2 -2
- package/harness/bundles/java/codebuddy/harness-sync/reference.md +5 -4
- package/harness/bundles/java/codebuddy/harness-test/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/scripts/harness_archive.py +282 -120
- package/harness/bundles/java/codebuddy/scripts/harness_events.py +400 -24
- package/harness/bundles/java/codebuddy/scripts/harness_gate.py +585 -21
- package/harness/bundles/java/codebuddy/scripts/harness_integration.py +403 -18
- package/harness/bundles/java/codebuddy/scripts/harness_ledger.py +225 -29
- package/harness/bundles/java/codebuddy/scripts/harness_phase.py +926 -0
- package/harness/bundles/java/codebuddy/scripts/harness_plan_finalize.py +361 -0
- package/harness/bundles/java/codebuddy/scripts/harness_profile.py +50 -0
- package/harness/bundles/java/codebuddy/scripts/harness_runtime.py +248 -0
- package/harness/bundles/java/codebuddy/scripts/harness_state.py +15 -2
- package/harness/bundles/java/codebuddy/scripts/harness_test_guard.py +188 -0
- package/harness/bundles/java/codebuddy/scripts/harness_workflow_policy.py +19 -0
- package/harness/bundles/java/codex/.harness-build.json +1 -1
- package/harness/bundles/java/codex/contracts/ci-metrics.schema.json +36 -0
- package/harness/bundles/java/codex/contracts/workflow-policy.json +30 -0
- package/harness/bundles/java/codex/harness-apidoc/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-archive/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-package/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-plan/SKILL.md +4 -4
- package/harness/bundles/java/codex/harness-plan/checklist.md +6 -0
- package/harness/bundles/java/codex/harness-plan/reference.md +7 -2
- package/harness/bundles/java/codex/harness-review/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-run/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-submit/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-sync/SKILL.md +2 -2
- package/harness/bundles/java/codex/harness-sync/reference.md +5 -4
- package/harness/bundles/java/codex/harness-test/SKILL.md +1 -1
- package/harness/bundles/java/codex/scripts/harness_archive.py +282 -120
- package/harness/bundles/java/codex/scripts/harness_events.py +400 -24
- package/harness/bundles/java/codex/scripts/harness_gate.py +585 -21
- package/harness/bundles/java/codex/scripts/harness_integration.py +403 -18
- package/harness/bundles/java/codex/scripts/harness_ledger.py +225 -29
- package/harness/bundles/java/codex/scripts/harness_phase.py +926 -0
- package/harness/bundles/java/codex/scripts/harness_plan_finalize.py +361 -0
- package/harness/bundles/java/codex/scripts/harness_profile.py +50 -0
- package/harness/bundles/java/codex/scripts/harness_runtime.py +248 -0
- package/harness/bundles/java/codex/scripts/harness_state.py +15 -2
- package/harness/bundles/java/codex/scripts/harness_test_guard.py +188 -0
- package/harness/bundles/java/codex/scripts/harness_workflow_policy.py +19 -0
- package/harness/bundles/java/cursor/.harness-build.json +1 -1
- package/harness/bundles/java/cursor/contracts/ci-metrics.schema.json +36 -0
- package/harness/bundles/java/cursor/contracts/workflow-policy.json +30 -0
- package/harness/bundles/java/cursor/harness-apidoc/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-archive/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-package/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-plan/SKILL.md +4 -4
- package/harness/bundles/java/cursor/harness-plan/checklist.md +6 -0
- package/harness/bundles/java/cursor/harness-plan/reference.md +7 -2
- package/harness/bundles/java/cursor/harness-review/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-run/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-submit/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-sync/SKILL.md +2 -2
- package/harness/bundles/java/cursor/harness-sync/reference.md +5 -4
- package/harness/bundles/java/cursor/harness-test/SKILL.md +1 -1
- package/harness/bundles/java/cursor/scripts/harness_archive.py +282 -120
- package/harness/bundles/java/cursor/scripts/harness_events.py +400 -24
- package/harness/bundles/java/cursor/scripts/harness_gate.py +585 -21
- package/harness/bundles/java/cursor/scripts/harness_integration.py +403 -18
- package/harness/bundles/java/cursor/scripts/harness_ledger.py +225 -29
- package/harness/bundles/java/cursor/scripts/harness_phase.py +926 -0
- package/harness/bundles/java/cursor/scripts/harness_plan_finalize.py +361 -0
- package/harness/bundles/java/cursor/scripts/harness_profile.py +50 -0
- package/harness/bundles/java/cursor/scripts/harness_runtime.py +248 -0
- package/harness/bundles/java/cursor/scripts/harness_state.py +15 -2
- package/harness/bundles/java/cursor/scripts/harness_test_guard.py +188 -0
- package/harness/bundles/java/cursor/scripts/harness_workflow_policy.py +19 -0
- package/harness/manifests/general/claude-code.json +43 -27
- package/harness/manifests/general/codebuddy.json +43 -27
- package/harness/manifests/general/codex.json +43 -27
- package/harness/manifests/general/cursor.json +43 -27
- package/harness/manifests/java/claude-code.json +43 -27
- package/harness/manifests/java/codebuddy.json +43 -27
- package/harness/manifests/java/codex.json +43 -27
- package/harness/manifests/java/cursor.json +43 -27
- package/hunter-workflow-family.json +2 -2
- package/package.json +1 -1
|
@@ -16,6 +16,7 @@ import datetime as dt
|
|
|
16
16
|
import hashlib
|
|
17
17
|
import json
|
|
18
18
|
import os
|
|
19
|
+
import re
|
|
19
20
|
import subprocess
|
|
20
21
|
import sys
|
|
21
22
|
import uuid
|
|
@@ -35,7 +36,17 @@ if hasattr(sys.stderr, "reconfigure"):
|
|
|
35
36
|
sys.stderr.reconfigure(encoding="utf-8")
|
|
36
37
|
|
|
37
38
|
|
|
38
|
-
VERIFICATIONS = frozenset(
|
|
39
|
+
VERIFICATIONS = frozenset(
|
|
40
|
+
{
|
|
41
|
+
"compile",
|
|
42
|
+
"unitTest",
|
|
43
|
+
"unitTestFull",
|
|
44
|
+
"apiTest",
|
|
45
|
+
"install",
|
|
46
|
+
"package",
|
|
47
|
+
"dbCompatibility",
|
|
48
|
+
}
|
|
49
|
+
)
|
|
39
50
|
STATUS_MAP = {
|
|
40
51
|
"ok": "OK",
|
|
41
52
|
"OK": "OK",
|
|
@@ -62,6 +73,7 @@ REQUIRED_COVERAGE = {
|
|
|
62
73
|
"apiTest": 1,
|
|
63
74
|
"install": 2, # module-am or broader
|
|
64
75
|
"package": 2,
|
|
76
|
+
"dbCompatibility": 1,
|
|
65
77
|
}
|
|
66
78
|
# git empty-tree object id (used as base fallback when no commit exists).
|
|
67
79
|
_EMPTY_TREE = "4b825dc642cb6eb9a060e54bf8d69288fbee4904"
|
|
@@ -108,6 +120,51 @@ def parse_files_arg(raw: str | None) -> list[str]:
|
|
|
108
120
|
return [p for p in parts if p]
|
|
109
121
|
|
|
110
122
|
|
|
123
|
+
def parse_files_manifest(raw: str | None) -> list[str]:
|
|
124
|
+
if raw is None or not str(raw).strip():
|
|
125
|
+
return []
|
|
126
|
+
path = Path(str(raw)).expanduser().resolve()
|
|
127
|
+
if not path.is_file():
|
|
128
|
+
raise FileNotFoundError(f"files manifest not found: {path}")
|
|
129
|
+
return [
|
|
130
|
+
line.strip()
|
|
131
|
+
for line in path.read_text(encoding="utf-8-sig").splitlines()
|
|
132
|
+
if line.strip() and not line.lstrip().startswith("#")
|
|
133
|
+
]
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def resolve_input_files(
|
|
137
|
+
files: list[str], project_root: Path | None
|
|
138
|
+
) -> list[str]:
|
|
139
|
+
if project_root is None:
|
|
140
|
+
return files
|
|
141
|
+
root = project_root.expanduser().resolve()
|
|
142
|
+
resolved: list[str] = []
|
|
143
|
+
for raw in files:
|
|
144
|
+
candidate = Path(raw).expanduser()
|
|
145
|
+
candidate = candidate.resolve() if candidate.is_absolute() else (root / candidate).resolve()
|
|
146
|
+
try:
|
|
147
|
+
candidate.relative_to(root)
|
|
148
|
+
except ValueError as exc:
|
|
149
|
+
raise ValueError(
|
|
150
|
+
f"INPUT_OUTSIDE_PROJECT: {candidate} is outside {root}"
|
|
151
|
+
) from exc
|
|
152
|
+
resolved.append(str(candidate))
|
|
153
|
+
return resolved
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def input_files_from_args(args: argparse.Namespace) -> tuple[list[str], Path | None]:
|
|
157
|
+
project_raw = getattr(args, "project", None)
|
|
158
|
+
project_root = (
|
|
159
|
+
Path(str(project_raw)).expanduser().resolve() if project_raw else None
|
|
160
|
+
)
|
|
161
|
+
files = parse_files_arg(getattr(args, "files", None))
|
|
162
|
+
manifest = parse_files_manifest(getattr(args, "files_from", None))
|
|
163
|
+
if files and manifest:
|
|
164
|
+
raise ValueError("use only one of --files and --files-from")
|
|
165
|
+
return resolve_input_files(files or manifest, project_root), project_root
|
|
166
|
+
|
|
167
|
+
|
|
111
168
|
def sha256_file(path: Path) -> str:
|
|
112
169
|
digest = hashlib.sha256()
|
|
113
170
|
with path.open("rb") as handle:
|
|
@@ -561,6 +618,68 @@ def validate_ledger_identity(ledger: dict[str, Any]) -> list[str]:
|
|
|
561
618
|
return missing
|
|
562
619
|
|
|
563
620
|
|
|
621
|
+
def record_integration_hashes(
|
|
622
|
+
ledger_path: Path,
|
|
623
|
+
*,
|
|
624
|
+
repository_id: str,
|
|
625
|
+
merge_final_hash: str,
|
|
626
|
+
ci_expected_head: str,
|
|
627
|
+
remote_head: str,
|
|
628
|
+
) -> dict[str, Any]:
|
|
629
|
+
"""Atomically attach post-push identity to an existing ledger v3."""
|
|
630
|
+
path = Path(ledger_path).resolve()
|
|
631
|
+
if not path.is_file():
|
|
632
|
+
return {"ok": False, "code": "LEDGER_MISSING", "ledgerPath": str(path)}
|
|
633
|
+
try:
|
|
634
|
+
ledger = json.loads(path.read_text(encoding="utf-8-sig"))
|
|
635
|
+
except (OSError, json.JSONDecodeError) as exc:
|
|
636
|
+
return {
|
|
637
|
+
"ok": False, "code": "LEDGER_INVALID", "ledgerPath": str(path),
|
|
638
|
+
"message": str(exc),
|
|
639
|
+
}
|
|
640
|
+
missing = validate_ledger_identity(ledger)
|
|
641
|
+
if missing:
|
|
642
|
+
return {
|
|
643
|
+
"ok": False,
|
|
644
|
+
"code": "LEDGER_IDENTITY_INVALID",
|
|
645
|
+
"ledgerPath": str(path),
|
|
646
|
+
"missing": missing,
|
|
647
|
+
}
|
|
648
|
+
if ledger.get("repositoryId") != repository_id:
|
|
649
|
+
return {
|
|
650
|
+
"ok": False,
|
|
651
|
+
"code": "LEDGER_REPOSITORY_MISMATCH",
|
|
652
|
+
"ledgerPath": str(path),
|
|
653
|
+
}
|
|
654
|
+
values = {
|
|
655
|
+
"mergeFinalHash": merge_final_hash,
|
|
656
|
+
"ciExpectedHead": ci_expected_head,
|
|
657
|
+
"remoteHead": remote_head,
|
|
658
|
+
}
|
|
659
|
+
invalid = [
|
|
660
|
+
field for field, value in values.items()
|
|
661
|
+
if not isinstance(value, str) or re.fullmatch(r"[0-9a-f]{40}", value) is None
|
|
662
|
+
]
|
|
663
|
+
if invalid:
|
|
664
|
+
return {
|
|
665
|
+
"ok": False,
|
|
666
|
+
"code": "FINAL_HASH_INVALID",
|
|
667
|
+
"ledgerPath": str(path),
|
|
668
|
+
"invalid": invalid,
|
|
669
|
+
}
|
|
670
|
+
if len(set(values.values())) != 1:
|
|
671
|
+
return {
|
|
672
|
+
"ok": False,
|
|
673
|
+
"code": "FINAL_HASH_MISMATCH",
|
|
674
|
+
"ledgerPath": str(path),
|
|
675
|
+
**values,
|
|
676
|
+
}
|
|
677
|
+
ledger.update(values)
|
|
678
|
+
ledger["integrationFinalizedAt"] = now_iso()
|
|
679
|
+
write_ledger(path, ledger)
|
|
680
|
+
return {"ok": True, "code": "INTEGRATION_HASHES_RECORDED", "ledgerPath": str(path), **values}
|
|
681
|
+
|
|
682
|
+
|
|
564
683
|
def _contract_is_v2(change_dir: Path) -> bool:
|
|
565
684
|
try:
|
|
566
685
|
contract = harness_paths.load_change_contract(change_dir)
|
|
@@ -581,6 +700,7 @@ def ownership_hash(contract: dict[str, Any]) -> str:
|
|
|
581
700
|
_METRICS_SCHEMAS: dict[str, dict[str, tuple[str, ...]]] = {
|
|
582
701
|
"unitTest": {"required": ("total", "passed", "failed"), "optional": ("errors", "skipped")},
|
|
583
702
|
"unitTestFull": {"required": ("total", "passed", "failed"), "optional": ("errors", "skipped")},
|
|
703
|
+
"apiTest": {"required": ("total", "passed", "failed"), "optional": ("blocked",)},
|
|
584
704
|
"apiContract": {"required": ("scenariosTotal", "passed", "failed"), "optional": ("blocked",)},
|
|
585
705
|
"browserE2E": {"required": ("total", "passed", "failed"), "optional": ("skipped", "retries")},
|
|
586
706
|
}
|
|
@@ -599,9 +719,37 @@ def validate_metrics(verification: str, metrics: Any) -> list[str]:
|
|
|
599
719
|
reason = metrics.get("reason")
|
|
600
720
|
if not isinstance(reason, str) or not reason.strip():
|
|
601
721
|
problems.append("dbCompatibility.reason required for NOT_APPLICABLE")
|
|
722
|
+
unknown = sorted(set(metrics) - {"applicability", "reason"})
|
|
723
|
+
problems.extend(
|
|
724
|
+
f"dbCompatibility.{field} is not valid for NOT_APPLICABLE"
|
|
725
|
+
for field in unknown
|
|
726
|
+
)
|
|
602
727
|
else:
|
|
603
|
-
|
|
604
|
-
|
|
728
|
+
allowed = {
|
|
729
|
+
"applicability", "status", "total", "passed", "failed", "evidenceHash"
|
|
730
|
+
}
|
|
731
|
+
status = metrics.get("status")
|
|
732
|
+
if status not in {"OK", "FAIL"}:
|
|
733
|
+
problems.append("dbCompatibility.status must be OK|FAIL for APPLICABLE")
|
|
734
|
+
for field in ("total", "passed", "failed"):
|
|
735
|
+
value = metrics.get(field)
|
|
736
|
+
if not isinstance(value, int) or isinstance(value, bool) or value < 0:
|
|
737
|
+
problems.append(f"dbCompatibility.{field} must be a non-negative int")
|
|
738
|
+
total = metrics.get("total")
|
|
739
|
+
passed = metrics.get("passed")
|
|
740
|
+
failed = metrics.get("failed")
|
|
741
|
+
if all(isinstance(value, int) and not isinstance(value, bool)
|
|
742
|
+
for value in (total, passed, failed)) and passed + failed != total:
|
|
743
|
+
problems.append("dbCompatibility counts must satisfy passed + failed == total")
|
|
744
|
+
evidence_hash = metrics.get("evidenceHash")
|
|
745
|
+
if not isinstance(evidence_hash, str) or not re.fullmatch(
|
|
746
|
+
r"sha256:[0-9a-f]{64}", evidence_hash
|
|
747
|
+
):
|
|
748
|
+
problems.append("dbCompatibility.evidenceHash must be sha256:<64 lowercase hex>")
|
|
749
|
+
problems.extend(
|
|
750
|
+
f"dbCompatibility.{field} is not a supported field"
|
|
751
|
+
for field in sorted(set(metrics) - allowed)
|
|
752
|
+
)
|
|
605
753
|
return problems
|
|
606
754
|
schema = _METRICS_SCHEMAS.get(verification)
|
|
607
755
|
if schema is None:
|
|
@@ -701,14 +849,19 @@ def compute_ownership_diff(
|
|
|
701
849
|
if head:
|
|
702
850
|
diff_args.append(head)
|
|
703
851
|
raw = _git_text(repo_root, *diff_args) or ""
|
|
852
|
+
changed_paths = {line.strip() for line in raw.splitlines() if line.strip()}
|
|
853
|
+
if head is None:
|
|
854
|
+
untracked = _git_text(
|
|
855
|
+
repo_root, "ls-files", "--others", "--exclude-standard"
|
|
856
|
+
) or ""
|
|
857
|
+
changed_paths.update(
|
|
858
|
+
line.strip() for line in untracked.splitlines() if line.strip()
|
|
859
|
+
)
|
|
704
860
|
owned: list[str] = []
|
|
705
861
|
static_evidence: list[str] = []
|
|
706
862
|
foreign: list[str] = []
|
|
707
863
|
excluded_runtime = 0
|
|
708
|
-
for
|
|
709
|
-
rel = line.strip()
|
|
710
|
-
if not rel:
|
|
711
|
-
continue
|
|
864
|
+
for rel in sorted(changed_paths):
|
|
712
865
|
verdict = _classify_ownership_path(rel, change_dir.name, ownership)
|
|
713
866
|
if verdict == "foreign":
|
|
714
867
|
foreign.append(rel)
|
|
@@ -1040,8 +1193,10 @@ def decide_can_reuse(
|
|
|
1040
1193
|
|
|
1041
1194
|
def cmd_hash(args: argparse.Namespace) -> int:
|
|
1042
1195
|
as_json = bool(args.json)
|
|
1043
|
-
files = parse_files_arg(args.files)
|
|
1044
1196
|
try:
|
|
1197
|
+
files, project_root = input_files_from_args(args)
|
|
1198
|
+
if not files:
|
|
1199
|
+
return emit_error("hash requires --files or --files-from", as_json=as_json)
|
|
1045
1200
|
inputs_hash, inputs_files = compute_inputs_hash(files)
|
|
1046
1201
|
except (OSError, FileNotFoundError) as exc:
|
|
1047
1202
|
return emit_error(str(exc), as_json=as_json)
|
|
@@ -1052,6 +1207,7 @@ def cmd_hash(args: argparse.Namespace) -> int:
|
|
|
1052
1207
|
"inputsHash": inputs_hash,
|
|
1053
1208
|
"inputsFiles": inputs_files,
|
|
1054
1209
|
"fileCount": len(inputs_files),
|
|
1210
|
+
"resolvedProjectRoot": str(project_root) if project_root else None,
|
|
1055
1211
|
}
|
|
1056
1212
|
emit_json(payload, as_json=as_json)
|
|
1057
1213
|
return 0
|
|
@@ -1066,15 +1222,16 @@ def cmd_can_reuse(args: argparse.Namespace) -> int:
|
|
|
1066
1222
|
as_json=as_json,
|
|
1067
1223
|
)
|
|
1068
1224
|
change_dir = resolve_path(args.change_dir)
|
|
1069
|
-
|
|
1225
|
+
try:
|
|
1226
|
+
files, project_root = input_files_from_args(args)
|
|
1227
|
+
except (OSError, ValueError) as exc:
|
|
1228
|
+
return emit_error(f"can-reuse failed: {exc}", as_json=as_json)
|
|
1070
1229
|
profile_input = getattr(args, "profile_input", None)
|
|
1071
1230
|
project_raw = getattr(args, "project", None)
|
|
1072
1231
|
if profile_input:
|
|
1073
1232
|
if not project_raw:
|
|
1074
1233
|
return emit_error("--profile-input requires --project", as_json=as_json)
|
|
1075
|
-
resolved_files, err = expand_profile_input_files(
|
|
1076
|
-
Path(str(project_raw)).expanduser().resolve(), profile_input
|
|
1077
|
-
)
|
|
1234
|
+
resolved_files, err = expand_profile_input_files(project_root, profile_input)
|
|
1078
1235
|
if err:
|
|
1079
1236
|
# profile 未正确配置:不允许缓存复用,返回 insufficient-evidence(exit 0)。
|
|
1080
1237
|
payload = {
|
|
@@ -1086,7 +1243,7 @@ def cmd_can_reuse(args: argparse.Namespace) -> int:
|
|
|
1086
1243
|
}
|
|
1087
1244
|
emit_json(payload, as_json=as_json)
|
|
1088
1245
|
return 0
|
|
1089
|
-
files = resolved_files
|
|
1246
|
+
files = resolve_input_files(resolved_files, project_root)
|
|
1090
1247
|
if not files:
|
|
1091
1248
|
return emit_error(
|
|
1092
1249
|
"can-reuse requires --files or a non-empty --profile-input file set",
|
|
@@ -1114,18 +1271,33 @@ def cmd_record(args: argparse.Namespace) -> int:
|
|
|
1114
1271
|
as_json = bool(args.json)
|
|
1115
1272
|
change_dir = resolve_path(args.change_dir)
|
|
1116
1273
|
verification = args.verification
|
|
1117
|
-
|
|
1274
|
+
if verification not in VERIFICATIONS:
|
|
1275
|
+
return emit_error(
|
|
1276
|
+
f"unsupported verification: {verification}; expected one of {sorted(VERIFICATIONS)}",
|
|
1277
|
+
as_json=as_json,
|
|
1278
|
+
)
|
|
1279
|
+
contract_v2 = _contract_is_v2(change_dir)
|
|
1280
|
+
if not contract_v2 and any(
|
|
1281
|
+
_nonempty_str(getattr(args, field, None))
|
|
1282
|
+
for field in ("base_commit", "diff_hash")
|
|
1283
|
+
):
|
|
1284
|
+
return emit_error(
|
|
1285
|
+
"IDENTITY_UNSUPPORTED: explicit ledger identity requires a v2 change contract",
|
|
1286
|
+
as_json=as_json,
|
|
1287
|
+
)
|
|
1288
|
+
try:
|
|
1289
|
+
files, project_root = input_files_from_args(args)
|
|
1290
|
+
except (OSError, ValueError) as exc:
|
|
1291
|
+
return emit_error(f"record failed: {exc}", as_json=as_json)
|
|
1118
1292
|
profile_input = getattr(args, "profile_input", None)
|
|
1119
1293
|
project_raw = getattr(args, "project", None)
|
|
1120
1294
|
if profile_input:
|
|
1121
1295
|
if not project_raw:
|
|
1122
1296
|
return emit_error("--profile-input requires --project", as_json=as_json)
|
|
1123
|
-
resolved_files, err = expand_profile_input_files(
|
|
1124
|
-
Path(str(project_raw)).expanduser().resolve(), profile_input
|
|
1125
|
-
)
|
|
1297
|
+
resolved_files, err = expand_profile_input_files(project_root, profile_input)
|
|
1126
1298
|
if err:
|
|
1127
1299
|
return emit_error(f"record failed: {err}", as_json=as_json)
|
|
1128
|
-
files = resolved_files
|
|
1300
|
+
files = resolve_input_files(resolved_files, project_root)
|
|
1129
1301
|
if not files:
|
|
1130
1302
|
return emit_error(
|
|
1131
1303
|
"record requires --files or a non-empty --profile-input file set",
|
|
@@ -1164,6 +1336,19 @@ def cmd_record(args: argparse.Namespace) -> int:
|
|
|
1164
1336
|
}
|
|
1165
1337
|
)
|
|
1166
1338
|
metrics_raw = getattr(args, "metrics_json", None)
|
|
1339
|
+
metrics_file = getattr(args, "metrics_file", None)
|
|
1340
|
+
if metrics_file and metrics_raw is not None and str(metrics_raw).strip():
|
|
1341
|
+
return emit_error(
|
|
1342
|
+
"use only one of --metrics-json and --metrics-file",
|
|
1343
|
+
as_json=as_json,
|
|
1344
|
+
)
|
|
1345
|
+
if metrics_file:
|
|
1346
|
+
try:
|
|
1347
|
+
metrics_raw = Path(str(metrics_file)).expanduser().resolve().read_text(
|
|
1348
|
+
encoding="utf-8-sig"
|
|
1349
|
+
)
|
|
1350
|
+
except OSError as exc:
|
|
1351
|
+
return emit_error(f"invalid --metrics-file: {exc}", as_json=as_json)
|
|
1167
1352
|
if metrics_raw is not None and str(metrics_raw).strip() != "":
|
|
1168
1353
|
try:
|
|
1169
1354
|
metrics_obj = json.loads(str(metrics_raw))
|
|
@@ -1171,7 +1356,7 @@ def cmd_record(args: argparse.Namespace) -> int:
|
|
|
1171
1356
|
return emit_error("invalid --metrics-json", as_json=as_json)
|
|
1172
1357
|
if not isinstance(metrics_obj, dict):
|
|
1173
1358
|
return emit_error("invalid --metrics-json", as_json=as_json)
|
|
1174
|
-
if
|
|
1359
|
+
if contract_v2:
|
|
1175
1360
|
# Typed metrics schemas are ledger v3 (contract-gated); legacy
|
|
1176
1361
|
# changes keep the loose run/failures shape (zero regression).
|
|
1177
1362
|
problems = validate_metrics(verification, metrics_obj)
|
|
@@ -1231,9 +1416,10 @@ def cmd_record(args: argparse.Namespace) -> int:
|
|
|
1231
1416
|
ledger["stateDir"] = str(change_dir)
|
|
1232
1417
|
|
|
1233
1418
|
out_path = preferred_write_path(change_dir)
|
|
1234
|
-
if
|
|
1419
|
+
if contract_v2:
|
|
1235
1420
|
# Ledger v3: forced top-level identity (RET-15/16, COM-002).
|
|
1236
|
-
|
|
1421
|
+
repo_probe = project_root or change_dir
|
|
1422
|
+
repo_root_raw = _git_text(repo_probe, "rev-parse", "--show-toplevel")
|
|
1237
1423
|
if not repo_root_raw:
|
|
1238
1424
|
return emit_error(
|
|
1239
1425
|
"record failed: change dir is not inside a git repository",
|
|
@@ -1251,8 +1437,6 @@ def cmd_record(args: argparse.Namespace) -> int:
|
|
|
1251
1437
|
base_commit = _git_text(repo_root, "rev-parse", "--verify", "HEAD")
|
|
1252
1438
|
current_head = _git_text(repo_root, "rev-parse", "--verify", "HEAD")
|
|
1253
1439
|
diff_hash = getattr(args, "diff_hash", None)
|
|
1254
|
-
if not _nonempty_str(diff_hash):
|
|
1255
|
-
diff_hash = ledger.get("diffHash")
|
|
1256
1440
|
if not _nonempty_str(diff_hash) and _nonempty_str(base_commit):
|
|
1257
1441
|
try:
|
|
1258
1442
|
diff_hash = compute_ownership_diff(
|
|
@@ -1289,6 +1473,7 @@ def cmd_record(args: argparse.Namespace) -> int:
|
|
|
1289
1473
|
"inputsFiles": inputs_files,
|
|
1290
1474
|
"ledger_path": str(out_path),
|
|
1291
1475
|
"diffHash": ledger.get("diffHash"),
|
|
1476
|
+
"resolvedProjectRoot": str(project_root) if project_root else None,
|
|
1292
1477
|
}
|
|
1293
1478
|
emit_json(payload, as_json=as_json)
|
|
1294
1479
|
return 0
|
|
@@ -1333,11 +1518,10 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
1333
1518
|
)
|
|
1334
1519
|
|
|
1335
1520
|
p_hash = sub.add_parser("hash", parents=[shared_json], help="compute inputsHash for a file set")
|
|
1336
|
-
p_hash.
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
)
|
|
1521
|
+
hash_files = p_hash.add_mutually_exclusive_group()
|
|
1522
|
+
hash_files.add_argument("--files", default=None, help="comma-separated source file paths")
|
|
1523
|
+
hash_files.add_argument("--files-from", default=None, help="UTF-8 newline-delimited source paths")
|
|
1524
|
+
p_hash.add_argument("--project", default=None, help="root for relative input paths")
|
|
1341
1525
|
p_hash.set_defaults(func=cmd_hash)
|
|
1342
1526
|
|
|
1343
1527
|
p_reuse = sub.add_parser("can-reuse", parents=[shared_json], help="decide whether a verification can be reused")
|
|
@@ -1352,6 +1536,11 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
1352
1536
|
default=None,
|
|
1353
1537
|
help="comma-separated source file paths for current inputsHash",
|
|
1354
1538
|
)
|
|
1539
|
+
p_reuse.add_argument(
|
|
1540
|
+
"--files-from",
|
|
1541
|
+
default=None,
|
|
1542
|
+
help="UTF-8 newline-delimited source paths",
|
|
1543
|
+
)
|
|
1355
1544
|
p_reuse.add_argument(
|
|
1356
1545
|
"--project",
|
|
1357
1546
|
default=None,
|
|
@@ -1397,7 +1586,9 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
1397
1586
|
p_record.add_argument("--command", required=True)
|
|
1398
1587
|
p_record.add_argument("--exit-code", type=int, required=True)
|
|
1399
1588
|
p_record.add_argument("--duration-ms", type=int, required=True)
|
|
1400
|
-
p_record.
|
|
1589
|
+
p_record_files = p_record.add_mutually_exclusive_group()
|
|
1590
|
+
p_record_files.add_argument("--files", default=None)
|
|
1591
|
+
p_record_files.add_argument("--files-from", default=None)
|
|
1401
1592
|
p_record.add_argument("--evidence", required=True)
|
|
1402
1593
|
p_record.add_argument(
|
|
1403
1594
|
"--project",
|
|
@@ -1440,6 +1631,11 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
1440
1631
|
default=None,
|
|
1441
1632
|
help='structured counts, e.g. \'{"run":155,"failures":0}\' or \'{"total":3,"passed":3}\'',
|
|
1442
1633
|
)
|
|
1634
|
+
p_record.add_argument(
|
|
1635
|
+
"--metrics-file",
|
|
1636
|
+
default=None,
|
|
1637
|
+
help="UTF-8 JSON file containing typed verification metrics",
|
|
1638
|
+
)
|
|
1443
1639
|
p_record.add_argument(
|
|
1444
1640
|
"--base-commit",
|
|
1445
1641
|
default=None,
|