@hunter-harness/workflow-harness 0.2.46 → 0.2.48
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/README.md +1 -1
- package/harness/bundles/general/claude-code/.harness-build.json +1 -1
- package/harness/bundles/general/claude-code/README.md +9 -8
- package/harness/bundles/general/claude-code/harness-archive/SKILL.md +10 -11
- 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-ingest/scripts/harness_knowledge.py +347 -1
- package/harness/bundles/general/claude-code/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-plan/SKILL.md +6 -3
- package/harness/bundles/general/claude-code/harness-review/SKILL.md +6 -7
- package/harness/bundles/general/claude-code/harness-run/SKILL.md +6 -3
- package/harness/bundles/general/claude-code/harness-submit/SKILL.md +5 -7
- package/harness/bundles/general/claude-code/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-test/SKILL.md +6 -3
- package/harness/bundles/general/claude-code/scripts/harness_archive.py +164 -17
- package/harness/bundles/general/claude-code/scripts/harness_events.py +30 -1
- package/harness/bundles/general/claude-code/scripts/harness_events_sync.py +269 -0
- package/harness/bundles/general/claude-code/scripts/harness_gate.py +222 -64
- package/harness/bundles/general/claude-code/scripts/harness_headless.py +37 -0
- package/harness/bundles/general/codebuddy/.harness-build.json +1 -1
- package/harness/bundles/general/codebuddy/README.md +9 -8
- package/harness/bundles/general/codebuddy/harness-archive/SKILL.md +10 -12
- 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-ingest/scripts/harness_knowledge.py +347 -1
- package/harness/bundles/general/codebuddy/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-plan/SKILL.md +6 -3
- package/harness/bundles/general/codebuddy/harness-review/SKILL.md +6 -7
- package/harness/bundles/general/codebuddy/harness-run/SKILL.md +6 -3
- package/harness/bundles/general/codebuddy/harness-submit/SKILL.md +5 -8
- package/harness/bundles/general/codebuddy/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-test/SKILL.md +6 -3
- package/harness/bundles/general/codebuddy/scripts/harness_archive.py +164 -17
- package/harness/bundles/general/codebuddy/scripts/harness_events.py +30 -1
- package/harness/bundles/general/codebuddy/scripts/harness_events_sync.py +269 -0
- package/harness/bundles/general/codebuddy/scripts/harness_gate.py +222 -64
- package/harness/bundles/general/codebuddy/scripts/harness_headless.py +37 -0
- package/harness/bundles/general/codex/.harness-build.json +1 -1
- package/harness/bundles/general/codex/README.md +9 -8
- package/harness/bundles/general/codex/harness-archive/SKILL.md +10 -12
- 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-ingest/scripts/harness_knowledge.py +347 -1
- package/harness/bundles/general/codex/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-plan/SKILL.md +6 -3
- package/harness/bundles/general/codex/harness-review/SKILL.md +6 -7
- package/harness/bundles/general/codex/harness-run/SKILL.md +6 -3
- package/harness/bundles/general/codex/harness-submit/SKILL.md +5 -8
- package/harness/bundles/general/codex/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-test/SKILL.md +6 -3
- package/harness/bundles/general/codex/scripts/harness_archive.py +164 -17
- package/harness/bundles/general/codex/scripts/harness_events.py +30 -1
- package/harness/bundles/general/codex/scripts/harness_events_sync.py +269 -0
- package/harness/bundles/general/codex/scripts/harness_gate.py +222 -64
- package/harness/bundles/general/codex/scripts/harness_headless.py +37 -0
- package/harness/bundles/general/cursor/.harness-build.json +1 -1
- package/harness/bundles/general/cursor/README.md +9 -8
- package/harness/bundles/general/cursor/harness-archive/SKILL.md +10 -12
- 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-ingest/scripts/harness_knowledge.py +347 -1
- package/harness/bundles/general/cursor/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-plan/SKILL.md +6 -3
- package/harness/bundles/general/cursor/harness-review/SKILL.md +6 -7
- package/harness/bundles/general/cursor/harness-run/SKILL.md +6 -3
- package/harness/bundles/general/cursor/harness-submit/SKILL.md +5 -8
- package/harness/bundles/general/cursor/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-test/SKILL.md +6 -3
- package/harness/bundles/general/cursor/scripts/harness_archive.py +164 -17
- package/harness/bundles/general/cursor/scripts/harness_events.py +30 -1
- package/harness/bundles/general/cursor/scripts/harness_events_sync.py +269 -0
- package/harness/bundles/general/cursor/scripts/harness_gate.py +222 -64
- package/harness/bundles/general/cursor/scripts/harness_headless.py +37 -0
- package/harness/bundles/java/claude-code/.harness-build.json +1 -1
- package/harness/bundles/java/claude-code/README.md +9 -8
- package/harness/bundles/java/claude-code/harness-apidoc/SKILL.md +3 -2
- package/harness/bundles/java/claude-code/harness-archive/SKILL.md +10 -11
- 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-ingest/scripts/harness_knowledge.py +347 -1
- package/harness/bundles/java/claude-code/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-package/SKILL.md +3 -2
- package/harness/bundles/java/claude-code/harness-plan/SKILL.md +6 -3
- package/harness/bundles/java/claude-code/harness-review/SKILL.md +6 -7
- package/harness/bundles/java/claude-code/harness-run/SKILL.md +6 -3
- package/harness/bundles/java/claude-code/harness-submit/SKILL.md +5 -7
- package/harness/bundles/java/claude-code/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-test/SKILL.md +6 -3
- package/harness/bundles/java/claude-code/scripts/harness_archive.py +164 -17
- package/harness/bundles/java/claude-code/scripts/harness_events.py +30 -1
- package/harness/bundles/java/claude-code/scripts/harness_events_sync.py +269 -0
- package/harness/bundles/java/claude-code/scripts/harness_gate.py +222 -64
- package/harness/bundles/java/claude-code/scripts/harness_headless.py +37 -0
- package/harness/bundles/java/codebuddy/.harness-build.json +1 -1
- package/harness/bundles/java/codebuddy/README.md +9 -8
- package/harness/bundles/java/codebuddy/harness-apidoc/SKILL.md +3 -2
- package/harness/bundles/java/codebuddy/harness-archive/SKILL.md +10 -12
- 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-ingest/scripts/harness_knowledge.py +347 -1
- package/harness/bundles/java/codebuddy/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-package/SKILL.md +2 -2
- package/harness/bundles/java/codebuddy/harness-plan/SKILL.md +6 -3
- package/harness/bundles/java/codebuddy/harness-review/SKILL.md +6 -7
- package/harness/bundles/java/codebuddy/harness-run/SKILL.md +6 -3
- package/harness/bundles/java/codebuddy/harness-submit/SKILL.md +5 -8
- package/harness/bundles/java/codebuddy/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-test/SKILL.md +6 -3
- package/harness/bundles/java/codebuddy/scripts/harness_archive.py +164 -17
- package/harness/bundles/java/codebuddy/scripts/harness_events.py +30 -1
- package/harness/bundles/java/codebuddy/scripts/harness_events_sync.py +269 -0
- package/harness/bundles/java/codebuddy/scripts/harness_gate.py +222 -64
- package/harness/bundles/java/codebuddy/scripts/harness_headless.py +37 -0
- package/harness/bundles/java/codex/.harness-build.json +1 -1
- package/harness/bundles/java/codex/README.md +9 -8
- package/harness/bundles/java/codex/harness-apidoc/SKILL.md +3 -2
- package/harness/bundles/java/codex/harness-archive/SKILL.md +10 -12
- 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-ingest/scripts/harness_knowledge.py +347 -1
- package/harness/bundles/java/codex/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-package/SKILL.md +2 -2
- package/harness/bundles/java/codex/harness-plan/SKILL.md +6 -3
- package/harness/bundles/java/codex/harness-review/SKILL.md +6 -7
- package/harness/bundles/java/codex/harness-run/SKILL.md +6 -3
- package/harness/bundles/java/codex/harness-submit/SKILL.md +5 -8
- package/harness/bundles/java/codex/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-test/SKILL.md +6 -3
- package/harness/bundles/java/codex/scripts/harness_archive.py +164 -17
- package/harness/bundles/java/codex/scripts/harness_events.py +30 -1
- package/harness/bundles/java/codex/scripts/harness_events_sync.py +269 -0
- package/harness/bundles/java/codex/scripts/harness_gate.py +222 -64
- package/harness/bundles/java/codex/scripts/harness_headless.py +37 -0
- package/harness/bundles/java/cursor/.harness-build.json +1 -1
- package/harness/bundles/java/cursor/README.md +9 -8
- package/harness/bundles/java/cursor/harness-apidoc/SKILL.md +3 -2
- package/harness/bundles/java/cursor/harness-archive/SKILL.md +10 -12
- 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-ingest/scripts/harness_knowledge.py +347 -1
- package/harness/bundles/java/cursor/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-package/SKILL.md +2 -2
- package/harness/bundles/java/cursor/harness-plan/SKILL.md +6 -3
- package/harness/bundles/java/cursor/harness-review/SKILL.md +6 -7
- package/harness/bundles/java/cursor/harness-run/SKILL.md +6 -3
- package/harness/bundles/java/cursor/harness-submit/SKILL.md +5 -8
- package/harness/bundles/java/cursor/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-test/SKILL.md +6 -3
- package/harness/bundles/java/cursor/scripts/harness_archive.py +164 -17
- package/harness/bundles/java/cursor/scripts/harness_events.py +30 -1
- package/harness/bundles/java/cursor/scripts/harness_events_sync.py +269 -0
- package/harness/bundles/java/cursor/scripts/harness_gate.py +222 -64
- package/harness/bundles/java/cursor/scripts/harness_headless.py +37 -0
- package/harness/manifests/general/claude-code.json +26 -18
- package/harness/manifests/general/codebuddy.json +26 -18
- package/harness/manifests/general/codex.json +26 -18
- package/harness/manifests/general/cursor.json +26 -18
- package/harness/manifests/java/claude-code.json +28 -20
- package/harness/manifests/java/codebuddy.json +28 -20
- package/harness/manifests/java/codex.json +28 -20
- package/harness/manifests/java/cursor.json +28 -20
- package/hunter-workflow-family.json +4 -4
- package/package.json +1 -1
|
@@ -124,6 +124,93 @@ def _read_json(path: Path) -> Any:
|
|
|
124
124
|
return json.loads(path.read_text(encoding="utf-8-sig"))
|
|
125
125
|
|
|
126
126
|
|
|
127
|
+
# ---------------------------------------------------------------------------
|
|
128
|
+
# Gate severity tiering (P1 controllability)
|
|
129
|
+
#
|
|
130
|
+
# Hard invariants stay fail-closed in every mode:
|
|
131
|
+
# 1. submit/merge identity & final-hash consistency (ledger/integration);
|
|
132
|
+
# 2. archive integrity (manifest/minimal set, enforced in harness_archive);
|
|
133
|
+
# 3. destructive-action protection (cleanup topology / protected paths).
|
|
134
|
+
# The sites listed in SOFT_GATE_SITES guard regenerable bookkeeping
|
|
135
|
+
# (plan-handoff projections, phase capsules, scenario coverage receipts,
|
|
136
|
+
# test-guard tracking). In "lenient" mode a failure at one of those sites is
|
|
137
|
+
# recorded as a WARN receipt (evidence/gate-warnings.ndjson) instead of
|
|
138
|
+
# blocking the phase. Release phases (submit/merge/archive/release/deploy)
|
|
139
|
+
# never downgrade.
|
|
140
|
+
# ---------------------------------------------------------------------------
|
|
141
|
+
|
|
142
|
+
GATE_RELEASE_PHASES = frozenset({"submit", "merge", "archive", "release", "deploy"})
|
|
143
|
+
SOFT_GATE_SITES = frozenset({
|
|
144
|
+
"plan-handoff",
|
|
145
|
+
"capsule",
|
|
146
|
+
"scenario-coverage",
|
|
147
|
+
"test-guard",
|
|
148
|
+
})
|
|
149
|
+
GATE_WARNINGS_REL = Path("evidence") / "gate-warnings.ndjson"
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def gate_severity_mode(project: Path, change_dir: Path | None = None) -> str:
|
|
153
|
+
"""Resolve gate severity mode: env > change gate-policy > project config.
|
|
154
|
+
|
|
155
|
+
Default is ``lenient`` (soft regenerable sites → WARN). Release phases and
|
|
156
|
+
the three hard invariants stay fail-closed; set ``HUNTER_HARNESS_GATE_MODE``
|
|
157
|
+
or ``gate-policy.json`` ``severityMode`` to ``strict`` to force fail-closed
|
|
158
|
+
soft sites as well.
|
|
159
|
+
"""
|
|
160
|
+
env_mode = str(os.environ.get("HUNTER_HARNESS_GATE_MODE") or "").strip().lower()
|
|
161
|
+
if env_mode in {"strict", "lenient"}:
|
|
162
|
+
return env_mode
|
|
163
|
+
candidates: list[Path] = []
|
|
164
|
+
if change_dir is not None:
|
|
165
|
+
candidates.append(change_dir / "meta" / "gate-policy.json")
|
|
166
|
+
candidates.append(project / ".harness" / "config" / "gate-policy.json")
|
|
167
|
+
for path in candidates:
|
|
168
|
+
if not path.is_file():
|
|
169
|
+
continue
|
|
170
|
+
try:
|
|
171
|
+
document = _read_json(path)
|
|
172
|
+
except (OSError, json.JSONDecodeError):
|
|
173
|
+
continue
|
|
174
|
+
if not isinstance(document, dict):
|
|
175
|
+
continue
|
|
176
|
+
value = str(document.get("severityMode") or "").strip().lower()
|
|
177
|
+
if value in {"strict", "lenient"}:
|
|
178
|
+
return value
|
|
179
|
+
return "lenient"
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def gate_soft_allowed(mode: str, phase: str, site: str) -> bool:
|
|
183
|
+
return (
|
|
184
|
+
mode == "lenient"
|
|
185
|
+
and phase not in GATE_RELEASE_PHASES
|
|
186
|
+
and site in SOFT_GATE_SITES
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def record_gate_warning(
|
|
191
|
+
change_dir: Path,
|
|
192
|
+
*,
|
|
193
|
+
phase: str,
|
|
194
|
+
site: str,
|
|
195
|
+
code: str,
|
|
196
|
+
message: str,
|
|
197
|
+
) -> dict[str, Any]:
|
|
198
|
+
"""Append a downgraded-gate receipt; the phase continues with WARN."""
|
|
199
|
+
entry = {
|
|
200
|
+
"ts": he.now_iso(),
|
|
201
|
+
"phase": phase,
|
|
202
|
+
"site": site,
|
|
203
|
+
"code": code,
|
|
204
|
+
"message": message,
|
|
205
|
+
"mode": "lenient",
|
|
206
|
+
}
|
|
207
|
+
path = change_dir / GATE_WARNINGS_REL
|
|
208
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
209
|
+
with path.open("a", encoding="utf-8", newline="\n") as handle:
|
|
210
|
+
handle.write(json.dumps(entry, ensure_ascii=False, separators=(",", ":")) + "\n")
|
|
211
|
+
return entry
|
|
212
|
+
|
|
213
|
+
|
|
127
214
|
def _write_json(path: Path, data: Any) -> None:
|
|
128
215
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
129
216
|
text = json.dumps(data, ensure_ascii=False, indent=2) + "\n"
|
|
@@ -1561,7 +1648,8 @@ def append_phase_event(
|
|
|
1561
1648
|
he.atomic_append_line(events_file, line)
|
|
1562
1649
|
rendered = False
|
|
1563
1650
|
log_path = None
|
|
1564
|
-
if type_ == "phase.end" or auto_sealed
|
|
1651
|
+
if (type_ == "phase.end" or auto_sealed) and \
|
|
1652
|
+
he.execution_log_render_enabled(change_dir):
|
|
1565
1653
|
events = he.load_events(events_file)
|
|
1566
1654
|
content = he.render_execution_log(events)
|
|
1567
1655
|
log_path = he.write_execution_log(change_dir, content)
|
|
@@ -1882,6 +1970,8 @@ def cmd_begin(args: argparse.Namespace) -> int:
|
|
|
1882
1970
|
extra={k: v for k, v in resolved.items() if k not in {"ok", "message"}},
|
|
1883
1971
|
)
|
|
1884
1972
|
change_dir = Path(resolved["changeDir"])
|
|
1973
|
+
severity_mode = gate_severity_mode(project, change_dir)
|
|
1974
|
+
gate_warnings: list[dict[str, Any]] = []
|
|
1885
1975
|
plan_verification: dict[str, Any] | None = None
|
|
1886
1976
|
if args.phase == "run":
|
|
1887
1977
|
plan_verification = validate_plan_handoff(change_dir)
|
|
@@ -1894,16 +1984,25 @@ def cmd_begin(args: argparse.Namespace) -> int:
|
|
|
1894
1984
|
or plan_verification.get("message")
|
|
1895
1985
|
or "finalized plan verification failed"
|
|
1896
1986
|
)
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1987
|
+
if gate_soft_allowed(severity_mode, args.phase, "plan-handoff"):
|
|
1988
|
+
gate_warnings.append(record_gate_warning(
|
|
1989
|
+
change_dir,
|
|
1990
|
+
phase=args.phase,
|
|
1991
|
+
site="plan-handoff",
|
|
1992
|
+
code=code,
|
|
1993
|
+
message=message,
|
|
1994
|
+
))
|
|
1995
|
+
else:
|
|
1996
|
+
return emit_error(
|
|
1997
|
+
code,
|
|
1998
|
+
message,
|
|
1999
|
+
as_json=as_json,
|
|
2000
|
+
extra={
|
|
2001
|
+
key: value
|
|
2002
|
+
for key, value in plan_verification.items()
|
|
2003
|
+
if key not in {"ok", "code", "error", "message"}
|
|
2004
|
+
},
|
|
2005
|
+
)
|
|
1907
2006
|
concurrency_block = hc.check_concurrency_block(project, resolved["changeId"])
|
|
1908
2007
|
if concurrency_block is not None:
|
|
1909
2008
|
record_blocked_attempt(
|
|
@@ -1998,7 +2097,18 @@ def cmd_begin(args: argparse.Namespace) -> int:
|
|
|
1998
2097
|
skills_root=Path(args.skills_root).expanduser(),
|
|
1999
2098
|
)
|
|
2000
2099
|
except ValueError as exc:
|
|
2001
|
-
|
|
2100
|
+
if gate_soft_allowed(severity_mode, args.phase, "capsule"):
|
|
2101
|
+
gate_warnings.append(record_gate_warning(
|
|
2102
|
+
change_dir,
|
|
2103
|
+
phase=args.phase,
|
|
2104
|
+
site="capsule",
|
|
2105
|
+
code="PHASE_CAPSULE_MISMATCH",
|
|
2106
|
+
message=str(exc),
|
|
2107
|
+
))
|
|
2108
|
+
# Stale capsule is regenerable state: rebuild it below.
|
|
2109
|
+
capsule = None
|
|
2110
|
+
else:
|
|
2111
|
+
return emit_error("PHASE_CAPSULE_MISMATCH", str(exc), as_json=as_json)
|
|
2002
2112
|
claim = hc.claim_lease(
|
|
2003
2113
|
project,
|
|
2004
2114
|
change_id=resolved["changeId"],
|
|
@@ -2022,18 +2132,27 @@ def cmd_begin(args: argparse.Namespace) -> int:
|
|
|
2022
2132
|
if args.phase in {"run", "test"}:
|
|
2023
2133
|
guard_result = htg.begin(execution_root, change_dir)
|
|
2024
2134
|
if not guard_result.get("ok"):
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2135
|
+
if gate_soft_allowed(severity_mode, args.phase, "test-guard"):
|
|
2136
|
+
gate_warnings.append(record_gate_warning(
|
|
2137
|
+
change_dir,
|
|
2138
|
+
phase=args.phase,
|
|
2139
|
+
site="test-guard",
|
|
2140
|
+
code=str(guard_result.get("code", "TEST_GUARD_BEGIN_FAILED")),
|
|
2141
|
+
message="test guard begin failed (downgraded to WARN)",
|
|
2142
|
+
))
|
|
2143
|
+
else:
|
|
2144
|
+
hc.release_lease(
|
|
2145
|
+
project,
|
|
2146
|
+
change_id=resolved["changeId"],
|
|
2147
|
+
phase=args.phase,
|
|
2148
|
+
run_id=run_id,
|
|
2149
|
+
)
|
|
2150
|
+
return emit_error(
|
|
2151
|
+
str(guard_result.get("code", "TEST_GUARD_BEGIN_FAILED")),
|
|
2152
|
+
"test guard begin failed",
|
|
2153
|
+
as_json=as_json,
|
|
2154
|
+
extra=guard_result,
|
|
2155
|
+
)
|
|
2037
2156
|
state_root = Path(hp.resolve_state_dir_for_contract(change_dir)).resolve()
|
|
2038
2157
|
current_head = _git_text(execution_root, "rev-parse", "--verify", "HEAD")
|
|
2039
2158
|
ledger, _ = hl.load_ledger(change_dir)
|
|
@@ -2091,6 +2210,8 @@ def cmd_begin(args: argparse.Namespace) -> int:
|
|
|
2091
2210
|
"planVerification": plan_verification,
|
|
2092
2211
|
"projection": projection,
|
|
2093
2212
|
"executionRootSource": execution_hint["source"],
|
|
2213
|
+
"gateSeverityMode": severity_mode,
|
|
2214
|
+
"gateWarnings": gate_warnings,
|
|
2094
2215
|
}
|
|
2095
2216
|
emit(payload, as_json=as_json)
|
|
2096
2217
|
return 0
|
|
@@ -2108,6 +2229,8 @@ def cmd_close(args: argparse.Namespace) -> int:
|
|
|
2108
2229
|
extra={k: v for k, v in resolved.items() if k not in {"ok", "message"}},
|
|
2109
2230
|
)
|
|
2110
2231
|
change_dir = Path(resolved["changeDir"])
|
|
2232
|
+
severity_mode = gate_severity_mode(project, change_dir)
|
|
2233
|
+
gate_warnings: list[dict[str, Any]] = []
|
|
2111
2234
|
blocked = foundation_gate_blocks(getattr(args, "task", None), change_dir)
|
|
2112
2235
|
if blocked:
|
|
2113
2236
|
return emit_error(
|
|
@@ -2204,18 +2327,27 @@ def cmd_close(args: argparse.Namespace) -> int:
|
|
|
2204
2327
|
allow_head_advance=args.phase in {"run", "submit", "merge"},
|
|
2205
2328
|
)
|
|
2206
2329
|
except ValueError as exc:
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2330
|
+
if gate_soft_allowed(severity_mode, args.phase, "capsule"):
|
|
2331
|
+
gate_warnings.append(record_gate_warning(
|
|
2332
|
+
change_dir,
|
|
2333
|
+
phase=args.phase,
|
|
2334
|
+
site="capsule",
|
|
2335
|
+
code="PHASE_CAPSULE_MISMATCH",
|
|
2336
|
+
message=str(exc),
|
|
2337
|
+
))
|
|
2338
|
+
else:
|
|
2339
|
+
persist_close_failure(
|
|
2340
|
+
change_dir,
|
|
2341
|
+
args.phase,
|
|
2342
|
+
run_id,
|
|
2343
|
+
capsule,
|
|
2344
|
+
status="CAPSULE_VALIDATION_FAILED",
|
|
2345
|
+
error={
|
|
2346
|
+
"code": "PHASE_CAPSULE_MISMATCH",
|
|
2347
|
+
"message": str(exc),
|
|
2348
|
+
},
|
|
2349
|
+
)
|
|
2350
|
+
return emit_error("PHASE_CAPSULE_MISMATCH", str(exc), as_json=as_json)
|
|
2219
2351
|
|
|
2220
2352
|
projection = evaluate_projection_gate(project, args.phase)
|
|
2221
2353
|
if not projection.get("ok"):
|
|
@@ -2294,20 +2426,29 @@ def cmd_close(args: argparse.Namespace) -> int:
|
|
|
2294
2426
|
if args.phase in {"run", "test"}:
|
|
2295
2427
|
coverage = _validate_scenario_coverage(change_dir)
|
|
2296
2428
|
if not coverage.get("ok"):
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2429
|
+
if gate_soft_allowed(severity_mode, args.phase, "scenario-coverage"):
|
|
2430
|
+
gate_warnings.append(record_gate_warning(
|
|
2431
|
+
change_dir,
|
|
2432
|
+
phase=args.phase,
|
|
2433
|
+
site="scenario-coverage",
|
|
2434
|
+
code=str(coverage.get("code", "SCENARIO_COVERAGE_FAILED")),
|
|
2435
|
+
message=str(coverage.get("message", "scenario coverage validation failed")),
|
|
2436
|
+
))
|
|
2437
|
+
else:
|
|
2438
|
+
persist_close_failure(
|
|
2439
|
+
change_dir,
|
|
2440
|
+
args.phase,
|
|
2441
|
+
run_id,
|
|
2442
|
+
capsule,
|
|
2443
|
+
status="SCENARIO_VALIDATION_FAILED",
|
|
2444
|
+
error=coverage,
|
|
2445
|
+
)
|
|
2446
|
+
return emit_error(
|
|
2447
|
+
str(coverage.get("code", "SCENARIO_COVERAGE_FAILED")),
|
|
2448
|
+
str(coverage.get("message", "scenario coverage validation failed")),
|
|
2449
|
+
as_json=as_json,
|
|
2450
|
+
extra={k: v for k, v in coverage.items() if k not in {"ok", "message", "code"}},
|
|
2451
|
+
)
|
|
2311
2452
|
|
|
2312
2453
|
close_status = args.status
|
|
2313
2454
|
close_code = "PHASE_CLOSED"
|
|
@@ -2316,6 +2457,12 @@ def cmd_close(args: argparse.Namespace) -> int:
|
|
|
2316
2457
|
# Degraded close: phase.end status must not exceed WARN (OK → WARN).
|
|
2317
2458
|
if close_status == "OK":
|
|
2318
2459
|
close_status = "WARN"
|
|
2460
|
+
if gate_warnings:
|
|
2461
|
+
# Lenient-mode downgrades: phase closes, but never better than WARN.
|
|
2462
|
+
if close_status == "OK":
|
|
2463
|
+
close_status = "WARN"
|
|
2464
|
+
if close_code == "PHASE_CLOSED":
|
|
2465
|
+
close_code = "CLOSED_WITH_WARNINGS"
|
|
2319
2466
|
|
|
2320
2467
|
close_transaction: dict[str, Any] = {}
|
|
2321
2468
|
if capsule is not None:
|
|
@@ -2340,20 +2487,29 @@ def cmd_close(args: argparse.Namespace) -> int:
|
|
|
2340
2487
|
else:
|
|
2341
2488
|
guard_result = htg.close(execution_root, change_dir)
|
|
2342
2489
|
if not guard_result.get("ok"):
|
|
2343
|
-
if
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
"
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2490
|
+
if gate_soft_allowed(severity_mode, args.phase, "test-guard"):
|
|
2491
|
+
gate_warnings.append(record_gate_warning(
|
|
2492
|
+
change_dir,
|
|
2493
|
+
phase=args.phase,
|
|
2494
|
+
site="test-guard",
|
|
2495
|
+
code=str(guard_result.get("code", "TEST_GUARD_CLOSE_FAILED")),
|
|
2496
|
+
message="test guard close failed (downgraded to WARN)",
|
|
2497
|
+
))
|
|
2498
|
+
else:
|
|
2499
|
+
if capsule is not None:
|
|
2500
|
+
close_transaction.update({
|
|
2501
|
+
"status": "GUARD_CLOSE_FAILED",
|
|
2502
|
+
"lastError": guard_result,
|
|
2503
|
+
"updatedAt": he.now_iso(),
|
|
2504
|
+
})
|
|
2505
|
+
write_phase_capsule(change_dir, args.phase, run_id, capsule)
|
|
2506
|
+
return emit_error(
|
|
2507
|
+
str(guard_result.get("code", "TEST_GUARD_CLOSE_FAILED")),
|
|
2508
|
+
"test guard close failed",
|
|
2509
|
+
as_json=as_json,
|
|
2510
|
+
extra=guard_result,
|
|
2511
|
+
)
|
|
2512
|
+
if guard_result.get("ok") and capsule is not None:
|
|
2357
2513
|
close_transaction["guardClosed"] = True
|
|
2358
2514
|
close_transaction["updatedAt"] = he.now_iso()
|
|
2359
2515
|
write_phase_capsule(change_dir, args.phase, run_id, capsule)
|
|
@@ -2437,6 +2593,8 @@ def cmd_close(args: argparse.Namespace) -> int:
|
|
|
2437
2593
|
"testGuard": guard_result,
|
|
2438
2594
|
"event": event_result,
|
|
2439
2595
|
"lease": release,
|
|
2596
|
+
"gateSeverityMode": severity_mode,
|
|
2597
|
+
"gateWarnings": gate_warnings,
|
|
2440
2598
|
}
|
|
2441
2599
|
emit(payload, as_json=as_json)
|
|
2442
2600
|
return 0
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Shared headless JSON envelope helpers for stage scripts (P5 contract)."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
import sys
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def headless_envelope(
|
|
12
|
+
*,
|
|
13
|
+
command: str,
|
|
14
|
+
ok: bool,
|
|
15
|
+
exit_code: int,
|
|
16
|
+
change: str | None = None,
|
|
17
|
+
phase: str | None = None,
|
|
18
|
+
warnings: list[Any] | None = None,
|
|
19
|
+
errors: list[Any] | None = None,
|
|
20
|
+
result: dict[str, Any] | None = None,
|
|
21
|
+
) -> dict[str, Any]:
|
|
22
|
+
return {
|
|
23
|
+
"schema_version": 1,
|
|
24
|
+
"ok": ok,
|
|
25
|
+
"command": command,
|
|
26
|
+
"change": change,
|
|
27
|
+
"phase": phase,
|
|
28
|
+
"exit_code": exit_code,
|
|
29
|
+
"warnings": warnings or [],
|
|
30
|
+
"errors": errors or [],
|
|
31
|
+
"result": result or {},
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def emit_headless(payload: dict[str, Any], *, stream=None) -> None:
|
|
36
|
+
out = stream or sys.stdout
|
|
37
|
+
print(json.dumps(payload, ensure_ascii=False, indent=2), file=out)
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"schema_version": 2,
|
|
3
3
|
"profile": "general",
|
|
4
4
|
"adapter": "claude-code",
|
|
5
|
-
"bundle_version": "0.2.
|
|
5
|
+
"bundle_version": "0.2.37",
|
|
6
6
|
"requires": {
|
|
7
|
-
"minimumCliVersion": "0.2.
|
|
7
|
+
"minimumCliVersion": "0.2.50",
|
|
8
8
|
"capabilities": [
|
|
9
9
|
"sync@2",
|
|
10
10
|
"rules-sync@1",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"files": [
|
|
24
24
|
{
|
|
25
25
|
"path": ".harness-build.json",
|
|
26
|
-
"sha256": "
|
|
26
|
+
"sha256": "eadad2ab2439a1213cdd76b7acb9c16fb7a15da6d51c1829bb2288eb830eed2a"
|
|
27
27
|
},
|
|
28
28
|
{
|
|
29
29
|
"path": "agents/harness-evaluator.md",
|
|
@@ -95,7 +95,7 @@
|
|
|
95
95
|
},
|
|
96
96
|
{
|
|
97
97
|
"path": "harness-archive/SKILL.md",
|
|
98
|
-
"sha256": "
|
|
98
|
+
"sha256": "55bef68b5a39d074df92590e5e1fd8d0ce24f0abbba6438de7561c691d59eb5e"
|
|
99
99
|
},
|
|
100
100
|
{
|
|
101
101
|
"path": "harness-archive/templates/render-summary.mjs",
|
|
@@ -115,7 +115,7 @@
|
|
|
115
115
|
},
|
|
116
116
|
{
|
|
117
117
|
"path": "harness-codebase-map/SKILL.md",
|
|
118
|
-
"sha256": "
|
|
118
|
+
"sha256": "666e18d829b6e54e878737950470002cc9c1fb4f0292cb1af9da13ab21cb488e"
|
|
119
119
|
},
|
|
120
120
|
{
|
|
121
121
|
"path": "harness-codebase-map/templates/ARCHITECTURE.md",
|
|
@@ -175,15 +175,15 @@
|
|
|
175
175
|
},
|
|
176
176
|
{
|
|
177
177
|
"path": "harness-knowledge-ingest/scripts/harness_knowledge.py",
|
|
178
|
-
"sha256": "
|
|
178
|
+
"sha256": "e12d01a9695ccc6624a4a7690f831622d18866fc0a6c383c640dc7345394d047"
|
|
179
179
|
},
|
|
180
180
|
{
|
|
181
181
|
"path": "harness-knowledge-ingest/SKILL.md",
|
|
182
|
-
"sha256": "
|
|
182
|
+
"sha256": "97173b627a90afafc3c77c0e1535194b42e7ff8453ed98a42fffe3fa12cbb8d4"
|
|
183
183
|
},
|
|
184
184
|
{
|
|
185
185
|
"path": "harness-knowledge-query/SKILL.md",
|
|
186
|
-
"sha256": "
|
|
186
|
+
"sha256": "3a328158896c205980132d69ba5db420977db9639d2d8a94c44dec46e48654bb"
|
|
187
187
|
},
|
|
188
188
|
{
|
|
189
189
|
"path": "harness-plan/checklist.md",
|
|
@@ -199,7 +199,7 @@
|
|
|
199
199
|
},
|
|
200
200
|
{
|
|
201
201
|
"path": "harness-plan/SKILL.md",
|
|
202
|
-
"sha256": "
|
|
202
|
+
"sha256": "ca4118e2ef6a1d952ec08c1fbdc099bcb886c75f078d2c61318790bf126e991b"
|
|
203
203
|
},
|
|
204
204
|
{
|
|
205
205
|
"path": "harness-review/checklist.md",
|
|
@@ -215,7 +215,7 @@
|
|
|
215
215
|
},
|
|
216
216
|
{
|
|
217
217
|
"path": "harness-review/SKILL.md",
|
|
218
|
-
"sha256": "
|
|
218
|
+
"sha256": "ab978e9d4a9e90043628a75e3b47c3668b028e1b278bdcc7c8e977fa7eefe19d"
|
|
219
219
|
},
|
|
220
220
|
{
|
|
221
221
|
"path": "harness-run/checklist.md",
|
|
@@ -231,7 +231,7 @@
|
|
|
231
231
|
},
|
|
232
232
|
{
|
|
233
233
|
"path": "harness-run/SKILL.md",
|
|
234
|
-
"sha256": "
|
|
234
|
+
"sha256": "8790202ebeeea57c40236353c03b68f476de4b092a1e519cb01fce1d56bef19f"
|
|
235
235
|
},
|
|
236
236
|
{
|
|
237
237
|
"path": "harness-submit/checklist.md",
|
|
@@ -243,7 +243,7 @@
|
|
|
243
243
|
},
|
|
244
244
|
{
|
|
245
245
|
"path": "harness-submit/SKILL.md",
|
|
246
|
-
"sha256": "
|
|
246
|
+
"sha256": "8f2fe6de526027d2af9a7261626c912226c2c42a7a2c05404c51ea4a2c8b1f5b"
|
|
247
247
|
},
|
|
248
248
|
{
|
|
249
249
|
"path": "harness-sync/reference.md",
|
|
@@ -251,7 +251,7 @@
|
|
|
251
251
|
},
|
|
252
252
|
{
|
|
253
253
|
"path": "harness-sync/SKILL.md",
|
|
254
|
-
"sha256": "
|
|
254
|
+
"sha256": "76963989cbf601dcd8b8bec19033b097f79e30bb2c5b348fc3fa9cd20bc400bc"
|
|
255
255
|
},
|
|
256
256
|
{
|
|
257
257
|
"path": "harness-test/checklist.md",
|
|
@@ -275,7 +275,7 @@
|
|
|
275
275
|
},
|
|
276
276
|
{
|
|
277
277
|
"path": "harness-test/SKILL.md",
|
|
278
|
-
"sha256": "
|
|
278
|
+
"sha256": "0337985e4716861ad656194eb16048a18814414ec1563aa1177882c589144398"
|
|
279
279
|
},
|
|
280
280
|
{
|
|
281
281
|
"path": "protocols/archive-report-protocol.md",
|
|
@@ -339,7 +339,7 @@
|
|
|
339
339
|
},
|
|
340
340
|
{
|
|
341
341
|
"path": "README.md",
|
|
342
|
-
"sha256": "
|
|
342
|
+
"sha256": "74a3ee92b7c7cc015e000143e53a0746a66bb6adcc6d2b6a648edc9c41cca32b"
|
|
343
343
|
},
|
|
344
344
|
{
|
|
345
345
|
"path": "scripts/harness_apidoc.py",
|
|
@@ -347,7 +347,7 @@
|
|
|
347
347
|
},
|
|
348
348
|
{
|
|
349
349
|
"path": "scripts/harness_archive.py",
|
|
350
|
-
"sha256": "
|
|
350
|
+
"sha256": "5ca2338060125a5cc2df55d53864be2122e7f573b4c35a4f14c5803a232b1ec7"
|
|
351
351
|
},
|
|
352
352
|
{
|
|
353
353
|
"path": "scripts/harness_change.py",
|
|
@@ -373,9 +373,13 @@
|
|
|
373
373
|
"path": "scripts/harness_environment.py",
|
|
374
374
|
"sha256": "dcbc6d17de9f07bea1d85e7cc2c2a96257f5b2e1f4aa08577815ad7b0ef548ff"
|
|
375
375
|
},
|
|
376
|
+
{
|
|
377
|
+
"path": "scripts/harness_events_sync.py",
|
|
378
|
+
"sha256": "1097e6fccc1b81a54db2ccacdd8f8db835b5763dbf5c472873b82eb718e33f5f"
|
|
379
|
+
},
|
|
376
380
|
{
|
|
377
381
|
"path": "scripts/harness_events.py",
|
|
378
|
-
"sha256": "
|
|
382
|
+
"sha256": "922240bc7ede9c17a6e99bbe9a04177363cc5101fca23e221523d3ddc3c27d3f"
|
|
379
383
|
},
|
|
380
384
|
{
|
|
381
385
|
"path": "scripts/harness_execution_contracts.py",
|
|
@@ -387,7 +391,11 @@
|
|
|
387
391
|
},
|
|
388
392
|
{
|
|
389
393
|
"path": "scripts/harness_gate.py",
|
|
390
|
-
"sha256": "
|
|
394
|
+
"sha256": "69588cf964a12e4819f4539090e39a2eb73666c9289fc1cf55f3846da4dc353c"
|
|
395
|
+
},
|
|
396
|
+
{
|
|
397
|
+
"path": "scripts/harness_headless.py",
|
|
398
|
+
"sha256": "1015d375244086edd501b3312c23c827cca3d72e3640676fd6f8b940f2649bf9"
|
|
391
399
|
},
|
|
392
400
|
{
|
|
393
401
|
"path": "scripts/harness_integration.py",
|