@hunter-harness/workflow-harness 0.2.4 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/harness/bundles/general/claude-code/.harness-build.json +1 -1
- package/harness/bundles/general/claude-code/harness-archive/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-plan/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-review/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-run/SKILL.md +15 -3
- package/harness/bundles/general/claude-code/harness-run/checklist.md +7 -3
- package/harness/bundles/general/claude-code/harness-run/reference.md +3 -9
- package/harness/bundles/general/claude-code/harness-submit/SKILL.md +5 -3
- package/harness/bundles/general/claude-code/harness-submit/checklist.md +10 -1
- package/harness/bundles/general/claude-code/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-test/SKILL.md +14 -2
- package/harness/bundles/general/claude-code/harness-test/checklist.md +4 -0
- package/harness/bundles/general/claude-code/harness-test/reference.md +4 -4
- package/harness/bundles/general/claude-code/protocols/archive-report-protocol.md +2 -4
- package/harness/bundles/general/claude-code/protocols/ledger-protocol.md +7 -15
- package/harness/bundles/general/claude-code/scripts/harness_ledger.py +111 -5
- package/harness/bundles/general/claude-code/scripts/harness_profile.py +29 -1
- package/harness/bundles/general/claude-code/scripts/harness_test_guard.py +502 -0
- package/harness/bundles/general/codebuddy/.harness-build.json +1 -1
- package/harness/bundles/general/codebuddy/harness-archive/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-plan/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-review/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-run/SKILL.md +15 -3
- package/harness/bundles/general/codebuddy/harness-run/checklist.md +7 -3
- package/harness/bundles/general/codebuddy/harness-run/reference.md +3 -9
- package/harness/bundles/general/codebuddy/harness-submit/SKILL.md +5 -3
- package/harness/bundles/general/codebuddy/harness-submit/checklist.md +10 -1
- package/harness/bundles/general/codebuddy/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-test/SKILL.md +14 -2
- package/harness/bundles/general/codebuddy/harness-test/checklist.md +4 -0
- package/harness/bundles/general/codebuddy/harness-test/reference.md +4 -4
- package/harness/bundles/general/codebuddy/protocols/archive-report-protocol.md +2 -4
- package/harness/bundles/general/codebuddy/protocols/ledger-protocol.md +7 -15
- package/harness/bundles/general/codebuddy/scripts/harness_ledger.py +111 -5
- package/harness/bundles/general/codebuddy/scripts/harness_profile.py +29 -1
- package/harness/bundles/general/codebuddy/scripts/harness_test_guard.py +502 -0
- package/harness/bundles/general/codex/.harness-build.json +1 -1
- package/harness/bundles/general/codex/harness-archive/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-plan/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-review/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-run/SKILL.md +15 -3
- package/harness/bundles/general/codex/harness-run/checklist.md +7 -3
- package/harness/bundles/general/codex/harness-run/reference.md +3 -9
- package/harness/bundles/general/codex/harness-submit/SKILL.md +5 -3
- package/harness/bundles/general/codex/harness-submit/checklist.md +10 -1
- package/harness/bundles/general/codex/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-test/SKILL.md +14 -2
- package/harness/bundles/general/codex/harness-test/checklist.md +4 -0
- package/harness/bundles/general/codex/harness-test/reference.md +4 -4
- package/harness/bundles/general/codex/protocols/archive-report-protocol.md +2 -4
- package/harness/bundles/general/codex/protocols/ledger-protocol.md +7 -15
- package/harness/bundles/general/codex/scripts/harness_ledger.py +111 -5
- package/harness/bundles/general/codex/scripts/harness_profile.py +29 -1
- package/harness/bundles/general/codex/scripts/harness_test_guard.py +502 -0
- package/harness/bundles/general/cursor/.harness-build.json +1 -1
- package/harness/bundles/general/cursor/harness-archive/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-plan/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-review/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-run/SKILL.md +15 -3
- package/harness/bundles/general/cursor/harness-run/checklist.md +7 -3
- package/harness/bundles/general/cursor/harness-run/reference.md +3 -9
- package/harness/bundles/general/cursor/harness-submit/SKILL.md +5 -3
- package/harness/bundles/general/cursor/harness-submit/checklist.md +10 -1
- package/harness/bundles/general/cursor/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-test/SKILL.md +14 -2
- package/harness/bundles/general/cursor/harness-test/checklist.md +4 -0
- package/harness/bundles/general/cursor/harness-test/reference.md +4 -4
- package/harness/bundles/general/cursor/protocols/archive-report-protocol.md +2 -4
- package/harness/bundles/general/cursor/protocols/ledger-protocol.md +7 -15
- package/harness/bundles/general/cursor/scripts/harness_ledger.py +111 -5
- package/harness/bundles/general/cursor/scripts/harness_profile.py +29 -1
- package/harness/bundles/general/cursor/scripts/harness_test_guard.py +502 -0
- package/harness/bundles/java/claude-code/.harness-build.json +1 -1
- package/harness/bundles/java/claude-code/harness-apidoc/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-archive/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-package/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-plan/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-review/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-run/SKILL.md +18 -4
- package/harness/bundles/java/claude-code/harness-submit/SKILL.md +5 -3
- package/harness/bundles/java/claude-code/harness-submit/checklist.md +10 -1
- package/harness/bundles/java/claude-code/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-test/SKILL.md +16 -2
- package/harness/bundles/java/claude-code/protocols/archive-report-protocol.md +2 -4
- package/harness/bundles/java/claude-code/protocols/ledger-protocol.md +7 -15
- package/harness/bundles/java/claude-code/scripts/harness_ledger.py +111 -5
- package/harness/bundles/java/claude-code/scripts/harness_profile.py +29 -1
- package/harness/bundles/java/claude-code/scripts/harness_test_guard.py +502 -0
- package/harness/bundles/java/codebuddy/.harness-build.json +1 -1
- package/harness/bundles/java/codebuddy/harness-apidoc/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-archive/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-package/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-plan/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-review/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-run/SKILL.md +18 -4
- package/harness/bundles/java/codebuddy/harness-submit/SKILL.md +5 -3
- package/harness/bundles/java/codebuddy/harness-submit/checklist.md +10 -1
- package/harness/bundles/java/codebuddy/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-test/SKILL.md +16 -2
- package/harness/bundles/java/codebuddy/protocols/archive-report-protocol.md +2 -4
- package/harness/bundles/java/codebuddy/protocols/ledger-protocol.md +7 -15
- package/harness/bundles/java/codebuddy/scripts/harness_ledger.py +111 -5
- package/harness/bundles/java/codebuddy/scripts/harness_profile.py +29 -1
- package/harness/bundles/java/codebuddy/scripts/harness_test_guard.py +502 -0
- package/harness/bundles/java/codex/.harness-build.json +1 -1
- package/harness/bundles/java/codex/harness-apidoc/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-archive/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-package/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-plan/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-review/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-run/SKILL.md +18 -4
- package/harness/bundles/java/codex/harness-submit/SKILL.md +5 -3
- package/harness/bundles/java/codex/harness-submit/checklist.md +10 -1
- package/harness/bundles/java/codex/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-test/SKILL.md +16 -2
- package/harness/bundles/java/codex/protocols/archive-report-protocol.md +2 -4
- package/harness/bundles/java/codex/protocols/ledger-protocol.md +7 -15
- package/harness/bundles/java/codex/scripts/harness_ledger.py +111 -5
- package/harness/bundles/java/codex/scripts/harness_profile.py +29 -1
- package/harness/bundles/java/codex/scripts/harness_test_guard.py +502 -0
- package/harness/bundles/java/cursor/.harness-build.json +1 -1
- package/harness/bundles/java/cursor/harness-apidoc/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-archive/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-package/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-plan/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-review/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-run/SKILL.md +18 -4
- package/harness/bundles/java/cursor/harness-submit/SKILL.md +5 -3
- package/harness/bundles/java/cursor/harness-submit/checklist.md +10 -1
- package/harness/bundles/java/cursor/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-test/SKILL.md +16 -2
- package/harness/bundles/java/cursor/protocols/archive-report-protocol.md +2 -4
- package/harness/bundles/java/cursor/protocols/ledger-protocol.md +7 -15
- package/harness/bundles/java/cursor/scripts/harness_ledger.py +111 -5
- package/harness/bundles/java/cursor/scripts/harness_profile.py +29 -1
- package/harness/bundles/java/cursor/scripts/harness_test_guard.py +502 -0
- package/harness/manifests/general/claude-code.json +25 -21
- package/harness/manifests/general/codebuddy.json +25 -21
- package/harness/manifests/general/codex.json +25 -21
- package/harness/manifests/general/cursor.json +25 -21
- package/harness/manifests/java/claude-code.json +23 -19
- package/harness/manifests/java/codebuddy.json +23 -19
- package/harness/manifests/java/codex.json +23 -19
- package/harness/manifests/java/cursor.json +23 -19
- package/hunter-workflow-family.json +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Record and force-stage only explicitly touched test files.
|
|
3
|
+
|
|
4
|
+
Python 3.10+ stdlib only. Git is always invoked with an argv list.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import argparse
|
|
10
|
+
import contextlib
|
|
11
|
+
import fnmatch
|
|
12
|
+
import hashlib
|
|
13
|
+
import json
|
|
14
|
+
import os
|
|
15
|
+
import re
|
|
16
|
+
import shutil
|
|
17
|
+
import subprocess
|
|
18
|
+
import sys
|
|
19
|
+
import tempfile
|
|
20
|
+
import time
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from typing import Any
|
|
23
|
+
|
|
24
|
+
SCHEMA_VERSION = 1
|
|
25
|
+
MODE = "force-track-touched"
|
|
26
|
+
MANIFEST_REL = Path("evidence") / "test-tracking.json"
|
|
27
|
+
PROFILE_REL = Path(".harness") / "config" / "build-profile.json"
|
|
28
|
+
REASONS = ("tdd-created", "stale-test-repair", "test-updated")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class LockUnavailable(RuntimeError):
|
|
32
|
+
"""A compatible lock file is already held by another process."""
|
|
33
|
+
|
|
34
|
+
if hasattr(sys.stdout, "reconfigure"):
|
|
35
|
+
sys.stdout.reconfigure(encoding="utf-8")
|
|
36
|
+
if hasattr(sys.stderr, "reconfigure"):
|
|
37
|
+
sys.stderr.reconfigure(encoding="utf-8")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _result(ok: bool, action: str, code: str, files: list[str], **extra: Any) -> dict[str, Any]:
|
|
41
|
+
return {"ok": ok, "action": action, "code": code, "files": files, **extra}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _write_json(path: Path, data: Any) -> None:
|
|
45
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
46
|
+
text = json.dumps(data, ensure_ascii=False, indent=2) + "\n"
|
|
47
|
+
tmp = path.with_name(f".{path.name}.{os.getpid()}.tmp")
|
|
48
|
+
try:
|
|
49
|
+
tmp.write_text(text, encoding="utf-8", newline="\n")
|
|
50
|
+
os.replace(tmp, path)
|
|
51
|
+
except BaseException:
|
|
52
|
+
tmp.unlink(missing_ok=True)
|
|
53
|
+
raise
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _read_json(path: Path) -> Any:
|
|
57
|
+
return json.loads(path.read_text(encoding="utf-8-sig"))
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _sha256(path: Path) -> str:
|
|
61
|
+
digest = hashlib.sha256()
|
|
62
|
+
with path.open("rb") as handle:
|
|
63
|
+
for chunk in iter(lambda: handle.read(65536), b""):
|
|
64
|
+
digest.update(chunk)
|
|
65
|
+
return "sha256:" + digest.hexdigest()
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _git(
|
|
69
|
+
project: Path,
|
|
70
|
+
*args: str,
|
|
71
|
+
index_file: Path | None = None,
|
|
72
|
+
) -> subprocess.CompletedProcess[str]:
|
|
73
|
+
env = os.environ.copy()
|
|
74
|
+
if index_file is not None:
|
|
75
|
+
env["GIT_INDEX_FILE"] = str(index_file)
|
|
76
|
+
return subprocess.run(
|
|
77
|
+
[
|
|
78
|
+
"git",
|
|
79
|
+
"--literal-pathspecs",
|
|
80
|
+
"-c",
|
|
81
|
+
"core.quotepath=false",
|
|
82
|
+
"-C",
|
|
83
|
+
str(project),
|
|
84
|
+
*args,
|
|
85
|
+
],
|
|
86
|
+
capture_output=True,
|
|
87
|
+
text=True,
|
|
88
|
+
encoding="utf-8",
|
|
89
|
+
errors="replace",
|
|
90
|
+
timeout=30,
|
|
91
|
+
check=False,
|
|
92
|
+
env=env,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _is_ignored(project: Path, rel: str) -> bool:
|
|
97
|
+
status = _git(
|
|
98
|
+
project,
|
|
99
|
+
"status",
|
|
100
|
+
"--ignored=matching",
|
|
101
|
+
"--untracked-files=all",
|
|
102
|
+
"--porcelain=v1",
|
|
103
|
+
"--",
|
|
104
|
+
rel,
|
|
105
|
+
)
|
|
106
|
+
return status.returncode == 0 and any(
|
|
107
|
+
line.startswith("!! ") for line in status.stdout.splitlines()
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _inside(path: Path, root: Path) -> bool:
|
|
112
|
+
try:
|
|
113
|
+
normalized_path = os.path.normcase(str(path))
|
|
114
|
+
normalized_root = os.path.normcase(str(root))
|
|
115
|
+
return os.path.commonpath((normalized_path, normalized_root)) == normalized_root
|
|
116
|
+
except ValueError:
|
|
117
|
+
return False
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@contextlib.contextmanager
|
|
121
|
+
def _exclusive_lock(path: Path, *, wait_seconds: float) -> Any:
|
|
122
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
123
|
+
deadline = time.monotonic() + wait_seconds
|
|
124
|
+
descriptor: int | None = None
|
|
125
|
+
while descriptor is None:
|
|
126
|
+
try:
|
|
127
|
+
descriptor = os.open(path, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o600)
|
|
128
|
+
except FileExistsError as exc:
|
|
129
|
+
if time.monotonic() >= deadline:
|
|
130
|
+
raise LockUnavailable(str(path)) from exc
|
|
131
|
+
time.sleep(0.01)
|
|
132
|
+
try:
|
|
133
|
+
os.write(descriptor, f"{os.getpid()}\n".encode("ascii"))
|
|
134
|
+
yield
|
|
135
|
+
finally:
|
|
136
|
+
os.close(descriptor)
|
|
137
|
+
path.unlink(missing_ok=True)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _change_dir(project: Path, change_dir: Path | str) -> Path | None:
|
|
141
|
+
candidate = Path(change_dir)
|
|
142
|
+
if not candidate.is_absolute():
|
|
143
|
+
candidate = project / candidate
|
|
144
|
+
resolved = candidate.resolve()
|
|
145
|
+
return resolved if _inside(resolved, project) else None
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _manifest_path(project: Path, change_root: Path) -> Path | None:
|
|
149
|
+
evidence = change_root / MANIFEST_REL.parent
|
|
150
|
+
manifest = change_root / MANIFEST_REL
|
|
151
|
+
expected_evidence = evidence.absolute()
|
|
152
|
+
resolved_evidence = evidence.resolve()
|
|
153
|
+
if (
|
|
154
|
+
not _inside(change_root.resolve(), project)
|
|
155
|
+
or not _inside(resolved_evidence, project)
|
|
156
|
+
or os.path.normcase(str(resolved_evidence))
|
|
157
|
+
!= os.path.normcase(str(expected_evidence))
|
|
158
|
+
):
|
|
159
|
+
return None
|
|
160
|
+
return manifest
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _manifest_target_inside(project: Path, manifest: Path) -> bool:
|
|
164
|
+
resolved = manifest.resolve()
|
|
165
|
+
return _inside(resolved, project) and _inside(resolved, manifest.parent.absolute())
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def _profile_config(project: Path) -> tuple[list[str], list[str]] | None:
|
|
169
|
+
profile_path = project / PROFILE_REL
|
|
170
|
+
if not profile_path.is_file():
|
|
171
|
+
return None
|
|
172
|
+
try:
|
|
173
|
+
profile = _read_json(profile_path)
|
|
174
|
+
except (OSError, json.JSONDecodeError):
|
|
175
|
+
return [], []
|
|
176
|
+
tracking = profile.get("testTracking") if isinstance(profile, dict) else None
|
|
177
|
+
paths = tracking.get("paths") if isinstance(tracking, dict) else None
|
|
178
|
+
if not isinstance(paths, list):
|
|
179
|
+
return [], []
|
|
180
|
+
excluded = profile.get("excludedRoots") if isinstance(profile, dict) else None
|
|
181
|
+
excluded_roots = (
|
|
182
|
+
[item.replace("\\", "/").strip("/") for item in excluded if isinstance(item, str) and item]
|
|
183
|
+
if isinstance(excluded, list)
|
|
184
|
+
else []
|
|
185
|
+
)
|
|
186
|
+
patterns = [
|
|
187
|
+
item.replace("\\", "/") for item in paths if isinstance(item, str) and item
|
|
188
|
+
]
|
|
189
|
+
return patterns, excluded_roots
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def _matches_pattern(rel: str, pattern: str) -> bool:
|
|
193
|
+
# fnmatch does not give **/ its usual zero-or-more-directory meaning, so
|
|
194
|
+
# also try the form with every **/ removed.
|
|
195
|
+
if fnmatch.fnmatchcase(rel, pattern):
|
|
196
|
+
return True
|
|
197
|
+
compact = pattern
|
|
198
|
+
while "**/" in compact:
|
|
199
|
+
compact = compact.replace("**/", "", 1)
|
|
200
|
+
if fnmatch.fnmatchcase(rel, compact):
|
|
201
|
+
return True
|
|
202
|
+
if pattern.endswith("/**"):
|
|
203
|
+
prefix = pattern[:-3].rstrip("/")
|
|
204
|
+
return rel == prefix or rel.startswith(prefix + "/")
|
|
205
|
+
return False
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def _standard_test_path(rel: str) -> bool:
|
|
209
|
+
parts = rel.split("/")
|
|
210
|
+
if len(parts) >= 2 and parts[0] in ("test", "tests"):
|
|
211
|
+
return True
|
|
212
|
+
if any(parts[index:index + 2] == ["src", "test"] for index in range(len(parts) - 1)):
|
|
213
|
+
return True
|
|
214
|
+
return False
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def _allowed_test_path(project: Path, rel: str) -> bool:
|
|
218
|
+
config = _profile_config(project)
|
|
219
|
+
if config is None:
|
|
220
|
+
return _standard_test_path(rel)
|
|
221
|
+
patterns, excluded_roots = config
|
|
222
|
+
rel_parts = tuple(os.path.normcase(part) for part in rel.split("/"))
|
|
223
|
+
for excluded in excluded_roots:
|
|
224
|
+
excluded_parts = tuple(os.path.normcase(part) for part in excluded.split("/"))
|
|
225
|
+
if rel_parts[: len(excluded_parts)] == excluded_parts:
|
|
226
|
+
return False
|
|
227
|
+
return any(_matches_pattern(rel, pattern) for pattern in patterns)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def _validate_file(project: Path, raw: str) -> tuple[Path | None, str | None, str | None]:
|
|
231
|
+
candidate = Path(raw)
|
|
232
|
+
if not candidate.is_absolute():
|
|
233
|
+
candidate = project / candidate
|
|
234
|
+
resolved = candidate.resolve()
|
|
235
|
+
if not _inside(resolved, project):
|
|
236
|
+
return None, None, "PATH_OUTSIDE_PROJECT"
|
|
237
|
+
if not resolved.exists():
|
|
238
|
+
return None, None, "FILE_NOT_FOUND"
|
|
239
|
+
if not resolved.is_file():
|
|
240
|
+
return None, None, "NOT_REGULAR_FILE"
|
|
241
|
+
rel = resolved.relative_to(project).as_posix()
|
|
242
|
+
if not _allowed_test_path(project, rel):
|
|
243
|
+
return None, rel, "TEST_PATH_NOT_ALLOWED"
|
|
244
|
+
return resolved, rel, None
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def _entry_shape_valid(item: Any) -> bool:
|
|
248
|
+
return (
|
|
249
|
+
isinstance(item, dict)
|
|
250
|
+
and isinstance(item.get("path"), str)
|
|
251
|
+
and bool(item["path"])
|
|
252
|
+
and isinstance(item.get("sha256"), str)
|
|
253
|
+
and re.fullmatch(r"sha256:[0-9a-f]{64}", item["sha256"]) is not None
|
|
254
|
+
and item.get("reason") in REASONS
|
|
255
|
+
and type(item.get("ignored")) is bool
|
|
256
|
+
and type(item.get("trackedBefore")) is bool
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def _validate_existing_manifest(
|
|
261
|
+
project: Path,
|
|
262
|
+
manifest: Any,
|
|
263
|
+
*,
|
|
264
|
+
allow_hash_drift: set[str] | None = None,
|
|
265
|
+
require_files: bool,
|
|
266
|
+
) -> tuple[str | None, dict[str, dict[str, Any]]]:
|
|
267
|
+
if (
|
|
268
|
+
not isinstance(manifest, dict)
|
|
269
|
+
or manifest.get("schemaVersion") != SCHEMA_VERSION
|
|
270
|
+
or manifest.get("mode") != MODE
|
|
271
|
+
):
|
|
272
|
+
return "MANIFEST_INVALID", {}
|
|
273
|
+
if manifest.get("projectRoot") != str(project):
|
|
274
|
+
return "MANIFEST_PROJECT_MISMATCH", {}
|
|
275
|
+
entries = manifest.get("files")
|
|
276
|
+
if not isinstance(entries, list):
|
|
277
|
+
return "MANIFEST_INVALID", {}
|
|
278
|
+
if require_files and not entries:
|
|
279
|
+
return "EMPTY_MANIFEST", {}
|
|
280
|
+
|
|
281
|
+
allowed_drift = allow_hash_drift or set()
|
|
282
|
+
validated: dict[str, dict[str, Any]] = {}
|
|
283
|
+
for item in entries:
|
|
284
|
+
if not _entry_shape_valid(item):
|
|
285
|
+
return "MANIFEST_INVALID", {}
|
|
286
|
+
rel = item["path"]
|
|
287
|
+
if rel in validated:
|
|
288
|
+
return "MANIFEST_INVALID", {}
|
|
289
|
+
path, normalized, error = _validate_file(project, rel)
|
|
290
|
+
if error or normalized != rel or path is None:
|
|
291
|
+
return error or "MANIFEST_INVALID", {}
|
|
292
|
+
if item["sha256"] != _sha256(path) and rel not in allowed_drift:
|
|
293
|
+
return "HASH_DRIFT" if require_files else "MANIFEST_INVALID", {}
|
|
294
|
+
ignored_now = _is_ignored(project, rel)
|
|
295
|
+
tracked_now = _git(
|
|
296
|
+
project, "ls-files", "--error-unmatch", "--", rel
|
|
297
|
+
).returncode == 0
|
|
298
|
+
if item["ignored"] != ignored_now or item["trackedBefore"] != tracked_now:
|
|
299
|
+
return "MANIFEST_INVALID", {}
|
|
300
|
+
validated[rel] = dict(item)
|
|
301
|
+
return None, validated
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def record(
|
|
305
|
+
project: Path | str,
|
|
306
|
+
change_dir: Path | str,
|
|
307
|
+
files: list[str],
|
|
308
|
+
reason: str,
|
|
309
|
+
) -> dict[str, Any]:
|
|
310
|
+
action = "record"
|
|
311
|
+
project_root = Path(project).resolve()
|
|
312
|
+
if not files:
|
|
313
|
+
return _result(False, action, "EMPTY_FILES", [])
|
|
314
|
+
if reason not in REASONS:
|
|
315
|
+
return _result(False, action, "INVALID_REASON", [])
|
|
316
|
+
change_root = _change_dir(project_root, change_dir)
|
|
317
|
+
if change_root is None:
|
|
318
|
+
return _result(False, action, "CHANGE_DIR_OUTSIDE_PROJECT", [])
|
|
319
|
+
|
|
320
|
+
validated: list[tuple[Path, str]] = []
|
|
321
|
+
for raw in files:
|
|
322
|
+
path, rel, error = _validate_file(project_root, raw)
|
|
323
|
+
if error:
|
|
324
|
+
return _result(False, action, error, [rel or str(raw)])
|
|
325
|
+
assert path is not None and rel is not None
|
|
326
|
+
validated.append((path, rel))
|
|
327
|
+
|
|
328
|
+
manifest_path = _manifest_path(project_root, change_root)
|
|
329
|
+
if manifest_path is None:
|
|
330
|
+
return _result(False, action, "MANIFEST_PATH_OUTSIDE_PROJECT", [])
|
|
331
|
+
lock_path = manifest_path.with_name(manifest_path.name + ".lock")
|
|
332
|
+
try:
|
|
333
|
+
with _exclusive_lock(lock_path, wait_seconds=5.0):
|
|
334
|
+
if not _manifest_target_inside(project_root, manifest_path):
|
|
335
|
+
return _result(
|
|
336
|
+
False, action, "MANIFEST_PATH_OUTSIDE_PROJECT", []
|
|
337
|
+
)
|
|
338
|
+
existing_files: dict[str, dict[str, Any]] = {}
|
|
339
|
+
if manifest_path.is_file():
|
|
340
|
+
try:
|
|
341
|
+
existing = _read_json(manifest_path)
|
|
342
|
+
except (OSError, json.JSONDecodeError) as exc:
|
|
343
|
+
return _result(
|
|
344
|
+
False, action, "MANIFEST_INVALID", [], error=str(exc)
|
|
345
|
+
)
|
|
346
|
+
error, existing_files = _validate_existing_manifest(
|
|
347
|
+
project_root,
|
|
348
|
+
existing,
|
|
349
|
+
allow_hash_drift={rel for _, rel in validated},
|
|
350
|
+
require_files=False,
|
|
351
|
+
)
|
|
352
|
+
if error:
|
|
353
|
+
return _result(False, action, error, [])
|
|
354
|
+
|
|
355
|
+
for path, rel in validated:
|
|
356
|
+
ignored = _is_ignored(project_root, rel)
|
|
357
|
+
tracked = _git(
|
|
358
|
+
project_root, "ls-files", "--error-unmatch", "--", rel
|
|
359
|
+
).returncode == 0
|
|
360
|
+
existing_files[rel] = {
|
|
361
|
+
"path": rel,
|
|
362
|
+
"sha256": _sha256(path),
|
|
363
|
+
"reason": reason,
|
|
364
|
+
"ignored": ignored,
|
|
365
|
+
"trackedBefore": tracked,
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
manifest = {
|
|
369
|
+
"schemaVersion": SCHEMA_VERSION,
|
|
370
|
+
"mode": MODE,
|
|
371
|
+
"projectRoot": str(project_root),
|
|
372
|
+
"files": [existing_files[key] for key in sorted(existing_files)],
|
|
373
|
+
}
|
|
374
|
+
_write_json(manifest_path, manifest)
|
|
375
|
+
except LockUnavailable:
|
|
376
|
+
return _result(False, action, "MANIFEST_LOCKED", [])
|
|
377
|
+
return _result(True, action, "RECORDED", [rel for _, rel in validated], manifestPath=str(manifest_path))
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def _stage_locked(
|
|
381
|
+
project_root: Path, manifest_path: Path, index_path: Path
|
|
382
|
+
) -> dict[str, Any]:
|
|
383
|
+
action = "stage"
|
|
384
|
+
if not _manifest_target_inside(project_root, manifest_path):
|
|
385
|
+
return _result(False, action, "MANIFEST_PATH_OUTSIDE_PROJECT", [])
|
|
386
|
+
try:
|
|
387
|
+
manifest = _read_json(manifest_path)
|
|
388
|
+
except FileNotFoundError:
|
|
389
|
+
return _result(False, action, "MANIFEST_MISSING", [])
|
|
390
|
+
except (OSError, json.JSONDecodeError) as exc:
|
|
391
|
+
return _result(False, action, "MANIFEST_INVALID", [], error=str(exc))
|
|
392
|
+
error, entries = _validate_existing_manifest(
|
|
393
|
+
project_root, manifest, require_files=True
|
|
394
|
+
)
|
|
395
|
+
if error:
|
|
396
|
+
return _result(False, action, error, [])
|
|
397
|
+
rels = list(entries)
|
|
398
|
+
|
|
399
|
+
before_cached_result = _git(project_root, "diff", "--cached", "--name-only")
|
|
400
|
+
if before_cached_result.returncode != 0:
|
|
401
|
+
return _result(False, action, "CACHED_DIFF_FAILED", rels)
|
|
402
|
+
before_cached = set(before_cached_result.stdout.splitlines())
|
|
403
|
+
|
|
404
|
+
handle, temp_name = tempfile.mkstemp(
|
|
405
|
+
prefix=f".{index_path.name}.test-guard.", dir=index_path.parent
|
|
406
|
+
)
|
|
407
|
+
os.close(handle)
|
|
408
|
+
temp_index = Path(temp_name)
|
|
409
|
+
temp_index.unlink(missing_ok=True)
|
|
410
|
+
try:
|
|
411
|
+
if index_path.is_file():
|
|
412
|
+
shutil.copy2(index_path, temp_index)
|
|
413
|
+
added = _git(project_root, "add", "-f", "--", *rels, index_file=temp_index)
|
|
414
|
+
if added.returncode != 0:
|
|
415
|
+
return _result(
|
|
416
|
+
False,
|
|
417
|
+
action,
|
|
418
|
+
"GIT_ADD_FAILED",
|
|
419
|
+
rels,
|
|
420
|
+
error=added.stderr.strip(),
|
|
421
|
+
)
|
|
422
|
+
cached = _git(
|
|
423
|
+
project_root,
|
|
424
|
+
"diff",
|
|
425
|
+
"--cached",
|
|
426
|
+
"--name-only",
|
|
427
|
+
index_file=temp_index,
|
|
428
|
+
)
|
|
429
|
+
if cached.returncode != 0:
|
|
430
|
+
return _result(False, action, "CACHED_DIFF_FAILED", rels)
|
|
431
|
+
after_cached = set(cached.stdout.splitlines())
|
|
432
|
+
missing = [rel for rel in rels if rel not in after_cached]
|
|
433
|
+
unexpected = sorted((after_cached - before_cached) - set(rels))
|
|
434
|
+
if missing or unexpected:
|
|
435
|
+
return _result(
|
|
436
|
+
False,
|
|
437
|
+
action,
|
|
438
|
+
"CACHED_DIFF_MISMATCH",
|
|
439
|
+
missing or unexpected,
|
|
440
|
+
)
|
|
441
|
+
os.replace(temp_index, index_path)
|
|
442
|
+
return _result(True, action, "STAGED", rels)
|
|
443
|
+
finally:
|
|
444
|
+
temp_index.unlink(missing_ok=True)
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
def stage(project: Path | str, change_dir: Path | str) -> dict[str, Any]:
|
|
448
|
+
action = "stage"
|
|
449
|
+
project_root = Path(project).resolve()
|
|
450
|
+
change_root = _change_dir(project_root, change_dir)
|
|
451
|
+
if change_root is None:
|
|
452
|
+
return _result(False, action, "CHANGE_DIR_OUTSIDE_PROJECT", [])
|
|
453
|
+
manifest_path = _manifest_path(project_root, change_root)
|
|
454
|
+
if manifest_path is None:
|
|
455
|
+
return _result(False, action, "MANIFEST_PATH_OUTSIDE_PROJECT", [])
|
|
456
|
+
|
|
457
|
+
index_result = _git(project_root, "rev-parse", "--git-path", "index")
|
|
458
|
+
if index_result.returncode != 0 or not index_result.stdout.strip():
|
|
459
|
+
return _result(False, action, "GIT_INDEX_NOT_FOUND", [])
|
|
460
|
+
index_path = Path(index_result.stdout.strip())
|
|
461
|
+
if not index_path.is_absolute():
|
|
462
|
+
index_path = (project_root / index_path).resolve()
|
|
463
|
+
index_path.parent.mkdir(parents=True, exist_ok=True)
|
|
464
|
+
|
|
465
|
+
index_lock = index_path.with_name(index_path.name + ".lock")
|
|
466
|
+
try:
|
|
467
|
+
with _exclusive_lock(index_lock, wait_seconds=0.0):
|
|
468
|
+
manifest_lock = manifest_path.with_name(manifest_path.name + ".lock")
|
|
469
|
+
try:
|
|
470
|
+
with _exclusive_lock(manifest_lock, wait_seconds=0.0):
|
|
471
|
+
return _stage_locked(project_root, manifest_path, index_path)
|
|
472
|
+
except LockUnavailable:
|
|
473
|
+
return _result(False, action, "MANIFEST_LOCKED", [])
|
|
474
|
+
except LockUnavailable:
|
|
475
|
+
return _result(False, action, "INDEX_LOCKED", [])
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
def main(argv: list[str] | None = None) -> int:
|
|
479
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
480
|
+
sub = parser.add_subparsers(dest="action", required=True)
|
|
481
|
+
record_parser = sub.add_parser("record")
|
|
482
|
+
record_parser.add_argument("--project", required=True)
|
|
483
|
+
record_parser.add_argument("--change-dir", required=True)
|
|
484
|
+
record_parser.add_argument("--files", required=True)
|
|
485
|
+
record_parser.add_argument("--reason", required=True, choices=REASONS)
|
|
486
|
+
record_parser.add_argument("--json", action="store_true")
|
|
487
|
+
stage_parser = sub.add_parser("stage")
|
|
488
|
+
stage_parser.add_argument("--project", required=True)
|
|
489
|
+
stage_parser.add_argument("--change-dir", required=True)
|
|
490
|
+
stage_parser.add_argument("--json", action="store_true")
|
|
491
|
+
args = parser.parse_args(argv)
|
|
492
|
+
if args.action == "record":
|
|
493
|
+
files = [item.strip() for item in args.files.split(",") if item.strip()]
|
|
494
|
+
result = record(args.project, args.change_dir, files, args.reason)
|
|
495
|
+
else:
|
|
496
|
+
result = stage(args.project, args.change_dir)
|
|
497
|
+
print(json.dumps(result, ensure_ascii=False, indent=2 if args.json else None))
|
|
498
|
+
return 0 if result["ok"] else 1
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
if __name__ == "__main__":
|
|
502
|
+
raise SystemExit(main())
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
"schema_version": 2,
|
|
3
3
|
"profile": "general",
|
|
4
4
|
"adapter": "claude-code",
|
|
5
|
-
"bundle_version": "0.2.
|
|
5
|
+
"bundle_version": "0.2.5",
|
|
6
6
|
"generator": "harness_deploy.py",
|
|
7
7
|
"files": [
|
|
8
8
|
{
|
|
9
9
|
"path": ".harness-build.json",
|
|
10
|
-
"sha256": "
|
|
10
|
+
"sha256": "71e04fb13b0cb8d4db07617e87c7209f4c3effcd1782fd7a4fdd7e457069d1ca"
|
|
11
11
|
},
|
|
12
12
|
{
|
|
13
13
|
"path": "agents/harness-evaluator.md",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
},
|
|
40
40
|
{
|
|
41
41
|
"path": "harness-archive/SKILL.md",
|
|
42
|
-
"sha256": "
|
|
42
|
+
"sha256": "827e9c212e0ae518302816a1987757330cd1b4878f8cff1cfbd2275da803c10d"
|
|
43
43
|
},
|
|
44
44
|
{
|
|
45
45
|
"path": "harness-archive/templates/render-summary.mjs",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
},
|
|
60
60
|
{
|
|
61
61
|
"path": "harness-codebase-map/SKILL.md",
|
|
62
|
-
"sha256": "
|
|
62
|
+
"sha256": "0cc5ab78b7d73ddc9e71423788c5148c90dcf3cc1ea36e790e058bb4285d09a1"
|
|
63
63
|
},
|
|
64
64
|
{
|
|
65
65
|
"path": "harness-codebase-map/templates/ARCHITECTURE.md",
|
|
@@ -123,11 +123,11 @@
|
|
|
123
123
|
},
|
|
124
124
|
{
|
|
125
125
|
"path": "harness-knowledge-ingest/SKILL.md",
|
|
126
|
-
"sha256": "
|
|
126
|
+
"sha256": "cfe85e03c9376d90c7dac244f3bb1b90a0fd0691d94f24151b3e2250e0062f26"
|
|
127
127
|
},
|
|
128
128
|
{
|
|
129
129
|
"path": "harness-knowledge-query/SKILL.md",
|
|
130
|
-
"sha256": "
|
|
130
|
+
"sha256": "dcce1d851ad2a6c9a261f53b078193080ee373f41757f4b45bdaf60c21f6759e"
|
|
131
131
|
},
|
|
132
132
|
{
|
|
133
133
|
"path": "harness-plan/checklist.md",
|
|
@@ -143,7 +143,7 @@
|
|
|
143
143
|
},
|
|
144
144
|
{
|
|
145
145
|
"path": "harness-plan/SKILL.md",
|
|
146
|
-
"sha256": "
|
|
146
|
+
"sha256": "43fcc5be953de9835a66a54d759a5756579388bd4bc3ac402861e103ed058a6b"
|
|
147
147
|
},
|
|
148
148
|
{
|
|
149
149
|
"path": "harness-review/checklist.md",
|
|
@@ -159,11 +159,11 @@
|
|
|
159
159
|
},
|
|
160
160
|
{
|
|
161
161
|
"path": "harness-review/SKILL.md",
|
|
162
|
-
"sha256": "
|
|
162
|
+
"sha256": "e1994473cebf329c0179728e7bf9a3ebe3d1905a47e0df63f90d407f1e115abd"
|
|
163
163
|
},
|
|
164
164
|
{
|
|
165
165
|
"path": "harness-run/checklist.md",
|
|
166
|
-
"sha256": "
|
|
166
|
+
"sha256": "350b352f2a85053449cf63a249780d30efd3be28718e912201388cbd0b4480a5"
|
|
167
167
|
},
|
|
168
168
|
{
|
|
169
169
|
"path": "harness-run/protocols.md",
|
|
@@ -171,15 +171,15 @@
|
|
|
171
171
|
},
|
|
172
172
|
{
|
|
173
173
|
"path": "harness-run/reference.md",
|
|
174
|
-
"sha256": "
|
|
174
|
+
"sha256": "dccaafad9a761284a13e2d909e2a79994698a506caa76148798647ed554d85da"
|
|
175
175
|
},
|
|
176
176
|
{
|
|
177
177
|
"path": "harness-run/SKILL.md",
|
|
178
|
-
"sha256": "
|
|
178
|
+
"sha256": "6760cab591f9a2bd9b096c2a2ce6fcde3fcfb2d88e7f58d946de333a7b954b84"
|
|
179
179
|
},
|
|
180
180
|
{
|
|
181
181
|
"path": "harness-submit/checklist.md",
|
|
182
|
-
"sha256": "
|
|
182
|
+
"sha256": "6b2343c6e8fdfc32b92b7dcd404487b9d25c404790a6e232a2292e5b996f90a2"
|
|
183
183
|
},
|
|
184
184
|
{
|
|
185
185
|
"path": "harness-submit/reference.md",
|
|
@@ -187,7 +187,7 @@
|
|
|
187
187
|
},
|
|
188
188
|
{
|
|
189
189
|
"path": "harness-submit/SKILL.md",
|
|
190
|
-
"sha256": "
|
|
190
|
+
"sha256": "c53afc1bb8ddb9c0e2ebb50e7f69907bc352fd3a363aaf843fe9d33d20c95be3"
|
|
191
191
|
},
|
|
192
192
|
{
|
|
193
193
|
"path": "harness-sync/reference.md",
|
|
@@ -195,11 +195,11 @@
|
|
|
195
195
|
},
|
|
196
196
|
{
|
|
197
197
|
"path": "harness-sync/SKILL.md",
|
|
198
|
-
"sha256": "
|
|
198
|
+
"sha256": "5e6b0aa3964fa25abc3e41b4a37c26dfa2151a507189cbd572ee18bf9641fe0b"
|
|
199
199
|
},
|
|
200
200
|
{
|
|
201
201
|
"path": "harness-test/checklist.md",
|
|
202
|
-
"sha256": "
|
|
202
|
+
"sha256": "a8bd0424bfc64fc60f2a54927a381ce25c088bb267f01c88dce9c409d6b2f010"
|
|
203
203
|
},
|
|
204
204
|
{
|
|
205
205
|
"path": "harness-test/pitfalls.md",
|
|
@@ -207,7 +207,7 @@
|
|
|
207
207
|
},
|
|
208
208
|
{
|
|
209
209
|
"path": "harness-test/reference.md",
|
|
210
|
-
"sha256": "
|
|
210
|
+
"sha256": "5a9ed764ea1f600c67456f42ecaea88147297796270137ea67afd09177801bc4"
|
|
211
211
|
},
|
|
212
212
|
{
|
|
213
213
|
"path": "harness-test/scripts/runtime-helpers.mjs",
|
|
@@ -215,11 +215,11 @@
|
|
|
215
215
|
},
|
|
216
216
|
{
|
|
217
217
|
"path": "harness-test/SKILL.md",
|
|
218
|
-
"sha256": "
|
|
218
|
+
"sha256": "be0aa762acafea69303adc55b5b1ed154821c951a1ad6c4eaea090b05ebcb17b"
|
|
219
219
|
},
|
|
220
220
|
{
|
|
221
221
|
"path": "protocols/archive-report-protocol.md",
|
|
222
|
-
"sha256": "
|
|
222
|
+
"sha256": "587047dfb6f308079d2bd9074f6b396570dfb11aecc401b0c63ae8edb5e806e4"
|
|
223
223
|
},
|
|
224
224
|
{
|
|
225
225
|
"path": "protocols/evidence-based-reporting-protocol.md",
|
|
@@ -227,7 +227,7 @@
|
|
|
227
227
|
},
|
|
228
228
|
{
|
|
229
229
|
"path": "protocols/ledger-protocol.md",
|
|
230
|
-
"sha256": "
|
|
230
|
+
"sha256": "2621749f0a1193015b185e3ebd5bd66e738db25fa091e4e27b3eaae06c950736"
|
|
231
231
|
},
|
|
232
232
|
{
|
|
233
233
|
"path": "protocols/powershell-protocol.md",
|
|
@@ -271,7 +271,7 @@
|
|
|
271
271
|
},
|
|
272
272
|
{
|
|
273
273
|
"path": "scripts/harness_ledger.py",
|
|
274
|
-
"sha256": "
|
|
274
|
+
"sha256": "36a9921ff70e17b3c8946db1846bf447607822ad90e17351b88efab189d15b41"
|
|
275
275
|
},
|
|
276
276
|
{
|
|
277
277
|
"path": "scripts/harness_preflight.py",
|
|
@@ -279,7 +279,7 @@
|
|
|
279
279
|
},
|
|
280
280
|
{
|
|
281
281
|
"path": "scripts/harness_profile.py",
|
|
282
|
-
"sha256": "
|
|
282
|
+
"sha256": "718e2f3a9e754850935e37c803bc16addb855061371b3351ec22fdc4b376c339"
|
|
283
283
|
},
|
|
284
284
|
{
|
|
285
285
|
"path": "scripts/harness_service.py",
|
|
@@ -288,6 +288,10 @@
|
|
|
288
288
|
{
|
|
289
289
|
"path": "scripts/harness_state.py",
|
|
290
290
|
"sha256": "b33efacf9aba471f9f542f738c653acf274ed3ce57c4326520036e6df0f18966"
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
"path": "scripts/harness_test_guard.py",
|
|
294
|
+
"sha256": "a6ad6f3619babb9ffc2bab16b3184ec916cc34f52b977dc4a5446e3e76fc894e"
|
|
291
295
|
}
|
|
292
296
|
]
|
|
293
297
|
}
|