@hunter-harness/workflow-harness 0.2.11 → 0.2.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/harness/bundles/general/claude-code/.harness-build.json +1 -1
- package/harness/bundles/general/claude-code/contracts/ci-metrics.schema.json +36 -0
- package/harness/bundles/general/claude-code/contracts/workflow-policy.json +30 -0
- package/harness/bundles/general/claude-code/harness-archive/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-plan/SKILL.md +4 -4
- package/harness/bundles/general/claude-code/harness-plan/checklist.md +6 -0
- package/harness/bundles/general/claude-code/harness-plan/reference.md +7 -2
- package/harness/bundles/general/claude-code/harness-review/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-run/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-run/checklist.md +3 -2
- package/harness/bundles/general/claude-code/harness-run/reference.md +6 -4
- package/harness/bundles/general/claude-code/harness-submit/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-sync/SKILL.md +2 -2
- package/harness/bundles/general/claude-code/harness-sync/reference.md +5 -4
- package/harness/bundles/general/claude-code/harness-test/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/scripts/harness_archive.py +282 -120
- package/harness/bundles/general/claude-code/scripts/harness_events.py +400 -24
- package/harness/bundles/general/claude-code/scripts/harness_gate.py +585 -21
- package/harness/bundles/general/claude-code/scripts/harness_integration.py +403 -18
- package/harness/bundles/general/claude-code/scripts/harness_ledger.py +225 -29
- package/harness/bundles/general/claude-code/scripts/harness_phase.py +926 -0
- package/harness/bundles/general/claude-code/scripts/harness_plan_finalize.py +361 -0
- package/harness/bundles/general/claude-code/scripts/harness_profile.py +50 -0
- package/harness/bundles/general/claude-code/scripts/harness_runtime.py +248 -0
- package/harness/bundles/general/claude-code/scripts/harness_state.py +15 -2
- package/harness/bundles/general/claude-code/scripts/harness_test_guard.py +188 -0
- package/harness/bundles/general/claude-code/scripts/harness_workflow_policy.py +19 -0
- package/harness/bundles/general/codebuddy/.harness-build.json +1 -1
- package/harness/bundles/general/codebuddy/contracts/ci-metrics.schema.json +36 -0
- package/harness/bundles/general/codebuddy/contracts/workflow-policy.json +30 -0
- package/harness/bundles/general/codebuddy/harness-archive/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-plan/SKILL.md +4 -4
- package/harness/bundles/general/codebuddy/harness-plan/checklist.md +6 -0
- package/harness/bundles/general/codebuddy/harness-plan/reference.md +7 -2
- package/harness/bundles/general/codebuddy/harness-review/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-run/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-run/checklist.md +3 -2
- package/harness/bundles/general/codebuddy/harness-run/reference.md +6 -4
- package/harness/bundles/general/codebuddy/harness-submit/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-sync/SKILL.md +2 -2
- package/harness/bundles/general/codebuddy/harness-sync/reference.md +5 -4
- package/harness/bundles/general/codebuddy/harness-test/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/scripts/harness_archive.py +282 -120
- package/harness/bundles/general/codebuddy/scripts/harness_events.py +400 -24
- package/harness/bundles/general/codebuddy/scripts/harness_gate.py +585 -21
- package/harness/bundles/general/codebuddy/scripts/harness_integration.py +403 -18
- package/harness/bundles/general/codebuddy/scripts/harness_ledger.py +225 -29
- package/harness/bundles/general/codebuddy/scripts/harness_phase.py +926 -0
- package/harness/bundles/general/codebuddy/scripts/harness_plan_finalize.py +361 -0
- package/harness/bundles/general/codebuddy/scripts/harness_profile.py +50 -0
- package/harness/bundles/general/codebuddy/scripts/harness_runtime.py +248 -0
- package/harness/bundles/general/codebuddy/scripts/harness_state.py +15 -2
- package/harness/bundles/general/codebuddy/scripts/harness_test_guard.py +188 -0
- package/harness/bundles/general/codebuddy/scripts/harness_workflow_policy.py +19 -0
- package/harness/bundles/general/codex/.harness-build.json +1 -1
- package/harness/bundles/general/codex/contracts/ci-metrics.schema.json +36 -0
- package/harness/bundles/general/codex/contracts/workflow-policy.json +30 -0
- package/harness/bundles/general/codex/harness-archive/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-plan/SKILL.md +4 -4
- package/harness/bundles/general/codex/harness-plan/checklist.md +6 -0
- package/harness/bundles/general/codex/harness-plan/reference.md +7 -2
- package/harness/bundles/general/codex/harness-review/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-run/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-run/checklist.md +3 -2
- package/harness/bundles/general/codex/harness-run/reference.md +6 -4
- package/harness/bundles/general/codex/harness-submit/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-sync/SKILL.md +2 -2
- package/harness/bundles/general/codex/harness-sync/reference.md +5 -4
- package/harness/bundles/general/codex/harness-test/SKILL.md +1 -1
- package/harness/bundles/general/codex/scripts/harness_archive.py +282 -120
- package/harness/bundles/general/codex/scripts/harness_events.py +400 -24
- package/harness/bundles/general/codex/scripts/harness_gate.py +585 -21
- package/harness/bundles/general/codex/scripts/harness_integration.py +403 -18
- package/harness/bundles/general/codex/scripts/harness_ledger.py +225 -29
- package/harness/bundles/general/codex/scripts/harness_phase.py +926 -0
- package/harness/bundles/general/codex/scripts/harness_plan_finalize.py +361 -0
- package/harness/bundles/general/codex/scripts/harness_profile.py +50 -0
- package/harness/bundles/general/codex/scripts/harness_runtime.py +248 -0
- package/harness/bundles/general/codex/scripts/harness_state.py +15 -2
- package/harness/bundles/general/codex/scripts/harness_test_guard.py +188 -0
- package/harness/bundles/general/codex/scripts/harness_workflow_policy.py +19 -0
- package/harness/bundles/general/cursor/.harness-build.json +1 -1
- package/harness/bundles/general/cursor/contracts/ci-metrics.schema.json +36 -0
- package/harness/bundles/general/cursor/contracts/workflow-policy.json +30 -0
- package/harness/bundles/general/cursor/harness-archive/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-plan/SKILL.md +4 -4
- package/harness/bundles/general/cursor/harness-plan/checklist.md +6 -0
- package/harness/bundles/general/cursor/harness-plan/reference.md +7 -2
- package/harness/bundles/general/cursor/harness-review/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-run/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-run/checklist.md +3 -2
- package/harness/bundles/general/cursor/harness-run/reference.md +6 -4
- package/harness/bundles/general/cursor/harness-submit/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-sync/SKILL.md +2 -2
- package/harness/bundles/general/cursor/harness-sync/reference.md +5 -4
- package/harness/bundles/general/cursor/harness-test/SKILL.md +1 -1
- package/harness/bundles/general/cursor/scripts/harness_archive.py +282 -120
- package/harness/bundles/general/cursor/scripts/harness_events.py +400 -24
- package/harness/bundles/general/cursor/scripts/harness_gate.py +585 -21
- package/harness/bundles/general/cursor/scripts/harness_integration.py +403 -18
- package/harness/bundles/general/cursor/scripts/harness_ledger.py +225 -29
- package/harness/bundles/general/cursor/scripts/harness_phase.py +926 -0
- package/harness/bundles/general/cursor/scripts/harness_plan_finalize.py +361 -0
- package/harness/bundles/general/cursor/scripts/harness_profile.py +50 -0
- package/harness/bundles/general/cursor/scripts/harness_runtime.py +248 -0
- package/harness/bundles/general/cursor/scripts/harness_state.py +15 -2
- package/harness/bundles/general/cursor/scripts/harness_test_guard.py +188 -0
- package/harness/bundles/general/cursor/scripts/harness_workflow_policy.py +19 -0
- package/harness/bundles/java/claude-code/.harness-build.json +1 -1
- package/harness/bundles/java/claude-code/contracts/ci-metrics.schema.json +36 -0
- package/harness/bundles/java/claude-code/contracts/workflow-policy.json +30 -0
- package/harness/bundles/java/claude-code/harness-apidoc/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-archive/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-package/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-plan/SKILL.md +4 -4
- package/harness/bundles/java/claude-code/harness-plan/checklist.md +6 -0
- package/harness/bundles/java/claude-code/harness-plan/reference.md +7 -2
- package/harness/bundles/java/claude-code/harness-review/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-run/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-submit/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-sync/SKILL.md +2 -2
- package/harness/bundles/java/claude-code/harness-sync/reference.md +5 -4
- package/harness/bundles/java/claude-code/harness-test/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/scripts/harness_archive.py +282 -120
- package/harness/bundles/java/claude-code/scripts/harness_events.py +400 -24
- package/harness/bundles/java/claude-code/scripts/harness_gate.py +585 -21
- package/harness/bundles/java/claude-code/scripts/harness_integration.py +403 -18
- package/harness/bundles/java/claude-code/scripts/harness_ledger.py +225 -29
- package/harness/bundles/java/claude-code/scripts/harness_phase.py +926 -0
- package/harness/bundles/java/claude-code/scripts/harness_plan_finalize.py +361 -0
- package/harness/bundles/java/claude-code/scripts/harness_profile.py +50 -0
- package/harness/bundles/java/claude-code/scripts/harness_runtime.py +248 -0
- package/harness/bundles/java/claude-code/scripts/harness_state.py +15 -2
- package/harness/bundles/java/claude-code/scripts/harness_test_guard.py +188 -0
- package/harness/bundles/java/claude-code/scripts/harness_workflow_policy.py +19 -0
- package/harness/bundles/java/codebuddy/.harness-build.json +1 -1
- package/harness/bundles/java/codebuddy/contracts/ci-metrics.schema.json +36 -0
- package/harness/bundles/java/codebuddy/contracts/workflow-policy.json +30 -0
- package/harness/bundles/java/codebuddy/harness-apidoc/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-archive/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-package/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-plan/SKILL.md +4 -4
- package/harness/bundles/java/codebuddy/harness-plan/checklist.md +6 -0
- package/harness/bundles/java/codebuddy/harness-plan/reference.md +7 -2
- package/harness/bundles/java/codebuddy/harness-review/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-run/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-submit/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-sync/SKILL.md +2 -2
- package/harness/bundles/java/codebuddy/harness-sync/reference.md +5 -4
- package/harness/bundles/java/codebuddy/harness-test/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/scripts/harness_archive.py +282 -120
- package/harness/bundles/java/codebuddy/scripts/harness_events.py +400 -24
- package/harness/bundles/java/codebuddy/scripts/harness_gate.py +585 -21
- package/harness/bundles/java/codebuddy/scripts/harness_integration.py +403 -18
- package/harness/bundles/java/codebuddy/scripts/harness_ledger.py +225 -29
- package/harness/bundles/java/codebuddy/scripts/harness_phase.py +926 -0
- package/harness/bundles/java/codebuddy/scripts/harness_plan_finalize.py +361 -0
- package/harness/bundles/java/codebuddy/scripts/harness_profile.py +50 -0
- package/harness/bundles/java/codebuddy/scripts/harness_runtime.py +248 -0
- package/harness/bundles/java/codebuddy/scripts/harness_state.py +15 -2
- package/harness/bundles/java/codebuddy/scripts/harness_test_guard.py +188 -0
- package/harness/bundles/java/codebuddy/scripts/harness_workflow_policy.py +19 -0
- package/harness/bundles/java/codex/.harness-build.json +1 -1
- package/harness/bundles/java/codex/contracts/ci-metrics.schema.json +36 -0
- package/harness/bundles/java/codex/contracts/workflow-policy.json +30 -0
- package/harness/bundles/java/codex/harness-apidoc/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-archive/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-package/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-plan/SKILL.md +4 -4
- package/harness/bundles/java/codex/harness-plan/checklist.md +6 -0
- package/harness/bundles/java/codex/harness-plan/reference.md +7 -2
- package/harness/bundles/java/codex/harness-review/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-run/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-submit/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-sync/SKILL.md +2 -2
- package/harness/bundles/java/codex/harness-sync/reference.md +5 -4
- package/harness/bundles/java/codex/harness-test/SKILL.md +1 -1
- package/harness/bundles/java/codex/scripts/harness_archive.py +282 -120
- package/harness/bundles/java/codex/scripts/harness_events.py +400 -24
- package/harness/bundles/java/codex/scripts/harness_gate.py +585 -21
- package/harness/bundles/java/codex/scripts/harness_integration.py +403 -18
- package/harness/bundles/java/codex/scripts/harness_ledger.py +225 -29
- package/harness/bundles/java/codex/scripts/harness_phase.py +926 -0
- package/harness/bundles/java/codex/scripts/harness_plan_finalize.py +361 -0
- package/harness/bundles/java/codex/scripts/harness_profile.py +50 -0
- package/harness/bundles/java/codex/scripts/harness_runtime.py +248 -0
- package/harness/bundles/java/codex/scripts/harness_state.py +15 -2
- package/harness/bundles/java/codex/scripts/harness_test_guard.py +188 -0
- package/harness/bundles/java/codex/scripts/harness_workflow_policy.py +19 -0
- package/harness/bundles/java/cursor/.harness-build.json +1 -1
- package/harness/bundles/java/cursor/contracts/ci-metrics.schema.json +36 -0
- package/harness/bundles/java/cursor/contracts/workflow-policy.json +30 -0
- package/harness/bundles/java/cursor/harness-apidoc/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-archive/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-package/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-plan/SKILL.md +4 -4
- package/harness/bundles/java/cursor/harness-plan/checklist.md +6 -0
- package/harness/bundles/java/cursor/harness-plan/reference.md +7 -2
- package/harness/bundles/java/cursor/harness-review/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-run/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-submit/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-sync/SKILL.md +2 -2
- package/harness/bundles/java/cursor/harness-sync/reference.md +5 -4
- package/harness/bundles/java/cursor/harness-test/SKILL.md +1 -1
- package/harness/bundles/java/cursor/scripts/harness_archive.py +282 -120
- package/harness/bundles/java/cursor/scripts/harness_events.py +400 -24
- package/harness/bundles/java/cursor/scripts/harness_gate.py +585 -21
- package/harness/bundles/java/cursor/scripts/harness_integration.py +403 -18
- package/harness/bundles/java/cursor/scripts/harness_ledger.py +225 -29
- package/harness/bundles/java/cursor/scripts/harness_phase.py +926 -0
- package/harness/bundles/java/cursor/scripts/harness_plan_finalize.py +361 -0
- package/harness/bundles/java/cursor/scripts/harness_profile.py +50 -0
- package/harness/bundles/java/cursor/scripts/harness_runtime.py +248 -0
- package/harness/bundles/java/cursor/scripts/harness_state.py +15 -2
- package/harness/bundles/java/cursor/scripts/harness_test_guard.py +188 -0
- package/harness/bundles/java/cursor/scripts/harness_workflow_policy.py +19 -0
- package/harness/manifests/general/claude-code.json +43 -27
- package/harness/manifests/general/codebuddy.json +43 -27
- package/harness/manifests/general/codex.json +43 -27
- package/harness/manifests/general/cursor.json +43 -27
- package/harness/manifests/java/claude-code.json +43 -27
- package/harness/manifests/java/codebuddy.json +43 -27
- package/harness/manifests/java/codex.json +43 -27
- package/harness/manifests/java/cursor.json +43 -27
- package/hunter-workflow-family.json +2 -2
- package/package.json +1 -1
|
@@ -43,6 +43,9 @@ if hasattr(sys.stderr, "reconfigure"):
|
|
|
43
43
|
sys.stderr.reconfigure(encoding="utf-8")
|
|
44
44
|
|
|
45
45
|
STEP_ORDER = ["preflight", "prepare", "merge", "verify", "push", "cleanup"]
|
|
46
|
+
HEAVY_WORKTREE_ROOTS = (
|
|
47
|
+
"node_modules", ".venv", "venv", "build", "dist", "target", ".cache", "__pycache__"
|
|
48
|
+
)
|
|
46
49
|
_FOREIGN_PATTERNS = (
|
|
47
50
|
re.compile(r"^\.harness/changes/([^/]+)(/|$)"),
|
|
48
51
|
re.compile(r"^\.harness/state/changes/([^/]+)(/|$)"),
|
|
@@ -73,14 +76,33 @@ class VerificationFailedError(IntegrationError):
|
|
|
73
76
|
code = "VERIFICATION_FAILED"
|
|
74
77
|
|
|
75
78
|
|
|
79
|
+
class VerifyPlanMissingError(IntegrationError):
|
|
80
|
+
code = "VERIFY_PLAN_MISSING"
|
|
81
|
+
|
|
82
|
+
|
|
76
83
|
class TargetMovedError(IntegrationError):
|
|
77
84
|
code = "TARGET_MOVED"
|
|
78
85
|
|
|
79
86
|
|
|
87
|
+
class LedgerSyncError(IntegrationError):
|
|
88
|
+
code = "LEDGER_SYNC_PENDING"
|
|
89
|
+
|
|
90
|
+
|
|
80
91
|
class CleanupRefusedError(IntegrationError):
|
|
81
92
|
code = "CLEANUP_REFUSED"
|
|
82
93
|
|
|
83
94
|
|
|
95
|
+
class CleanupResidualError(IntegrationError):
|
|
96
|
+
code = "CLEANUP_RESIDUAL"
|
|
97
|
+
|
|
98
|
+
def __init__(self, result: dict[str, Any]):
|
|
99
|
+
self.result = result
|
|
100
|
+
super().__init__(
|
|
101
|
+
"worktree cleanup retained paths: "
|
|
102
|
+
+ ", ".join(result.get("residualPaths") or ["<registration>"])
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
|
|
84
106
|
class JournalConflictError(IntegrationError):
|
|
85
107
|
code = "JOURNAL_CONFLICT"
|
|
86
108
|
|
|
@@ -361,6 +383,120 @@ class IntegrationTransaction:
|
|
|
361
383
|
"ledgerIdentity": ledger_identity,
|
|
362
384
|
}
|
|
363
385
|
|
|
386
|
+
def _ledger_path(self) -> Path | None:
|
|
387
|
+
candidates = (
|
|
388
|
+
self.project_root / ".harness" / "state" / "changes" / self.change_id
|
|
389
|
+
/ "evidence" / "verification-ledger.json",
|
|
390
|
+
self.project_root / ".harness" / "changes" / self.change_id
|
|
391
|
+
/ "evidence" / "verification-ledger.json",
|
|
392
|
+
self.project_root / ".harness" / "state" / "changes" / self.change_id
|
|
393
|
+
/ "verification-ledger.json",
|
|
394
|
+
)
|
|
395
|
+
return next((path for path in candidates if path.is_file()), None)
|
|
396
|
+
|
|
397
|
+
def _finalize_remote_push(
|
|
398
|
+
self,
|
|
399
|
+
journal: dict[str, Any],
|
|
400
|
+
*,
|
|
401
|
+
merge_commit: str,
|
|
402
|
+
remote_head: str,
|
|
403
|
+
) -> None:
|
|
404
|
+
journal.update({
|
|
405
|
+
"pushedHead": merge_commit,
|
|
406
|
+
"mergeFinalHash": merge_commit,
|
|
407
|
+
"ciExpectedHead": merge_commit,
|
|
408
|
+
"remoteHead": remote_head,
|
|
409
|
+
"pushIntent": {
|
|
410
|
+
"status": "REMOTE_CONFIRMED",
|
|
411
|
+
"expectedRemoteHead": journal.get("remoteTargetHead") or journal.get("base"),
|
|
412
|
+
"mergeCommit": merge_commit,
|
|
413
|
+
"observedRemoteHead": remote_head,
|
|
414
|
+
"updatedAt": now_iso(),
|
|
415
|
+
},
|
|
416
|
+
"ledgerSync": {"status": "PENDING", "updatedAt": now_iso()},
|
|
417
|
+
})
|
|
418
|
+
self._save(journal)
|
|
419
|
+
ledger_path = self._ledger_path()
|
|
420
|
+
if ledger_path is None:
|
|
421
|
+
result = {"ok": False, "code": "LEDGER_MISSING"}
|
|
422
|
+
else:
|
|
423
|
+
result = hl.record_integration_hashes(
|
|
424
|
+
ledger_path,
|
|
425
|
+
repository_id=str(journal["repositoryId"]),
|
|
426
|
+
merge_final_hash=merge_commit,
|
|
427
|
+
ci_expected_head=merge_commit,
|
|
428
|
+
remote_head=remote_head,
|
|
429
|
+
)
|
|
430
|
+
journal = self._load()
|
|
431
|
+
journal["ledgerSync"] = {
|
|
432
|
+
"status": "DONE" if result.get("ok") else "FAILED",
|
|
433
|
+
"result": result,
|
|
434
|
+
"updatedAt": now_iso(),
|
|
435
|
+
}
|
|
436
|
+
self._save(journal)
|
|
437
|
+
if not result.get("ok"):
|
|
438
|
+
raise LedgerSyncError(
|
|
439
|
+
f"remote push is confirmed but ledger sync requires recovery: {result.get('code')}"
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
def _assert_cleanup_consistency(self, journal: dict[str, Any]) -> None:
|
|
443
|
+
push_step = self._step(journal, "push")
|
|
444
|
+
merge_commit = str(journal.get("mergeCommit") or "")
|
|
445
|
+
remote_line = self.runner.text(
|
|
446
|
+
self.project_root, "ls-remote", "origin", self.target_branch
|
|
447
|
+
)
|
|
448
|
+
observed_remote = (
|
|
449
|
+
remote_line.split()[0] if remote_line and remote_line.split() else ""
|
|
450
|
+
)
|
|
451
|
+
remote_contains_merge = bool(merge_commit and observed_remote == merge_commit)
|
|
452
|
+
if push_step.get("status") != "DONE" and not remote_contains_merge:
|
|
453
|
+
if self._step(journal, "prepare").get("status") == "DONE":
|
|
454
|
+
raise CleanupRefusedError(
|
|
455
|
+
"push is not complete and remote does not contain the merge commit"
|
|
456
|
+
)
|
|
457
|
+
return
|
|
458
|
+
final_values = [
|
|
459
|
+
journal.get("pushedHead"),
|
|
460
|
+
journal.get("mergeFinalHash"),
|
|
461
|
+
journal.get("ciExpectedHead"),
|
|
462
|
+
journal.get("remoteHead"),
|
|
463
|
+
observed_remote,
|
|
464
|
+
]
|
|
465
|
+
if any(not isinstance(value, str) or not value for value in final_values) or len(
|
|
466
|
+
set(final_values)
|
|
467
|
+
) != 1:
|
|
468
|
+
raise CleanupRefusedError(
|
|
469
|
+
"remote/journal final hashes are missing or inconsistent"
|
|
470
|
+
)
|
|
471
|
+
ledger_path = self._ledger_path()
|
|
472
|
+
if ledger_path is None:
|
|
473
|
+
raise CleanupRefusedError("verification ledger missing before cleanup")
|
|
474
|
+
try:
|
|
475
|
+
ledger = json.loads(ledger_path.read_text(encoding="utf-8-sig"))
|
|
476
|
+
except (OSError, json.JSONDecodeError) as exc:
|
|
477
|
+
raise CleanupRefusedError(f"verification ledger unreadable: {exc}") from exc
|
|
478
|
+
ledger_values = [
|
|
479
|
+
ledger.get("mergeFinalHash"),
|
|
480
|
+
ledger.get("ciExpectedHead"),
|
|
481
|
+
ledger.get("remoteHead"),
|
|
482
|
+
]
|
|
483
|
+
if len(set(ledger_values + [observed_remote])) != 1:
|
|
484
|
+
raise CleanupRefusedError(
|
|
485
|
+
"remote/journal/ledger final hashes are inconsistent"
|
|
486
|
+
)
|
|
487
|
+
ancestor = self.runner.run(
|
|
488
|
+
self.project_root,
|
|
489
|
+
"merge-base",
|
|
490
|
+
"--is-ancestor",
|
|
491
|
+
str(journal.get("featureHead") or ""),
|
|
492
|
+
observed_remote,
|
|
493
|
+
check=False,
|
|
494
|
+
)
|
|
495
|
+
if ancestor.returncode != 0:
|
|
496
|
+
raise CleanupRefusedError(
|
|
497
|
+
"feature head is not an ancestor of the pushed target"
|
|
498
|
+
)
|
|
499
|
+
|
|
364
500
|
def _load(self) -> dict[str, Any]:
|
|
365
501
|
return load_journal(self.project_root, self.transaction_id)
|
|
366
502
|
|
|
@@ -414,15 +550,68 @@ class IntegrationTransaction:
|
|
|
414
550
|
|
|
415
551
|
def preflight(self) -> dict[str, Any]:
|
|
416
552
|
def action(journal: dict[str, Any]) -> None:
|
|
417
|
-
|
|
553
|
+
self.runner.run(
|
|
554
|
+
self.project_root,
|
|
555
|
+
"fetch",
|
|
556
|
+
"origin",
|
|
557
|
+
self.target_branch,
|
|
558
|
+
check=False,
|
|
559
|
+
)
|
|
560
|
+
local_target = self.runner.text(
|
|
418
561
|
self.project_root, "rev-parse", self.target_branch
|
|
419
562
|
)
|
|
563
|
+
remote_line = self.runner.text(
|
|
564
|
+
self.project_root, "ls-remote", "origin", self.target_branch
|
|
565
|
+
)
|
|
566
|
+
remote_target = (
|
|
567
|
+
remote_line.split()[0] if remote_line and remote_line.split() else None
|
|
568
|
+
)
|
|
420
569
|
feature_head = self.runner.text(
|
|
421
570
|
self.project_root, "rev-parse", self.feature_branch
|
|
422
571
|
)
|
|
423
|
-
if not
|
|
572
|
+
if not local_target or not feature_head:
|
|
424
573
|
raise IntegrationError("target or feature branch not found")
|
|
574
|
+
reconciliation_required = False
|
|
575
|
+
base = local_target
|
|
576
|
+
base_source = "local"
|
|
577
|
+
if remote_target:
|
|
578
|
+
if remote_target == local_target:
|
|
579
|
+
base = remote_target
|
|
580
|
+
base_source = "remote"
|
|
581
|
+
else:
|
|
582
|
+
local_behind = self.runner.run(
|
|
583
|
+
self.project_root,
|
|
584
|
+
"merge-base",
|
|
585
|
+
"--is-ancestor",
|
|
586
|
+
local_target,
|
|
587
|
+
remote_target,
|
|
588
|
+
check=False,
|
|
589
|
+
)
|
|
590
|
+
remote_behind = self.runner.run(
|
|
591
|
+
self.project_root,
|
|
592
|
+
"merge-base",
|
|
593
|
+
"--is-ancestor",
|
|
594
|
+
remote_target,
|
|
595
|
+
local_target,
|
|
596
|
+
check=False,
|
|
597
|
+
)
|
|
598
|
+
if local_behind.returncode == 0:
|
|
599
|
+
base = remote_target
|
|
600
|
+
base_source = "remote-ahead"
|
|
601
|
+
reconciliation_required = True
|
|
602
|
+
elif remote_behind.returncode == 0:
|
|
603
|
+
base = local_target
|
|
604
|
+
base_source = "local-ahead"
|
|
605
|
+
else:
|
|
606
|
+
raise TargetMovedError(
|
|
607
|
+
f"local and remote {self.target_branch} diverged: "
|
|
608
|
+
f"local={local_target}, remote={remote_target}"
|
|
609
|
+
)
|
|
425
610
|
fresh = self._new_journal(base, feature_head)
|
|
611
|
+
fresh["baseSource"] = base_source
|
|
612
|
+
fresh["localTargetHead"] = local_target
|
|
613
|
+
fresh["remoteTargetHead"] = remote_target
|
|
614
|
+
fresh["primaryReconciliationRequired"] = reconciliation_required
|
|
426
615
|
fresh["revision"] = int(journal.get("revision") or 0)
|
|
427
616
|
journal.clear()
|
|
428
617
|
journal.update(fresh)
|
|
@@ -500,11 +689,17 @@ class IntegrationTransaction:
|
|
|
500
689
|
return self._run_step("prepare", action)
|
|
501
690
|
|
|
502
691
|
def _foreign_paths(self, journal: dict[str, Any]) -> list[str]:
|
|
692
|
+
feature_base = self.runner.text(
|
|
693
|
+
self.project_root,
|
|
694
|
+
"merge-base",
|
|
695
|
+
journal["base"],
|
|
696
|
+
journal["featureHead"],
|
|
697
|
+
) or journal["base"]
|
|
503
698
|
diff = self.runner.text(
|
|
504
699
|
self.project_root,
|
|
505
700
|
"diff",
|
|
506
701
|
"--name-only",
|
|
507
|
-
|
|
702
|
+
feature_base,
|
|
508
703
|
journal["featureHead"],
|
|
509
704
|
)
|
|
510
705
|
contract: dict[str, Any] = {}
|
|
@@ -558,11 +753,27 @@ class IntegrationTransaction:
|
|
|
558
753
|
current_target = self.runner.text(
|
|
559
754
|
self.project_root, "rev-parse", self.target_branch
|
|
560
755
|
)
|
|
561
|
-
|
|
756
|
+
expected_local = journal.get("localTargetHead") or journal["base"]
|
|
757
|
+
if current_target != expected_local:
|
|
562
758
|
raise TargetMovedError(
|
|
563
759
|
f"local {self.target_branch} moved: expected "
|
|
564
|
-
f"{
|
|
760
|
+
f"{expected_local}, found {current_target}"
|
|
761
|
+
)
|
|
762
|
+
if journal.get("remoteTargetHead"):
|
|
763
|
+
remote_line = self.runner.text(
|
|
764
|
+
self.project_root, "ls-remote", "origin", self.target_branch
|
|
765
|
+
)
|
|
766
|
+
current_remote = (
|
|
767
|
+
remote_line.split()[0]
|
|
768
|
+
if remote_line and remote_line.split()
|
|
769
|
+
else None
|
|
565
770
|
)
|
|
771
|
+
expected_remote = journal.get("remoteTargetHead")
|
|
772
|
+
if current_remote != expected_remote:
|
|
773
|
+
raise TargetMovedError(
|
|
774
|
+
f"remote {self.target_branch} moved: expected "
|
|
775
|
+
f"{expected_remote}, found {current_remote}"
|
|
776
|
+
)
|
|
566
777
|
foreign = self._foreign_paths(journal)
|
|
567
778
|
if foreign:
|
|
568
779
|
raise ForeignChangePathsError(
|
|
@@ -589,6 +800,13 @@ class IntegrationTransaction:
|
|
|
589
800
|
commands = list(commands or [])
|
|
590
801
|
|
|
591
802
|
def action(journal: dict[str, Any]) -> None:
|
|
803
|
+
if not commands or any(
|
|
804
|
+
not isinstance(command, (list, tuple)) or not command or not command[0]
|
|
805
|
+
for command in commands
|
|
806
|
+
):
|
|
807
|
+
raise VerifyPlanMissingError(
|
|
808
|
+
"integration verification requires at least one executable command"
|
|
809
|
+
)
|
|
592
810
|
intg_root = Path(journal["integrationRoot"]).resolve()
|
|
593
811
|
results: list[dict[str, Any]] = []
|
|
594
812
|
for command in commands:
|
|
@@ -641,6 +859,24 @@ class IntegrationTransaction:
|
|
|
641
859
|
|
|
642
860
|
def push(self) -> dict[str, Any]:
|
|
643
861
|
def action(journal: dict[str, Any]) -> None:
|
|
862
|
+
verify_step = self._step(journal, "verify")
|
|
863
|
+
verify_results = journal.get("verifyResults")
|
|
864
|
+
if (
|
|
865
|
+
verify_step.get("status") != "DONE"
|
|
866
|
+
or not isinstance(verify_results, list)
|
|
867
|
+
or not verify_results
|
|
868
|
+
or any(
|
|
869
|
+
not isinstance(item, dict)
|
|
870
|
+
or item.get("exitCode") != 0
|
|
871
|
+
or not isinstance(item.get("command"), list)
|
|
872
|
+
or not item.get("command")
|
|
873
|
+
for item in verify_results
|
|
874
|
+
)
|
|
875
|
+
or not isinstance(journal.get("verificationIdentity"), dict)
|
|
876
|
+
):
|
|
877
|
+
raise VerifyPlanMissingError(
|
|
878
|
+
"push requires a completed, non-empty verification plan"
|
|
879
|
+
)
|
|
644
880
|
remote_head = self.runner.text(
|
|
645
881
|
self.project_root, "ls-remote", "origin", self.target_branch
|
|
646
882
|
)
|
|
@@ -656,28 +892,60 @@ class IntegrationTransaction:
|
|
|
656
892
|
# The previous push may have reached the remote before the local
|
|
657
893
|
# process could persist pushedHead/step=DONE. Reconcile that
|
|
658
894
|
# successful outcome instead of misclassifying it as drift.
|
|
659
|
-
|
|
660
|
-
|
|
895
|
+
self._finalize_remote_push(
|
|
896
|
+
journal, merge_commit=merge_commit, remote_head=remote_head
|
|
897
|
+
)
|
|
661
898
|
return
|
|
662
|
-
|
|
899
|
+
expected_remote = journal.get("remoteTargetHead") or journal["base"]
|
|
900
|
+
if remote_head != expected_remote:
|
|
663
901
|
raise TargetMovedError(
|
|
664
902
|
f"remote {self.target_branch} moved: expected "
|
|
665
|
-
f"{
|
|
903
|
+
f"{expected_remote}, found {remote_head}"
|
|
666
904
|
)
|
|
905
|
+
journal["pushIntent"] = {
|
|
906
|
+
"status": "PENDING",
|
|
907
|
+
"expectedRemoteHead": expected_remote,
|
|
908
|
+
"mergeCommit": merge_commit,
|
|
909
|
+
"updatedAt": now_iso(),
|
|
910
|
+
}
|
|
911
|
+
self._save(journal)
|
|
667
912
|
self.runner.run(
|
|
668
913
|
self.project_root,
|
|
669
914
|
"push",
|
|
670
915
|
"origin",
|
|
671
916
|
f"{merge_commit}:refs/heads/{self.target_branch}",
|
|
672
917
|
)
|
|
673
|
-
|
|
674
|
-
|
|
918
|
+
remote_after_line = self.runner.text(
|
|
919
|
+
self.project_root, "ls-remote", "origin", self.target_branch
|
|
920
|
+
)
|
|
921
|
+
remote_after = (
|
|
922
|
+
remote_after_line.split()[0]
|
|
923
|
+
if remote_after_line and remote_after_line.split()
|
|
924
|
+
else ""
|
|
925
|
+
)
|
|
926
|
+
if remote_after != merge_commit:
|
|
927
|
+
journal = self._load()
|
|
928
|
+
journal["pushIntent"] = {
|
|
929
|
+
"status": "REMOTE_UNCONFIRMED",
|
|
930
|
+
"expectedRemoteHead": expected_remote,
|
|
931
|
+
"mergeCommit": merge_commit,
|
|
932
|
+
"observedRemoteHead": remote_after,
|
|
933
|
+
"updatedAt": now_iso(),
|
|
934
|
+
}
|
|
935
|
+
self._save(journal)
|
|
936
|
+
raise IntegrationError(
|
|
937
|
+
"push returned but the remote target does not equal the merge commit"
|
|
938
|
+
)
|
|
939
|
+
journal = self._load()
|
|
940
|
+
self._finalize_remote_push(
|
|
941
|
+
journal, merge_commit=merge_commit, remote_head=remote_after
|
|
942
|
+
)
|
|
675
943
|
|
|
676
944
|
return self._run_step("push", action)
|
|
677
945
|
|
|
678
946
|
# ------------------------------------------------------------ cleanup
|
|
679
947
|
|
|
680
|
-
def cleanup_target(self, target: Path) ->
|
|
948
|
+
def cleanup_target(self, target: Path) -> dict[str, Any]:
|
|
681
949
|
"""Boundary-checked removal of this transaction's exact worktree."""
|
|
682
950
|
resolved = harness_paths.assert_path_within(target, self.temp_root)
|
|
683
951
|
try:
|
|
@@ -692,15 +960,71 @@ class IntegrationTransaction:
|
|
|
692
960
|
raise CleanupRefusedError(
|
|
693
961
|
f"refusing to clean non-transaction path: {resolved}"
|
|
694
962
|
)
|
|
963
|
+
listed_before = self.runner.text(
|
|
964
|
+
self.project_root, "worktree", "list", "--porcelain"
|
|
965
|
+
)
|
|
966
|
+
registered_before = {
|
|
967
|
+
str(Path(line.removeprefix("worktree ")).resolve())
|
|
968
|
+
for line in (listed_before or "").splitlines()
|
|
969
|
+
if line.startswith("worktree ")
|
|
970
|
+
}
|
|
971
|
+
if resolved.exists() and str(resolved) not in registered_before:
|
|
972
|
+
raise CleanupRefusedError(
|
|
973
|
+
f"refusing to clean unregistered worktree path: {resolved}"
|
|
974
|
+
)
|
|
975
|
+
|
|
976
|
+
heavy_removed: list[str] = []
|
|
977
|
+
heavy_failures: list[dict[str, str]] = []
|
|
978
|
+
if resolved.is_dir():
|
|
979
|
+
for name in HEAVY_WORKTREE_ROOTS:
|
|
980
|
+
heavy = resolved / name
|
|
981
|
+
if not heavy.exists():
|
|
982
|
+
continue
|
|
983
|
+
harness_paths.assert_path_within(heavy, resolved)
|
|
984
|
+
try:
|
|
985
|
+
if heavy.is_dir() and not heavy.is_symlink():
|
|
986
|
+
shutil.rmtree(heavy)
|
|
987
|
+
else:
|
|
988
|
+
heavy.unlink()
|
|
989
|
+
heavy_removed.append(name)
|
|
990
|
+
except OSError as exc:
|
|
991
|
+
heavy_failures.append({"path": name, "error": str(exc)})
|
|
695
992
|
self.runner.run(
|
|
696
993
|
self.project_root, "worktree", "remove", "--force", str(resolved),
|
|
697
994
|
check=False,
|
|
698
995
|
)
|
|
699
|
-
if resolved.exists():
|
|
700
|
-
shutil.rmtree(resolved)
|
|
701
996
|
listed = self.runner.text(self.project_root, "worktree", "list", "--porcelain")
|
|
702
|
-
|
|
703
|
-
|
|
997
|
+
registered_after = {
|
|
998
|
+
str(Path(line.removeprefix("worktree ")).resolve())
|
|
999
|
+
for line in (listed or "").splitlines()
|
|
1000
|
+
if line.startswith("worktree ")
|
|
1001
|
+
}
|
|
1002
|
+
still_registered = str(resolved) in registered_after
|
|
1003
|
+
residual_paths: list[str] = []
|
|
1004
|
+
if resolved.exists():
|
|
1005
|
+
if resolved.is_file():
|
|
1006
|
+
residual_paths.append(resolved.name)
|
|
1007
|
+
else:
|
|
1008
|
+
for root, dirs, files in os.walk(resolved):
|
|
1009
|
+
rel_root = Path(root).relative_to(resolved)
|
|
1010
|
+
for name in sorted(dirs + files):
|
|
1011
|
+
rel = (rel_root / name).as_posix()
|
|
1012
|
+
if rel not in residual_paths:
|
|
1013
|
+
residual_paths.append(rel)
|
|
1014
|
+
if len(residual_paths) >= 200:
|
|
1015
|
+
break
|
|
1016
|
+
if len(residual_paths) >= 200:
|
|
1017
|
+
break
|
|
1018
|
+
ok = not heavy_failures and not still_registered and not residual_paths
|
|
1019
|
+
return {
|
|
1020
|
+
"ok": ok,
|
|
1021
|
+
"code": "CLEANUP_COMPLETE" if ok else "CLEANUP_RESIDUAL",
|
|
1022
|
+
"target": str(resolved),
|
|
1023
|
+
"heavyRootsRemoved": heavy_removed,
|
|
1024
|
+
"heavyRootFailures": heavy_failures,
|
|
1025
|
+
"worktreeRegistered": still_registered,
|
|
1026
|
+
"residualPaths": residual_paths,
|
|
1027
|
+
}
|
|
704
1028
|
|
|
705
1029
|
def cleanup(self) -> dict[str, Any]:
|
|
706
1030
|
try:
|
|
@@ -713,9 +1037,23 @@ class IntegrationTransaction:
|
|
|
713
1037
|
raise IntegrationError("cannot cleanup before preflight")
|
|
714
1038
|
|
|
715
1039
|
def action(journal: dict[str, Any]) -> None:
|
|
1040
|
+
self._assert_cleanup_consistency(journal)
|
|
716
1041
|
intg_root = Path(journal["integrationRoot"])
|
|
1042
|
+
cleanup_result: dict[str, Any] = {
|
|
1043
|
+
"ok": True,
|
|
1044
|
+
"code": "ALREADY_ABSENT",
|
|
1045
|
+
"target": str(intg_root.resolve()),
|
|
1046
|
+
"heavyRootsRemoved": [],
|
|
1047
|
+
"heavyRootFailures": [],
|
|
1048
|
+
"worktreeRegistered": False,
|
|
1049
|
+
"residualPaths": [],
|
|
1050
|
+
}
|
|
717
1051
|
if intg_root.exists():
|
|
718
|
-
self.cleanup_target(intg_root)
|
|
1052
|
+
cleanup_result = self.cleanup_target(intg_root)
|
|
1053
|
+
journal["cleanupResult"] = cleanup_result
|
|
1054
|
+
self._save(journal)
|
|
1055
|
+
if not cleanup_result["ok"]:
|
|
1056
|
+
raise CleanupResidualError(cleanup_result)
|
|
719
1057
|
self.runner.run(
|
|
720
1058
|
self.project_root, "branch", "-D", self.temp_branch, check=False
|
|
721
1059
|
)
|
|
@@ -725,6 +1063,49 @@ class IntegrationTransaction:
|
|
|
725
1063
|
self.runner.run(
|
|
726
1064
|
self.project_root, "update-ref", "-d", ref, check=False
|
|
727
1065
|
)
|
|
1066
|
+
worktrees = self.runner.text(
|
|
1067
|
+
self.project_root, "worktree", "list", "--porcelain"
|
|
1068
|
+
) or ""
|
|
1069
|
+
feature_ref = f"refs/heads/{self.feature_branch}"
|
|
1070
|
+
feature_branch_retained = self.runner.run(
|
|
1071
|
+
self.project_root,
|
|
1072
|
+
"show-ref",
|
|
1073
|
+
"--verify",
|
|
1074
|
+
feature_ref,
|
|
1075
|
+
check=False,
|
|
1076
|
+
).returncode == 0
|
|
1077
|
+
local_target = self.runner.text(
|
|
1078
|
+
self.project_root, "rev-parse", self.target_branch
|
|
1079
|
+
)
|
|
1080
|
+
remote_line = self.runner.text(
|
|
1081
|
+
self.project_root, "ls-remote", "origin", self.target_branch
|
|
1082
|
+
)
|
|
1083
|
+
remote_target = (
|
|
1084
|
+
remote_line.split()[0] if remote_line and remote_line.split() else None
|
|
1085
|
+
)
|
|
1086
|
+
journal["cleanupSummary"] = {
|
|
1087
|
+
"transactionArtifactsRemoved": {
|
|
1088
|
+
"integrationWorktree": not intg_root.exists(),
|
|
1089
|
+
"temporaryBranch": self.runner.run(
|
|
1090
|
+
self.project_root,
|
|
1091
|
+
"show-ref",
|
|
1092
|
+
"--verify",
|
|
1093
|
+
f"refs/heads/{self.temp_branch}",
|
|
1094
|
+
check=False,
|
|
1095
|
+
).returncode != 0,
|
|
1096
|
+
"protectionRefs": self._step(journal, "push")["status"] == "DONE",
|
|
1097
|
+
"heavyRoots": cleanup_result.get("heavyRootsRemoved") or [],
|
|
1098
|
+
},
|
|
1099
|
+
"sourceWorktreeRetained": f"branch {feature_ref}" in worktrees,
|
|
1100
|
+
"featureBranchRetained": feature_branch_retained,
|
|
1101
|
+
"primaryWorktreeUpdated": bool(
|
|
1102
|
+
local_target and remote_target and local_target == remote_target
|
|
1103
|
+
),
|
|
1104
|
+
"primaryHead": local_target,
|
|
1105
|
+
"remoteHead": remote_target,
|
|
1106
|
+
"residualPaths": cleanup_result.get("residualPaths") or [],
|
|
1107
|
+
}
|
|
1108
|
+
self._save(journal)
|
|
728
1109
|
|
|
729
1110
|
journal = self._run_step("cleanup", action)
|
|
730
1111
|
harness_change.integration_lock_release(self.project_root, run_id=self.run_id)
|
|
@@ -864,9 +1245,13 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
864
1245
|
try:
|
|
865
1246
|
return int(args.func(args))
|
|
866
1247
|
except IntegrationError as exc:
|
|
1248
|
+
extra = getattr(exc, "result", None)
|
|
1249
|
+
payload = {"ok": False, "code": exc.code, "message": str(exc)}
|
|
1250
|
+
if isinstance(extra, dict):
|
|
1251
|
+
payload["cleanup"] = extra
|
|
867
1252
|
sys.stderr.write(
|
|
868
1253
|
json.dumps(
|
|
869
|
-
|
|
1254
|
+
payload,
|
|
870
1255
|
ensure_ascii=False,
|
|
871
1256
|
)
|
|
872
1257
|
+ "\n"
|