@hunter-harness/workflow-harness 0.2.55 → 0.2.56
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/README.md +3 -3
- package/harness/bundles/general/claude-code/contracts/workflow-policy.json +1 -1
- package/harness/bundles/general/claude-code/harness-archive/SKILL.md +21 -33
- package/harness/bundles/general/claude-code/harness-archive/checklist.md +6 -10
- 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 +7 -3
- package/harness/bundles/general/claude-code/harness-review/SKILL.md +6 -4
- package/harness/bundles/general/claude-code/harness-run/SKILL.md +7 -5
- package/harness/bundles/general/claude-code/harness-run/checklist.md +1 -1
- package/harness/bundles/general/claude-code/harness-run/reference.md +2 -2
- package/harness/bundles/general/claude-code/harness-submit/SKILL.md +6 -6
- package/harness/bundles/general/claude-code/harness-submit/checklist.md +1 -1
- package/harness/bundles/general/claude-code/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-test/SKILL.md +6 -6
- package/harness/bundles/general/claude-code/harness-test/checklist.md +1 -1
- package/harness/bundles/general/claude-code/harness-test/reference.md +2 -2
- package/harness/bundles/general/claude-code/protocols/ledger-protocol.md +3 -3
- package/harness/bundles/general/claude-code/scripts/harness_archive.py +276 -67
- package/harness/bundles/general/claude-code/scripts/harness_context.py +165 -41
- package/harness/bundles/general/claude-code/scripts/harness_events.py +46 -4
- package/harness/bundles/general/claude-code/scripts/harness_events_sync.py +108 -3
- package/harness/bundles/general/claude-code/scripts/harness_fixback.py +189 -1
- package/harness/bundles/general/claude-code/scripts/harness_gate.py +188 -2
- package/harness/bundles/general/claude-code/scripts/harness_ledger.py +193 -11
- package/harness/bundles/general/claude-code/scripts/harness_plan_finalize.py +80 -0
- package/harness/bundles/general/codebuddy/.harness-build.json +1 -1
- package/harness/bundles/general/codebuddy/README.md +3 -3
- package/harness/bundles/general/codebuddy/contracts/workflow-policy.json +1 -1
- package/harness/bundles/general/codebuddy/harness-archive/SKILL.md +21 -33
- package/harness/bundles/general/codebuddy/harness-archive/checklist.md +6 -10
- 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 +7 -3
- package/harness/bundles/general/codebuddy/harness-review/SKILL.md +6 -4
- package/harness/bundles/general/codebuddy/harness-run/SKILL.md +7 -5
- package/harness/bundles/general/codebuddy/harness-run/checklist.md +1 -1
- package/harness/bundles/general/codebuddy/harness-run/reference.md +2 -2
- package/harness/bundles/general/codebuddy/harness-submit/SKILL.md +6 -6
- package/harness/bundles/general/codebuddy/harness-submit/checklist.md +1 -1
- package/harness/bundles/general/codebuddy/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-test/SKILL.md +6 -6
- package/harness/bundles/general/codebuddy/harness-test/checklist.md +1 -1
- package/harness/bundles/general/codebuddy/harness-test/reference.md +2 -2
- package/harness/bundles/general/codebuddy/protocols/ledger-protocol.md +3 -3
- package/harness/bundles/general/codebuddy/scripts/harness_archive.py +276 -67
- package/harness/bundles/general/codebuddy/scripts/harness_context.py +165 -41
- package/harness/bundles/general/codebuddy/scripts/harness_events.py +46 -4
- package/harness/bundles/general/codebuddy/scripts/harness_events_sync.py +108 -3
- package/harness/bundles/general/codebuddy/scripts/harness_fixback.py +189 -1
- package/harness/bundles/general/codebuddy/scripts/harness_gate.py +188 -2
- package/harness/bundles/general/codebuddy/scripts/harness_ledger.py +193 -11
- package/harness/bundles/general/codebuddy/scripts/harness_plan_finalize.py +80 -0
- package/harness/bundles/general/codex/.harness-build.json +1 -1
- package/harness/bundles/general/codex/README.md +3 -3
- package/harness/bundles/general/codex/contracts/workflow-policy.json +1 -1
- package/harness/bundles/general/codex/harness-archive/SKILL.md +21 -33
- package/harness/bundles/general/codex/harness-archive/checklist.md +6 -10
- 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 +7 -3
- package/harness/bundles/general/codex/harness-review/SKILL.md +4 -4
- package/harness/bundles/general/codex/harness-run/SKILL.md +7 -5
- package/harness/bundles/general/codex/harness-run/checklist.md +1 -1
- package/harness/bundles/general/codex/harness-run/reference.md +2 -2
- package/harness/bundles/general/codex/harness-submit/SKILL.md +6 -6
- package/harness/bundles/general/codex/harness-submit/checklist.md +1 -1
- package/harness/bundles/general/codex/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-test/SKILL.md +6 -6
- package/harness/bundles/general/codex/harness-test/checklist.md +1 -1
- package/harness/bundles/general/codex/harness-test/reference.md +2 -2
- package/harness/bundles/general/codex/protocols/ledger-protocol.md +3 -3
- package/harness/bundles/general/codex/scripts/harness_archive.py +276 -67
- package/harness/bundles/general/codex/scripts/harness_context.py +165 -41
- package/harness/bundles/general/codex/scripts/harness_events.py +46 -4
- package/harness/bundles/general/codex/scripts/harness_events_sync.py +108 -3
- package/harness/bundles/general/codex/scripts/harness_fixback.py +189 -1
- package/harness/bundles/general/codex/scripts/harness_gate.py +188 -2
- package/harness/bundles/general/codex/scripts/harness_ledger.py +193 -11
- package/harness/bundles/general/codex/scripts/harness_plan_finalize.py +80 -0
- package/harness/bundles/general/cursor/.harness-build.json +1 -1
- package/harness/bundles/general/cursor/README.md +3 -3
- package/harness/bundles/general/cursor/contracts/workflow-policy.json +1 -1
- package/harness/bundles/general/cursor/harness-archive/SKILL.md +21 -33
- package/harness/bundles/general/cursor/harness-archive/checklist.md +6 -10
- 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 +7 -3
- package/harness/bundles/general/cursor/harness-review/SKILL.md +4 -4
- package/harness/bundles/general/cursor/harness-run/SKILL.md +7 -5
- package/harness/bundles/general/cursor/harness-run/checklist.md +1 -1
- package/harness/bundles/general/cursor/harness-run/reference.md +2 -2
- package/harness/bundles/general/cursor/harness-submit/SKILL.md +6 -6
- package/harness/bundles/general/cursor/harness-submit/checklist.md +1 -1
- package/harness/bundles/general/cursor/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-test/SKILL.md +6 -6
- package/harness/bundles/general/cursor/harness-test/checklist.md +1 -1
- package/harness/bundles/general/cursor/harness-test/reference.md +2 -2
- package/harness/bundles/general/cursor/protocols/ledger-protocol.md +3 -3
- package/harness/bundles/general/cursor/scripts/harness_archive.py +276 -67
- package/harness/bundles/general/cursor/scripts/harness_context.py +165 -41
- package/harness/bundles/general/cursor/scripts/harness_events.py +46 -4
- package/harness/bundles/general/cursor/scripts/harness_events_sync.py +108 -3
- package/harness/bundles/general/cursor/scripts/harness_fixback.py +189 -1
- package/harness/bundles/general/cursor/scripts/harness_gate.py +188 -2
- package/harness/bundles/general/cursor/scripts/harness_ledger.py +193 -11
- package/harness/bundles/general/cursor/scripts/harness_plan_finalize.py +80 -0
- package/harness/bundles/java/claude-code/.harness-build.json +1 -1
- package/harness/bundles/java/claude-code/README.md +3 -3
- package/harness/bundles/java/claude-code/contracts/workflow-policy.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 +21 -33
- package/harness/bundles/java/claude-code/harness-archive/checklist.md +6 -10
- 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-package/checklist.md +1 -1
- package/harness/bundles/java/claude-code/harness-plan/SKILL.md +7 -3
- package/harness/bundles/java/claude-code/harness-review/SKILL.md +6 -4
- package/harness/bundles/java/claude-code/harness-run/SKILL.md +7 -5
- package/harness/bundles/java/claude-code/harness-run/checklist.md +1 -1
- package/harness/bundles/java/claude-code/harness-run/reference.md +2 -2
- package/harness/bundles/java/claude-code/harness-submit/SKILL.md +6 -6
- package/harness/bundles/java/claude-code/harness-submit/checklist.md +1 -1
- package/harness/bundles/java/claude-code/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-test/SKILL.md +6 -6
- package/harness/bundles/java/claude-code/harness-test/checklist.md +1 -1
- package/harness/bundles/java/claude-code/harness-test/reference.md +1 -1
- package/harness/bundles/java/claude-code/protocols/ledger-protocol.md +3 -3
- package/harness/bundles/java/claude-code/scripts/harness_archive.py +276 -67
- package/harness/bundles/java/claude-code/scripts/harness_context.py +165 -41
- package/harness/bundles/java/claude-code/scripts/harness_events.py +46 -4
- package/harness/bundles/java/claude-code/scripts/harness_events_sync.py +108 -3
- package/harness/bundles/java/claude-code/scripts/harness_fixback.py +189 -1
- package/harness/bundles/java/claude-code/scripts/harness_gate.py +188 -2
- package/harness/bundles/java/claude-code/scripts/harness_ledger.py +193 -11
- package/harness/bundles/java/claude-code/scripts/harness_plan_finalize.py +80 -0
- package/harness/bundles/java/codebuddy/.harness-build.json +1 -1
- package/harness/bundles/java/codebuddy/README.md +3 -3
- package/harness/bundles/java/codebuddy/contracts/workflow-policy.json +1 -1
- package/harness/bundles/java/codebuddy/harness-apidoc/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-archive/SKILL.md +21 -33
- package/harness/bundles/java/codebuddy/harness-archive/checklist.md +6 -10
- 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-package/checklist.md +1 -1
- package/harness/bundles/java/codebuddy/harness-plan/SKILL.md +7 -3
- package/harness/bundles/java/codebuddy/harness-review/SKILL.md +6 -4
- package/harness/bundles/java/codebuddy/harness-run/SKILL.md +7 -5
- package/harness/bundles/java/codebuddy/harness-run/checklist.md +1 -1
- package/harness/bundles/java/codebuddy/harness-run/reference.md +2 -2
- package/harness/bundles/java/codebuddy/harness-submit/SKILL.md +6 -6
- package/harness/bundles/java/codebuddy/harness-submit/checklist.md +1 -1
- package/harness/bundles/java/codebuddy/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-test/SKILL.md +6 -6
- package/harness/bundles/java/codebuddy/harness-test/checklist.md +1 -1
- package/harness/bundles/java/codebuddy/harness-test/reference.md +1 -1
- package/harness/bundles/java/codebuddy/protocols/ledger-protocol.md +3 -3
- package/harness/bundles/java/codebuddy/scripts/harness_archive.py +276 -67
- package/harness/bundles/java/codebuddy/scripts/harness_context.py +165 -41
- package/harness/bundles/java/codebuddy/scripts/harness_events.py +46 -4
- package/harness/bundles/java/codebuddy/scripts/harness_events_sync.py +108 -3
- package/harness/bundles/java/codebuddy/scripts/harness_fixback.py +189 -1
- package/harness/bundles/java/codebuddy/scripts/harness_gate.py +188 -2
- package/harness/bundles/java/codebuddy/scripts/harness_ledger.py +193 -11
- package/harness/bundles/java/codebuddy/scripts/harness_plan_finalize.py +80 -0
- package/harness/bundles/java/codex/.harness-build.json +1 -1
- package/harness/bundles/java/codex/README.md +3 -3
- package/harness/bundles/java/codex/contracts/workflow-policy.json +1 -1
- package/harness/bundles/java/codex/harness-apidoc/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-archive/SKILL.md +21 -33
- package/harness/bundles/java/codex/harness-archive/checklist.md +6 -10
- 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-package/checklist.md +1 -1
- package/harness/bundles/java/codex/harness-plan/SKILL.md +7 -3
- package/harness/bundles/java/codex/harness-review/SKILL.md +4 -4
- package/harness/bundles/java/codex/harness-run/SKILL.md +7 -5
- package/harness/bundles/java/codex/harness-run/checklist.md +1 -1
- package/harness/bundles/java/codex/harness-run/reference.md +2 -2
- package/harness/bundles/java/codex/harness-submit/SKILL.md +6 -6
- package/harness/bundles/java/codex/harness-submit/checklist.md +1 -1
- package/harness/bundles/java/codex/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-test/SKILL.md +6 -6
- package/harness/bundles/java/codex/harness-test/checklist.md +1 -1
- package/harness/bundles/java/codex/harness-test/reference.md +1 -1
- package/harness/bundles/java/codex/protocols/ledger-protocol.md +3 -3
- package/harness/bundles/java/codex/scripts/harness_archive.py +276 -67
- package/harness/bundles/java/codex/scripts/harness_context.py +165 -41
- package/harness/bundles/java/codex/scripts/harness_events.py +46 -4
- package/harness/bundles/java/codex/scripts/harness_events_sync.py +108 -3
- package/harness/bundles/java/codex/scripts/harness_fixback.py +189 -1
- package/harness/bundles/java/codex/scripts/harness_gate.py +188 -2
- package/harness/bundles/java/codex/scripts/harness_ledger.py +193 -11
- package/harness/bundles/java/codex/scripts/harness_plan_finalize.py +80 -0
- package/harness/bundles/java/cursor/.harness-build.json +1 -1
- package/harness/bundles/java/cursor/README.md +3 -3
- package/harness/bundles/java/cursor/contracts/workflow-policy.json +1 -1
- package/harness/bundles/java/cursor/harness-apidoc/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-archive/SKILL.md +21 -33
- package/harness/bundles/java/cursor/harness-archive/checklist.md +6 -10
- 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-package/checklist.md +1 -1
- package/harness/bundles/java/cursor/harness-plan/SKILL.md +7 -3
- package/harness/bundles/java/cursor/harness-review/SKILL.md +4 -4
- package/harness/bundles/java/cursor/harness-run/SKILL.md +7 -5
- package/harness/bundles/java/cursor/harness-run/checklist.md +1 -1
- package/harness/bundles/java/cursor/harness-run/reference.md +2 -2
- package/harness/bundles/java/cursor/harness-submit/SKILL.md +6 -6
- package/harness/bundles/java/cursor/harness-submit/checklist.md +1 -1
- package/harness/bundles/java/cursor/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-test/SKILL.md +6 -6
- package/harness/bundles/java/cursor/harness-test/checklist.md +1 -1
- package/harness/bundles/java/cursor/harness-test/reference.md +1 -1
- package/harness/bundles/java/cursor/protocols/ledger-protocol.md +3 -3
- package/harness/bundles/java/cursor/scripts/harness_archive.py +276 -67
- package/harness/bundles/java/cursor/scripts/harness_context.py +165 -41
- package/harness/bundles/java/cursor/scripts/harness_events.py +46 -4
- package/harness/bundles/java/cursor/scripts/harness_events_sync.py +108 -3
- package/harness/bundles/java/cursor/scripts/harness_fixback.py +189 -1
- package/harness/bundles/java/cursor/scripts/harness_gate.py +188 -2
- package/harness/bundles/java/cursor/scripts/harness_ledger.py +193 -11
- package/harness/bundles/java/cursor/scripts/harness_plan_finalize.py +80 -0
- package/harness/manifests/general/claude-code.json +30 -30
- package/harness/manifests/general/codebuddy.json +30 -30
- package/harness/manifests/general/codex.json +30 -30
- package/harness/manifests/general/cursor.json +30 -30
- package/harness/manifests/java/claude-code.json +33 -33
- package/harness/manifests/java/codebuddy.json +33 -33
- package/harness/manifests/java/codex.json +33 -33
- package/harness/manifests/java/cursor.json +33 -33
- package/hunter-workflow-family.json +5 -5
- package/package.json +1 -1
|
@@ -11,6 +11,7 @@ import os
|
|
|
11
11
|
import re
|
|
12
12
|
import tempfile
|
|
13
13
|
import sys
|
|
14
|
+
import uuid
|
|
14
15
|
from pathlib import Path
|
|
15
16
|
from typing import Any
|
|
16
17
|
|
|
@@ -19,6 +20,7 @@ if str(SCRIPTS_DIR) not in sys.path:
|
|
|
19
20
|
sys.path.insert(0, str(SCRIPTS_DIR))
|
|
20
21
|
|
|
21
22
|
from harness_paths import resolve_state_dir_for_contract
|
|
23
|
+
import harness_events as he
|
|
22
24
|
|
|
23
25
|
|
|
24
26
|
SCHEMA_VERSION = 1
|
|
@@ -96,6 +98,171 @@ def _all_batches(change_dir: Path) -> list[dict[str, Any]]:
|
|
|
96
98
|
return batches
|
|
97
99
|
|
|
98
100
|
|
|
101
|
+
def _session_path(change_dir: Path) -> Path:
|
|
102
|
+
return _state_root(change_dir) / "runtime" / "fixback-session.json"
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _next_step(batch: dict[str, Any]) -> str:
|
|
106
|
+
issues = [item for item in batch.get("issues", []) if isinstance(item, dict)]
|
|
107
|
+
if not issues:
|
|
108
|
+
return "add-issues"
|
|
109
|
+
if any(item.get("status") != "RESOLVED" for item in issues):
|
|
110
|
+
return "resolve-issues"
|
|
111
|
+
receipts = batch.get("receipts") if isinstance(batch.get("receipts"), dict) else {}
|
|
112
|
+
if not receipts.get("affected"):
|
|
113
|
+
return "verify-affected"
|
|
114
|
+
if not receipts.get("review"):
|
|
115
|
+
return "review"
|
|
116
|
+
return "close"
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def resume_batch(
|
|
120
|
+
change_dir: Path,
|
|
121
|
+
*,
|
|
122
|
+
batch_id: str | None,
|
|
123
|
+
product_identity: str | None,
|
|
124
|
+
root_cause: str | None,
|
|
125
|
+
run_id: str | None,
|
|
126
|
+
attempt: int | None,
|
|
127
|
+
) -> dict[str, Any]:
|
|
128
|
+
"""Open once or resume the active Fixback session with a stable run identity."""
|
|
129
|
+
opened = [item for item in _all_batches(change_dir) if item.get("status") == "OPEN"]
|
|
130
|
+
resumed = bool(opened)
|
|
131
|
+
if opened:
|
|
132
|
+
batch = sorted(opened, key=lambda item: str(item.get("openedAt") or ""))[0]
|
|
133
|
+
else:
|
|
134
|
+
if not batch_id or not product_identity or not root_cause:
|
|
135
|
+
raise ValueError(
|
|
136
|
+
"FIXBACK_RESUME_INPUT_REQUIRED: new sessions require batch-id, "
|
|
137
|
+
"product-identity and root-cause"
|
|
138
|
+
)
|
|
139
|
+
batch = open_batch(
|
|
140
|
+
change_dir,
|
|
141
|
+
batch_id=batch_id,
|
|
142
|
+
product_identity=product_identity,
|
|
143
|
+
root_cause=root_cause,
|
|
144
|
+
)
|
|
145
|
+
session_path = _session_path(change_dir)
|
|
146
|
+
existing: dict[str, Any] = {}
|
|
147
|
+
if session_path.is_file():
|
|
148
|
+
try:
|
|
149
|
+
loaded = json.loads(session_path.read_text(encoding="utf-8-sig"))
|
|
150
|
+
existing = loaded if isinstance(loaded, dict) else {}
|
|
151
|
+
except (OSError, json.JSONDecodeError):
|
|
152
|
+
existing = {}
|
|
153
|
+
same_open_session = (
|
|
154
|
+
existing.get("status") == "ACTIVE"
|
|
155
|
+
and existing.get("batchId") == batch.get("batchId")
|
|
156
|
+
)
|
|
157
|
+
effective_run_id = (
|
|
158
|
+
str(existing.get("runId"))
|
|
159
|
+
if same_open_session and existing.get("runId")
|
|
160
|
+
else str(run_id or "fixback-" + uuid.uuid4().hex)
|
|
161
|
+
)
|
|
162
|
+
effective_attempt = (
|
|
163
|
+
int(existing.get("attempt"))
|
|
164
|
+
if same_open_session and isinstance(existing.get("attempt"), int)
|
|
165
|
+
else max(1, int(attempt or 1))
|
|
166
|
+
)
|
|
167
|
+
next_step = _next_step(batch)
|
|
168
|
+
session = {
|
|
169
|
+
"schemaVersion": 1,
|
|
170
|
+
"status": "ACTIVE",
|
|
171
|
+
"batchId": batch.get("batchId"),
|
|
172
|
+
"runId": effective_run_id,
|
|
173
|
+
"attempt": effective_attempt,
|
|
174
|
+
"nextStep": next_step,
|
|
175
|
+
"updatedAt": now_iso(),
|
|
176
|
+
}
|
|
177
|
+
_write_json(session_path, session)
|
|
178
|
+
he.append_event(
|
|
179
|
+
change_dir,
|
|
180
|
+
phase="run",
|
|
181
|
+
type_="decision",
|
|
182
|
+
note=(
|
|
183
|
+
"已恢复现有修复批次,将从未完成步骤继续。"
|
|
184
|
+
if resumed
|
|
185
|
+
else "已创建修复批次,将按问题逐项执行 RED/GREEN。"
|
|
186
|
+
),
|
|
187
|
+
run_id=effective_run_id,
|
|
188
|
+
)
|
|
189
|
+
return {
|
|
190
|
+
"ok": True,
|
|
191
|
+
"code": "FIXBACK_RESUMED" if resumed else "FIXBACK_OPENED",
|
|
192
|
+
"resumed": resumed,
|
|
193
|
+
"batchId": batch.get("batchId"),
|
|
194
|
+
"runId": effective_run_id,
|
|
195
|
+
"attempt": effective_attempt,
|
|
196
|
+
"nextStep": next_step,
|
|
197
|
+
"requiredVerifications": batch.get("requiredVerifications", []),
|
|
198
|
+
"changedFiles": batch.get("changedFiles", []),
|
|
199
|
+
"sessionPath": str(session_path),
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def invalidate_affected_evidence(
|
|
204
|
+
change_dir: Path,
|
|
205
|
+
*,
|
|
206
|
+
changed_files: list[str],
|
|
207
|
+
batch_id: str,
|
|
208
|
+
) -> dict[str, Any]:
|
|
209
|
+
"""Invalidate only verification targets whose declared inputs were changed."""
|
|
210
|
+
normalized = {
|
|
211
|
+
str(path).replace("\\", "/").lstrip("./")
|
|
212
|
+
for path in changed_files
|
|
213
|
+
if str(path).strip()
|
|
214
|
+
}
|
|
215
|
+
ledger_path = _state_root(change_dir) / "evidence" / "verification-ledger.json"
|
|
216
|
+
if not normalized or not ledger_path.is_file():
|
|
217
|
+
return {"ok": True, "targetIds": [], "validations": []}
|
|
218
|
+
try:
|
|
219
|
+
ledger = json.loads(ledger_path.read_text(encoding="utf-8-sig"))
|
|
220
|
+
except (OSError, json.JSONDecodeError):
|
|
221
|
+
return {"ok": False, "code": "FIXBACK_LEDGER_INVALID"}
|
|
222
|
+
if not isinstance(ledger, dict):
|
|
223
|
+
return {"ok": False, "code": "FIXBACK_LEDGER_INVALID"}
|
|
224
|
+
|
|
225
|
+
def affected(entry: dict[str, Any]) -> bool:
|
|
226
|
+
inputs = entry.get("inputsFiles")
|
|
227
|
+
if not isinstance(inputs, list):
|
|
228
|
+
return False
|
|
229
|
+
input_set = {
|
|
230
|
+
str(path).replace("\\", "/").lstrip("./")
|
|
231
|
+
for path in inputs
|
|
232
|
+
if str(path).strip()
|
|
233
|
+
}
|
|
234
|
+
return bool(normalized & input_set)
|
|
235
|
+
|
|
236
|
+
invalidation = {
|
|
237
|
+
"code": "FIXBACK_AFFECTED_INPUT_CHANGED",
|
|
238
|
+
"batchId": batch_id,
|
|
239
|
+
"changedFiles": sorted(normalized),
|
|
240
|
+
"invalidatedAt": now_iso(),
|
|
241
|
+
}
|
|
242
|
+
target_ids: list[str] = []
|
|
243
|
+
targets = ledger.get("verificationTargets")
|
|
244
|
+
if isinstance(targets, dict):
|
|
245
|
+
for target_id, target in targets.items():
|
|
246
|
+
if isinstance(target, dict) and affected(target):
|
|
247
|
+
target["reusable"] = False
|
|
248
|
+
target["invalidation"] = dict(invalidation)
|
|
249
|
+
target_ids.append(str(target_id))
|
|
250
|
+
validation_names: list[str] = []
|
|
251
|
+
validations = ledger.get("validations")
|
|
252
|
+
if isinstance(validations, dict):
|
|
253
|
+
for name, entry in validations.items():
|
|
254
|
+
if isinstance(entry, dict) and affected(entry):
|
|
255
|
+
entry["reusable"] = False
|
|
256
|
+
entry["invalidation"] = dict(invalidation)
|
|
257
|
+
validation_names.append(str(name))
|
|
258
|
+
_write_json(ledger_path, ledger)
|
|
259
|
+
return {
|
|
260
|
+
"ok": True,
|
|
261
|
+
"targetIds": sorted(target_ids),
|
|
262
|
+
"validations": sorted(validation_names),
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
|
|
99
266
|
def _required_verifications(issues: list[dict[str, Any]]) -> list[str]:
|
|
100
267
|
required = {"affected", "review"}
|
|
101
268
|
for issue in issues:
|
|
@@ -506,6 +673,11 @@ def resolve_issue(
|
|
|
506
673
|
}
|
|
507
674
|
)
|
|
508
675
|
batch["updatedAt"] = resolved_at
|
|
676
|
+
batch["invalidation"] = invalidate_affected_evidence(
|
|
677
|
+
change_dir,
|
|
678
|
+
changed_files=[record["path"] for record in changed_records],
|
|
679
|
+
batch_id=batch_id,
|
|
680
|
+
)
|
|
509
681
|
_write_json(_batch_path(change_dir, batch_id), batch)
|
|
510
682
|
return issue
|
|
511
683
|
|
|
@@ -652,6 +824,13 @@ def _emit(value: dict[str, Any]) -> int:
|
|
|
652
824
|
def build_parser() -> argparse.ArgumentParser:
|
|
653
825
|
parser = argparse.ArgumentParser(prog="harness_fixback.py")
|
|
654
826
|
sub = parser.add_subparsers(dest="command", required=True)
|
|
827
|
+
resume = sub.add_parser("resume")
|
|
828
|
+
resume.add_argument("--change-dir", required=True)
|
|
829
|
+
resume.add_argument("--batch-id")
|
|
830
|
+
resume.add_argument("--product-identity")
|
|
831
|
+
resume.add_argument("--root-cause")
|
|
832
|
+
resume.add_argument("--run-id")
|
|
833
|
+
resume.add_argument("--attempt", type=int)
|
|
655
834
|
opened = sub.add_parser("open")
|
|
656
835
|
opened.add_argument("--change-dir", required=True)
|
|
657
836
|
opened.add_argument("--batch-id", required=True)
|
|
@@ -692,7 +871,16 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
692
871
|
args = build_parser().parse_args(argv)
|
|
693
872
|
change_dir = Path(args.change_dir)
|
|
694
873
|
try:
|
|
695
|
-
if args.command == "
|
|
874
|
+
if args.command == "resume":
|
|
875
|
+
value = resume_batch(
|
|
876
|
+
change_dir,
|
|
877
|
+
batch_id=args.batch_id,
|
|
878
|
+
product_identity=args.product_identity,
|
|
879
|
+
root_cause=args.root_cause,
|
|
880
|
+
run_id=args.run_id,
|
|
881
|
+
attempt=args.attempt,
|
|
882
|
+
)
|
|
883
|
+
elif args.command == "open":
|
|
696
884
|
value = open_batch(
|
|
697
885
|
change_dir,
|
|
698
886
|
batch_id=args.batch_id,
|
|
@@ -693,11 +693,12 @@ def validate_ledger_for_phase_close(
|
|
|
693
693
|
try:
|
|
694
694
|
current_repository = hp.repository_identity(root)
|
|
695
695
|
current_ownership = hl.ownership_hash(contract)
|
|
696
|
-
|
|
696
|
+
current_diff_detail = hl.compute_ownership_diff(
|
|
697
697
|
root,
|
|
698
698
|
base=str(ledger["baseCommit"]),
|
|
699
699
|
change_dir=change_dir,
|
|
700
|
-
)
|
|
700
|
+
)
|
|
701
|
+
current_diff = current_diff_detail["diffHash"]
|
|
701
702
|
current_head = _git_text(root, "rev-parse", "--verify", "HEAD")
|
|
702
703
|
except (OSError, ValueError, RuntimeError) as exc:
|
|
703
704
|
return {
|
|
@@ -727,6 +728,18 @@ def validate_ledger_for_phase_close(
|
|
|
727
728
|
"storedHead": ledger.get("currentHead"),
|
|
728
729
|
"currentHead": current_head,
|
|
729
730
|
"ledgerPath": str(ledger_path) if ledger_path else None,
|
|
731
|
+
"pathSummary": {
|
|
732
|
+
"ownedFiles": list(current_diff_detail.get("files") or [])[:20],
|
|
733
|
+
"foreignPaths": list(current_diff_detail.get("foreignPaths") or [])[:20],
|
|
734
|
+
"ownedFileCount": current_diff_detail.get("ownedFileCount", 0),
|
|
735
|
+
"excludedRuntimeCount": current_diff_detail.get(
|
|
736
|
+
"excludedRuntimeCount", 0
|
|
737
|
+
),
|
|
738
|
+
},
|
|
739
|
+
"recoveryAction": (
|
|
740
|
+
"先确认新增、删除或越界路径是否属于当前变更;更新所有权后,"
|
|
741
|
+
"只重新执行受影响的验证,再原样重试 close。"
|
|
742
|
+
),
|
|
730
743
|
}
|
|
731
744
|
|
|
732
745
|
problems: list[dict[str, Any]] = []
|
|
@@ -1713,6 +1726,98 @@ def record_blocked_attempt(
|
|
|
1713
1726
|
)
|
|
1714
1727
|
|
|
1715
1728
|
|
|
1729
|
+
def record_gate_blocked(
|
|
1730
|
+
change_dir: Path,
|
|
1731
|
+
*,
|
|
1732
|
+
phase: str,
|
|
1733
|
+
code: str,
|
|
1734
|
+
message: str,
|
|
1735
|
+
run_id: str | None = None,
|
|
1736
|
+
) -> dict[str, Any]:
|
|
1737
|
+
"""Persist a recoverable gate rejection without manufacturing a phase run."""
|
|
1738
|
+
return append_phase_event(
|
|
1739
|
+
change_dir,
|
|
1740
|
+
phase=phase,
|
|
1741
|
+
type_="gate.blocked",
|
|
1742
|
+
status="BLOCKED",
|
|
1743
|
+
note=message,
|
|
1744
|
+
run_id=run_id or "blocked-" + uuid.uuid4().hex[:12],
|
|
1745
|
+
code=code,
|
|
1746
|
+
)
|
|
1747
|
+
|
|
1748
|
+
|
|
1749
|
+
def record_gate_recovered(
|
|
1750
|
+
change_dir: Path,
|
|
1751
|
+
*,
|
|
1752
|
+
phase: str,
|
|
1753
|
+
run_id: str,
|
|
1754
|
+
) -> dict[str, Any]:
|
|
1755
|
+
"""Close the latest unresolved gate.blocked record for this phase."""
|
|
1756
|
+
blocked: dict[str, Any] | None = None
|
|
1757
|
+
for event in reversed(he.load_events(he.events_path(change_dir))):
|
|
1758
|
+
if event.get("phase") != phase:
|
|
1759
|
+
continue
|
|
1760
|
+
if event.get("type") == "gate.recovered":
|
|
1761
|
+
break
|
|
1762
|
+
if event.get("type") == "gate.blocked":
|
|
1763
|
+
blocked = event
|
|
1764
|
+
break
|
|
1765
|
+
if blocked is None:
|
|
1766
|
+
return {"ok": True, "skipped": True, "reason": "no-unresolved-block"}
|
|
1767
|
+
return append_phase_event(
|
|
1768
|
+
change_dir,
|
|
1769
|
+
phase=phase,
|
|
1770
|
+
type_="gate.recovered",
|
|
1771
|
+
status="OK",
|
|
1772
|
+
note="阶段交接已恢复,门禁现已成功启动。",
|
|
1773
|
+
run_id=run_id,
|
|
1774
|
+
code=str(blocked.get("code") or "GATE_BLOCKED"),
|
|
1775
|
+
)
|
|
1776
|
+
|
|
1777
|
+
|
|
1778
|
+
def validate_context_for_gate_begin(
|
|
1779
|
+
project: Path,
|
|
1780
|
+
change_id: str,
|
|
1781
|
+
phase: str,
|
|
1782
|
+
) -> dict[str, Any]:
|
|
1783
|
+
"""Require a completed Context handoff whenever modern Context state exists."""
|
|
1784
|
+
view = hctx.context_view(project, change_id)
|
|
1785
|
+
if not isinstance(view, dict) or not view.get("ok"):
|
|
1786
|
+
return {"ok": True, "code": "CONTEXT_LEGACY_COMPATIBLE"}
|
|
1787
|
+
current = view.get("current")
|
|
1788
|
+
transitions = view.get("transitions")
|
|
1789
|
+
begins = (view.get("attemptHistory") or {}).get("begins")
|
|
1790
|
+
if not isinstance(current, dict):
|
|
1791
|
+
return {"ok": True, "code": "CONTEXT_LEGACY_COMPATIBLE"}
|
|
1792
|
+
current_phase = str(current.get("phase") or view.get("currentPhase") or "")
|
|
1793
|
+
if current_phase != phase:
|
|
1794
|
+
return {
|
|
1795
|
+
"ok": False,
|
|
1796
|
+
"code": "CONTEXT_HANDOFF_REQUIRED",
|
|
1797
|
+
"message": "阶段交接尚未完成,门禁未启动;请先完成上下文交接。",
|
|
1798
|
+
"expectedPhase": current_phase or None,
|
|
1799
|
+
"requestedPhase": phase,
|
|
1800
|
+
}
|
|
1801
|
+
if isinstance(transitions, list) and transitions:
|
|
1802
|
+
latest = transitions[-1] if isinstance(transitions[-1], dict) else {}
|
|
1803
|
+
receipt_hash = latest.get("receiptHash")
|
|
1804
|
+
begin_items = begins if isinstance(begins, list) else []
|
|
1805
|
+
acknowledged = any(
|
|
1806
|
+
isinstance(item, dict)
|
|
1807
|
+
and item.get("phase") == phase
|
|
1808
|
+
and item.get("receiptHash") == receipt_hash
|
|
1809
|
+
for item in begin_items
|
|
1810
|
+
)
|
|
1811
|
+
if latest.get("toPhase") != phase or not acknowledged:
|
|
1812
|
+
return {
|
|
1813
|
+
"ok": False,
|
|
1814
|
+
"code": "CONTEXT_BEGIN_REQUIRED",
|
|
1815
|
+
"message": "阶段交接收据尚未确认,门禁未启动;请先执行上下文 begin。",
|
|
1816
|
+
"requestedPhase": phase,
|
|
1817
|
+
}
|
|
1818
|
+
return {"ok": True, "code": "CONTEXT_READY"}
|
|
1819
|
+
|
|
1820
|
+
|
|
1716
1821
|
def _phase_event_exists(change_dir: Path, phase: str, type_: str, run_id: str) -> bool:
|
|
1717
1822
|
return any(
|
|
1718
1823
|
event.get("phase") == phase
|
|
@@ -2065,6 +2170,32 @@ def cmd_begin(args: argparse.Namespace) -> int:
|
|
|
2065
2170
|
extra={k: v for k, v in resolved.items() if k not in {"ok", "message"}},
|
|
2066
2171
|
)
|
|
2067
2172
|
change_dir = Path(resolved["changeDir"])
|
|
2173
|
+
context_check = validate_context_for_gate_begin(
|
|
2174
|
+
project, str(resolved["changeId"]), args.phase
|
|
2175
|
+
)
|
|
2176
|
+
if not context_check.get("ok"):
|
|
2177
|
+
code = str(context_check.get("code") or "CONTEXT_HANDOFF_REQUIRED")
|
|
2178
|
+
message = str(
|
|
2179
|
+
context_check.get("message")
|
|
2180
|
+
or "阶段交接尚未完成,门禁未启动。"
|
|
2181
|
+
)
|
|
2182
|
+
record_gate_blocked(
|
|
2183
|
+
change_dir,
|
|
2184
|
+
phase=args.phase,
|
|
2185
|
+
code=code,
|
|
2186
|
+
message=message,
|
|
2187
|
+
run_id=args.run_id or os.environ.get("HUNTER_HARNESS_RUN_ID"),
|
|
2188
|
+
)
|
|
2189
|
+
return emit_error(
|
|
2190
|
+
code,
|
|
2191
|
+
message,
|
|
2192
|
+
as_json=as_json,
|
|
2193
|
+
extra={
|
|
2194
|
+
key: value
|
|
2195
|
+
for key, value in context_check.items()
|
|
2196
|
+
if key not in {"ok", "code", "message"}
|
|
2197
|
+
},
|
|
2198
|
+
)
|
|
2068
2199
|
severity_mode = gate_severity_mode(project, change_dir)
|
|
2069
2200
|
gate_warnings: list[dict[str, Any]] = []
|
|
2070
2201
|
plan_verification: dict[str, Any] | None = None
|
|
@@ -2236,6 +2367,16 @@ def cmd_begin(args: argparse.Namespace) -> int:
|
|
|
2236
2367
|
message="test guard begin failed (downgraded to WARN)",
|
|
2237
2368
|
))
|
|
2238
2369
|
else:
|
|
2370
|
+
record_gate_blocked(
|
|
2371
|
+
change_dir,
|
|
2372
|
+
phase=args.phase,
|
|
2373
|
+
code=str(
|
|
2374
|
+
guard_result.get("code")
|
|
2375
|
+
or "TEST_GUARD_BEGIN_FAILED"
|
|
2376
|
+
),
|
|
2377
|
+
message="测试保护快照建立失败,已停止阶段启动。",
|
|
2378
|
+
run_id=run_id,
|
|
2379
|
+
)
|
|
2239
2380
|
hc.release_lease(
|
|
2240
2381
|
project,
|
|
2241
2382
|
change_id=resolved["changeId"],
|
|
@@ -2252,6 +2393,11 @@ def cmd_begin(args: argparse.Namespace) -> int:
|
|
|
2252
2393
|
current_head = _git_text(execution_root, "rev-parse", "--verify", "HEAD")
|
|
2253
2394
|
ledger, _ = hl.load_ledger(change_dir)
|
|
2254
2395
|
base_commit = ledger.get("baseCommit") if isinstance(ledger, dict) else None
|
|
2396
|
+
try:
|
|
2397
|
+
begin_contract = hp.load_change_contract(change_dir)
|
|
2398
|
+
begin_ownership_hash = hl.ownership_hash(begin_contract)
|
|
2399
|
+
except (OSError, ValueError, json.JSONDecodeError):
|
|
2400
|
+
begin_ownership_hash = None
|
|
2255
2401
|
capsule = {
|
|
2256
2402
|
"schemaVersion": 1,
|
|
2257
2403
|
"changeId": resolved["changeId"],
|
|
@@ -2264,6 +2410,7 @@ def cmd_begin(args: argparse.Namespace) -> int:
|
|
|
2264
2410
|
"repositoryId": hp.repository_identity(execution_root),
|
|
2265
2411
|
"baseCommit": base_commit or current_head,
|
|
2266
2412
|
"currentHead": current_head,
|
|
2413
|
+
"ownershipHash": begin_ownership_hash,
|
|
2267
2414
|
"createdAt": he.now_iso(),
|
|
2268
2415
|
"projectionReceipt": projection,
|
|
2269
2416
|
}
|
|
@@ -2281,6 +2428,11 @@ def cmd_begin(args: argparse.Namespace) -> int:
|
|
|
2281
2428
|
executor_agent=args.executor_agent or os.environ.get("HUNTER_HARNESS_AGENT"),
|
|
2282
2429
|
executor_model=args.executor_model or os.environ.get("HUNTER_HARNESS_MODEL"),
|
|
2283
2430
|
)
|
|
2431
|
+
recovery_result = record_gate_recovered(
|
|
2432
|
+
change_dir,
|
|
2433
|
+
phase=args.phase,
|
|
2434
|
+
run_id=run_id,
|
|
2435
|
+
)
|
|
2284
2436
|
except BaseException:
|
|
2285
2437
|
hc.release_lease(
|
|
2286
2438
|
project, change_id=resolved["changeId"], phase=args.phase, run_id=run_id
|
|
@@ -2300,6 +2452,7 @@ def cmd_begin(args: argparse.Namespace) -> int:
|
|
|
2300
2452
|
"lease": claim.get("lease"),
|
|
2301
2453
|
"identity": identity,
|
|
2302
2454
|
"event": event_result,
|
|
2455
|
+
"gateRecovery": recovery_result,
|
|
2303
2456
|
"testGuard": guard_result,
|
|
2304
2457
|
"policySchemaVersion": policy.get("schemaVersion"),
|
|
2305
2458
|
"planVerification": plan_verification,
|
|
@@ -2568,6 +2721,16 @@ def cmd_close(args: argparse.Namespace) -> int:
|
|
|
2568
2721
|
status="LEDGER_VALIDATION_FAILED",
|
|
2569
2722
|
error=ledger_result,
|
|
2570
2723
|
)
|
|
2724
|
+
record_gate_blocked(
|
|
2725
|
+
change_dir,
|
|
2726
|
+
phase=args.phase,
|
|
2727
|
+
code=str(ledger_result.get("code") or "LEDGER_INVALID"),
|
|
2728
|
+
message=(
|
|
2729
|
+
"验证记录与当前变更身份不一致,已保留现场;"
|
|
2730
|
+
"请按返回的路径摘要重新验证受影响范围。"
|
|
2731
|
+
),
|
|
2732
|
+
run_id=run_id,
|
|
2733
|
+
)
|
|
2571
2734
|
return emit_error(
|
|
2572
2735
|
str(ledger_result.get("code", "LEDGER_INVALID")),
|
|
2573
2736
|
str(ledger_result.get("message", "ledger validation failed")),
|
|
@@ -2656,6 +2819,16 @@ def cmd_close(args: argparse.Namespace) -> int:
|
|
|
2656
2819
|
"updatedAt": he.now_iso(),
|
|
2657
2820
|
})
|
|
2658
2821
|
write_phase_capsule(change_dir, args.phase, run_id, capsule)
|
|
2822
|
+
record_gate_blocked(
|
|
2823
|
+
change_dir,
|
|
2824
|
+
phase=args.phase,
|
|
2825
|
+
code=str(
|
|
2826
|
+
guard_result.get("code")
|
|
2827
|
+
or "TEST_GUARD_CLOSE_FAILED"
|
|
2828
|
+
),
|
|
2829
|
+
message="测试保护关门失败,已保留现场并等待同一命令恢复。",
|
|
2830
|
+
run_id=run_id,
|
|
2831
|
+
)
|
|
2659
2832
|
return emit_error(
|
|
2660
2833
|
str(guard_result.get("code", "TEST_GUARD_CLOSE_FAILED")),
|
|
2661
2834
|
"test guard close failed",
|
|
@@ -2741,6 +2914,13 @@ def cmd_close(args: argparse.Namespace) -> int:
|
|
|
2741
2914
|
)
|
|
2742
2915
|
platform_monitor = _sync_after_phase_close(project, change_dir)
|
|
2743
2916
|
if isinstance(context_handoff, dict) and not context_handoff.get("ok"):
|
|
2917
|
+
record_gate_blocked(
|
|
2918
|
+
change_dir,
|
|
2919
|
+
phase=args.phase,
|
|
2920
|
+
code=str(context_handoff.get("code") or "PHASE_HANDOFF_PENDING"),
|
|
2921
|
+
message="阶段本地关门已完成,但上下文交接尚待恢复。",
|
|
2922
|
+
run_id=run_id,
|
|
2923
|
+
)
|
|
2744
2924
|
return emit_error(
|
|
2745
2925
|
"PHASE_HANDOFF_PENDING",
|
|
2746
2926
|
"phase gate closed locally, but context handoff failed; retry the same close command",
|
|
@@ -2756,6 +2936,11 @@ def cmd_close(args: argparse.Namespace) -> int:
|
|
|
2756
2936
|
},
|
|
2757
2937
|
)
|
|
2758
2938
|
|
|
2939
|
+
recovery_result = record_gate_recovered(
|
|
2940
|
+
change_dir,
|
|
2941
|
+
phase=args.phase,
|
|
2942
|
+
run_id=run_id,
|
|
2943
|
+
)
|
|
2759
2944
|
payload = {
|
|
2760
2945
|
"ok": True,
|
|
2761
2946
|
"code": close_code,
|
|
@@ -2773,6 +2958,7 @@ def cmd_close(args: argparse.Namespace) -> int:
|
|
|
2773
2958
|
"gateWarnings": gate_warnings,
|
|
2774
2959
|
"contextHandoff": context_handoff,
|
|
2775
2960
|
"platformMonitor": platform_monitor,
|
|
2961
|
+
"gateRecovery": recovery_result,
|
|
2776
2962
|
}
|
|
2777
2963
|
emit(payload, as_json=as_json)
|
|
2778
2964
|
return 0
|