@hunter-harness/workflow-harness 0.2.54 → 0.2.55
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/harness/bundles/general/claude-code/.harness-build.json +1 -1
- package/harness/bundles/general/claude-code/harness-archive/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-plan/SKILL.md +2 -2
- package/harness/bundles/general/claude-code/harness-plan/checklist.md +2 -1
- package/harness/bundles/general/claude-code/harness-review/SKILL.md +6 -4
- package/harness/bundles/general/claude-code/harness-run/SKILL.md +4 -4
- package/harness/bundles/general/claude-code/harness-run/reference.md +2 -2
- package/harness/bundles/general/claude-code/harness-submit/SKILL.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 +5 -3
- package/harness/bundles/general/claude-code/harness-test/reference.md +1 -1
- package/harness/bundles/general/claude-code/scripts/harness_archive.py +46 -7
- package/harness/bundles/general/claude-code/scripts/harness_context.py +71 -22
- package/harness/bundles/general/claude-code/scripts/harness_events.py +40 -1
- package/harness/bundles/general/claude-code/scripts/harness_events_sync.py +93 -14
- package/harness/bundles/general/claude-code/scripts/harness_gate.py +165 -1
- package/harness/bundles/general/claude-code/scripts/harness_ledger.py +2 -0
- package/harness/bundles/general/claude-code/scripts/harness_plan_finalize.py +2 -0
- package/harness/bundles/general/claude-code/scripts/harness_test_guard.py +37 -44
- package/harness/bundles/general/codebuddy/.harness-build.json +1 -1
- package/harness/bundles/general/codebuddy/harness-archive/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-plan/SKILL.md +2 -2
- package/harness/bundles/general/codebuddy/harness-plan/checklist.md +2 -1
- package/harness/bundles/general/codebuddy/harness-review/SKILL.md +6 -4
- package/harness/bundles/general/codebuddy/harness-run/SKILL.md +4 -4
- package/harness/bundles/general/codebuddy/harness-run/reference.md +2 -2
- package/harness/bundles/general/codebuddy/harness-submit/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-test/SKILL.md +5 -3
- package/harness/bundles/general/codebuddy/harness-test/reference.md +1 -1
- package/harness/bundles/general/codebuddy/scripts/harness_archive.py +46 -7
- package/harness/bundles/general/codebuddy/scripts/harness_context.py +71 -22
- package/harness/bundles/general/codebuddy/scripts/harness_events.py +40 -1
- package/harness/bundles/general/codebuddy/scripts/harness_events_sync.py +93 -14
- package/harness/bundles/general/codebuddy/scripts/harness_gate.py +165 -1
- package/harness/bundles/general/codebuddy/scripts/harness_ledger.py +2 -0
- package/harness/bundles/general/codebuddy/scripts/harness_plan_finalize.py +2 -0
- package/harness/bundles/general/codebuddy/scripts/harness_test_guard.py +37 -44
- package/harness/bundles/general/codex/.harness-build.json +1 -1
- package/harness/bundles/general/codex/harness-archive/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-plan/SKILL.md +2 -2
- package/harness/bundles/general/codex/harness-plan/checklist.md +2 -1
- package/harness/bundles/general/codex/harness-review/SKILL.md +3 -3
- package/harness/bundles/general/codex/harness-run/SKILL.md +4 -4
- package/harness/bundles/general/codex/harness-run/reference.md +2 -2
- package/harness/bundles/general/codex/harness-submit/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-test/SKILL.md +5 -3
- package/harness/bundles/general/codex/harness-test/reference.md +1 -1
- package/harness/bundles/general/codex/scripts/harness_archive.py +46 -7
- package/harness/bundles/general/codex/scripts/harness_context.py +71 -22
- package/harness/bundles/general/codex/scripts/harness_events.py +40 -1
- package/harness/bundles/general/codex/scripts/harness_events_sync.py +93 -14
- package/harness/bundles/general/codex/scripts/harness_gate.py +165 -1
- package/harness/bundles/general/codex/scripts/harness_ledger.py +2 -0
- package/harness/bundles/general/codex/scripts/harness_plan_finalize.py +2 -0
- package/harness/bundles/general/codex/scripts/harness_test_guard.py +37 -44
- package/harness/bundles/general/cursor/.harness-build.json +1 -1
- package/harness/bundles/general/cursor/harness-archive/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-plan/SKILL.md +2 -2
- package/harness/bundles/general/cursor/harness-plan/checklist.md +2 -1
- package/harness/bundles/general/cursor/harness-review/SKILL.md +3 -3
- package/harness/bundles/general/cursor/harness-run/SKILL.md +4 -4
- package/harness/bundles/general/cursor/harness-run/reference.md +2 -2
- package/harness/bundles/general/cursor/harness-submit/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-test/SKILL.md +5 -3
- package/harness/bundles/general/cursor/harness-test/reference.md +1 -1
- package/harness/bundles/general/cursor/scripts/harness_archive.py +46 -7
- package/harness/bundles/general/cursor/scripts/harness_context.py +71 -22
- package/harness/bundles/general/cursor/scripts/harness_events.py +40 -1
- package/harness/bundles/general/cursor/scripts/harness_events_sync.py +93 -14
- package/harness/bundles/general/cursor/scripts/harness_gate.py +165 -1
- package/harness/bundles/general/cursor/scripts/harness_ledger.py +2 -0
- package/harness/bundles/general/cursor/scripts/harness_plan_finalize.py +2 -0
- package/harness/bundles/general/cursor/scripts/harness_test_guard.py +37 -44
- package/harness/bundles/java/claude-code/.harness-build.json +1 -1
- package/harness/bundles/java/claude-code/harness-apidoc/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-archive/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-package/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-plan/SKILL.md +2 -2
- package/harness/bundles/java/claude-code/harness-plan/checklist.md +2 -1
- package/harness/bundles/java/claude-code/harness-review/SKILL.md +6 -4
- package/harness/bundles/java/claude-code/harness-run/SKILL.md +4 -4
- package/harness/bundles/java/claude-code/harness-submit/SKILL.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 +5 -3
- package/harness/bundles/java/claude-code/scripts/harness_archive.py +46 -7
- package/harness/bundles/java/claude-code/scripts/harness_context.py +71 -22
- package/harness/bundles/java/claude-code/scripts/harness_events.py +40 -1
- package/harness/bundles/java/claude-code/scripts/harness_events_sync.py +93 -14
- package/harness/bundles/java/claude-code/scripts/harness_gate.py +165 -1
- package/harness/bundles/java/claude-code/scripts/harness_ledger.py +2 -0
- package/harness/bundles/java/claude-code/scripts/harness_plan_finalize.py +2 -0
- package/harness/bundles/java/claude-code/scripts/harness_test_guard.py +37 -44
- package/harness/bundles/java/codebuddy/.harness-build.json +1 -1
- package/harness/bundles/java/codebuddy/harness-apidoc/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-archive/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-package/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-plan/SKILL.md +2 -2
- package/harness/bundles/java/codebuddy/harness-plan/checklist.md +2 -1
- package/harness/bundles/java/codebuddy/harness-review/SKILL.md +6 -4
- package/harness/bundles/java/codebuddy/harness-run/SKILL.md +4 -4
- package/harness/bundles/java/codebuddy/harness-submit/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-test/SKILL.md +5 -3
- package/harness/bundles/java/codebuddy/scripts/harness_archive.py +46 -7
- package/harness/bundles/java/codebuddy/scripts/harness_context.py +71 -22
- package/harness/bundles/java/codebuddy/scripts/harness_events.py +40 -1
- package/harness/bundles/java/codebuddy/scripts/harness_events_sync.py +93 -14
- package/harness/bundles/java/codebuddy/scripts/harness_gate.py +165 -1
- package/harness/bundles/java/codebuddy/scripts/harness_ledger.py +2 -0
- package/harness/bundles/java/codebuddy/scripts/harness_plan_finalize.py +2 -0
- package/harness/bundles/java/codebuddy/scripts/harness_test_guard.py +37 -44
- package/harness/bundles/java/codex/.harness-build.json +1 -1
- package/harness/bundles/java/codex/harness-apidoc/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-archive/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-package/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-plan/SKILL.md +2 -2
- package/harness/bundles/java/codex/harness-plan/checklist.md +2 -1
- package/harness/bundles/java/codex/harness-review/SKILL.md +3 -3
- package/harness/bundles/java/codex/harness-run/SKILL.md +4 -4
- package/harness/bundles/java/codex/harness-submit/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-test/SKILL.md +5 -3
- package/harness/bundles/java/codex/scripts/harness_archive.py +46 -7
- package/harness/bundles/java/codex/scripts/harness_context.py +71 -22
- package/harness/bundles/java/codex/scripts/harness_events.py +40 -1
- package/harness/bundles/java/codex/scripts/harness_events_sync.py +93 -14
- package/harness/bundles/java/codex/scripts/harness_gate.py +165 -1
- package/harness/bundles/java/codex/scripts/harness_ledger.py +2 -0
- package/harness/bundles/java/codex/scripts/harness_plan_finalize.py +2 -0
- package/harness/bundles/java/codex/scripts/harness_test_guard.py +37 -44
- package/harness/bundles/java/cursor/.harness-build.json +1 -1
- package/harness/bundles/java/cursor/harness-apidoc/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-archive/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-package/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-plan/SKILL.md +2 -2
- package/harness/bundles/java/cursor/harness-plan/checklist.md +2 -1
- package/harness/bundles/java/cursor/harness-review/SKILL.md +3 -3
- package/harness/bundles/java/cursor/harness-run/SKILL.md +4 -4
- package/harness/bundles/java/cursor/harness-submit/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-test/SKILL.md +5 -3
- package/harness/bundles/java/cursor/scripts/harness_archive.py +46 -7
- package/harness/bundles/java/cursor/scripts/harness_context.py +71 -22
- package/harness/bundles/java/cursor/scripts/harness_events.py +40 -1
- package/harness/bundles/java/cursor/scripts/harness_events_sync.py +93 -14
- package/harness/bundles/java/cursor/scripts/harness_gate.py +165 -1
- package/harness/bundles/java/cursor/scripts/harness_ledger.py +2 -0
- package/harness/bundles/java/cursor/scripts/harness_plan_finalize.py +2 -0
- package/harness/bundles/java/cursor/scripts/harness_test_guard.py +37 -44
- package/harness/manifests/general/claude-code.json +24 -24
- package/harness/manifests/general/codebuddy.json +24 -24
- package/harness/manifests/general/codex.json +24 -24
- package/harness/manifests/general/cursor.json +24 -24
- package/harness/manifests/java/claude-code.json +24 -24
- package/harness/manifests/java/codebuddy.json +24 -24
- package/harness/manifests/java/codex.json +24 -24
- package/harness/manifests/java/cursor.json +24 -24
- package/hunter-workflow-family.json +5 -4
- package/package.json +1 -1
|
@@ -30,6 +30,7 @@ if str(SCRIPTS_DIR) not in sys.path:
|
|
|
30
30
|
sys.path.insert(0, str(SCRIPTS_DIR))
|
|
31
31
|
|
|
32
32
|
import harness_change as hc # noqa: E402
|
|
33
|
+
import harness_context as hctx # noqa: E402
|
|
33
34
|
import harness_events as he # noqa: E402
|
|
34
35
|
import harness_events_sync as hes # noqa: E402
|
|
35
36
|
import harness_ledger as hl # noqa: E402
|
|
@@ -1602,6 +1603,18 @@ def append_phase_event(
|
|
|
1602
1603
|
auto_sealed: list[dict[str, Any]] = []
|
|
1603
1604
|
with he.event_file_lock(lock_path):
|
|
1604
1605
|
existing = he.load_events(events_file)
|
|
1606
|
+
phase_attempts = he.split_phase_attempts(
|
|
1607
|
+
[event for event in existing if event.get("phase") == phase]
|
|
1608
|
+
)
|
|
1609
|
+
latest_attempt = max(
|
|
1610
|
+
(
|
|
1611
|
+
int(item.get("attempt"))
|
|
1612
|
+
for item in phase_attempts
|
|
1613
|
+
if isinstance(item.get("attempt"), int)
|
|
1614
|
+
),
|
|
1615
|
+
default=0,
|
|
1616
|
+
)
|
|
1617
|
+
event_attempt = latest_attempt + 1 if type_ == "phase.start" else latest_attempt or 1
|
|
1605
1618
|
args = argparse.Namespace(
|
|
1606
1619
|
phase=phase,
|
|
1607
1620
|
type=type_,
|
|
@@ -1619,12 +1632,18 @@ def append_phase_event(
|
|
|
1619
1632
|
decision=None,
|
|
1620
1633
|
reason=None,
|
|
1621
1634
|
run_id=run_id,
|
|
1622
|
-
attempt=
|
|
1635
|
+
attempt=event_attempt,
|
|
1623
1636
|
executor_tool=executor_tool,
|
|
1624
1637
|
executor_agent=executor_agent,
|
|
1625
1638
|
executor_model=executor_model,
|
|
1626
1639
|
handoff_from_tool=None,
|
|
1627
1640
|
handoff_reason=None,
|
|
1641
|
+
execution_mode=None,
|
|
1642
|
+
decision_reason_code=None,
|
|
1643
|
+
fallback_reason_code=None,
|
|
1644
|
+
trigger=("fixback" if "fixback" in note.lower() else None),
|
|
1645
|
+
from_phase=("review" if phase == "run" and "fixback" in note.lower() else None),
|
|
1646
|
+
result_status=None,
|
|
1628
1647
|
)
|
|
1629
1648
|
event = he.build_event(args, existing)
|
|
1630
1649
|
if identity:
|
|
@@ -1655,6 +1674,7 @@ def append_phase_event(
|
|
|
1655
1674
|
content = he.render_execution_log(events)
|
|
1656
1675
|
log_path = he.write_execution_log(change_dir, content)
|
|
1657
1676
|
rendered = True
|
|
1677
|
+
he.nudge_remote_sync(change_dir)
|
|
1658
1678
|
return {
|
|
1659
1679
|
"ok": True,
|
|
1660
1680
|
"eventId": event.get("id"),
|
|
@@ -1702,6 +1722,80 @@ def _phase_event_exists(change_dir: Path, phase: str, type_: str, run_id: str) -
|
|
|
1702
1722
|
)
|
|
1703
1723
|
|
|
1704
1724
|
|
|
1725
|
+
def _latest_phase_end(
|
|
1726
|
+
change_dir: Path,
|
|
1727
|
+
phase: str,
|
|
1728
|
+
run_id: str | None = None,
|
|
1729
|
+
) -> dict[str, Any] | None:
|
|
1730
|
+
for event in reversed(he.load_events(he.events_path(change_dir))):
|
|
1731
|
+
if event.get("phase") != phase or event.get("type") != "phase.end":
|
|
1732
|
+
continue
|
|
1733
|
+
if run_id and event.get("run_id") != run_id:
|
|
1734
|
+
continue
|
|
1735
|
+
return event
|
|
1736
|
+
return None
|
|
1737
|
+
|
|
1738
|
+
|
|
1739
|
+
def _terminal_matches_context_session(
|
|
1740
|
+
project: Path,
|
|
1741
|
+
change_id: str,
|
|
1742
|
+
phase: str,
|
|
1743
|
+
terminal: dict[str, Any],
|
|
1744
|
+
) -> bool:
|
|
1745
|
+
view = hctx.context_view(project, change_id)
|
|
1746
|
+
current = view.get("current") if isinstance(view, dict) else None
|
|
1747
|
+
if not isinstance(current, dict) or current.get("phase") != phase:
|
|
1748
|
+
return False
|
|
1749
|
+
prepared_at = he.parse_timestamp(current.get("preparedAt"))
|
|
1750
|
+
terminal_at = he.parse_timestamp(
|
|
1751
|
+
terminal.get("timestamp") or terminal.get("occurred_at")
|
|
1752
|
+
)
|
|
1753
|
+
return (
|
|
1754
|
+
prepared_at is not None
|
|
1755
|
+
and terminal_at is not None
|
|
1756
|
+
and terminal_at >= prepared_at
|
|
1757
|
+
)
|
|
1758
|
+
|
|
1759
|
+
|
|
1760
|
+
def _close_context_handoff(
|
|
1761
|
+
project: Path,
|
|
1762
|
+
change_id: str,
|
|
1763
|
+
args: argparse.Namespace,
|
|
1764
|
+
*,
|
|
1765
|
+
status: str,
|
|
1766
|
+
) -> dict[str, Any] | None:
|
|
1767
|
+
to_phase = getattr(args, "to_phase", None)
|
|
1768
|
+
if not to_phase:
|
|
1769
|
+
return None
|
|
1770
|
+
try:
|
|
1771
|
+
return hctx.close_transition(
|
|
1772
|
+
project,
|
|
1773
|
+
change_id,
|
|
1774
|
+
from_phase=args.phase,
|
|
1775
|
+
to_phase=str(to_phase),
|
|
1776
|
+
executor=getattr(args, "executor", None),
|
|
1777
|
+
artifacts=list(getattr(args, "artifact", None) or []),
|
|
1778
|
+
status=status,
|
|
1779
|
+
)
|
|
1780
|
+
except Exception as exc: # noqa: BLE001 — phase close remains locally durable
|
|
1781
|
+
return {
|
|
1782
|
+
"ok": False,
|
|
1783
|
+
"code": "CONTEXT_HANDOFF_FAILED",
|
|
1784
|
+
"message": str(exc),
|
|
1785
|
+
}
|
|
1786
|
+
|
|
1787
|
+
|
|
1788
|
+
def _sync_after_phase_close(project: Path, change_dir: Path) -> dict[str, Any]:
|
|
1789
|
+
try:
|
|
1790
|
+
return hes.auto_events_sync(project, change_dir)
|
|
1791
|
+
except Exception as exc: # noqa: BLE001 — remote sync is recoverable
|
|
1792
|
+
return {
|
|
1793
|
+
"ok": False,
|
|
1794
|
+
"code": "PLATFORM_EVENTS_SYNC_WARN",
|
|
1795
|
+
"warning": f"events-sync hook failed: {exc}",
|
|
1796
|
+
}
|
|
1797
|
+
|
|
1798
|
+
|
|
1705
1799
|
def _validate_scenario_coverage(change_dir: Path) -> dict[str, Any]:
|
|
1706
1800
|
"""C9: validate all ledger-required scenarios are covered by ledger entries.
|
|
1707
1801
|
|
|
@@ -2266,6 +2360,48 @@ def cmd_close(args: argparse.Namespace) -> int:
|
|
|
2266
2360
|
explicit_run_id = args.run_id or os.environ.get("HUNTER_HARNESS_RUN_ID")
|
|
2267
2361
|
current_lease = hc.inspect_lease(project, resolved["changeId"])
|
|
2268
2362
|
if current_lease is None:
|
|
2363
|
+
terminal = _latest_phase_end(change_dir, args.phase, explicit_run_id)
|
|
2364
|
+
if (
|
|
2365
|
+
getattr(args, "to_phase", None)
|
|
2366
|
+
and terminal is not None
|
|
2367
|
+
and _terminal_matches_context_session(
|
|
2368
|
+
project, str(resolved["changeId"]), args.phase, terminal
|
|
2369
|
+
)
|
|
2370
|
+
):
|
|
2371
|
+
terminal_status = str(terminal.get("status") or args.status)
|
|
2372
|
+
handoff = _close_context_handoff(
|
|
2373
|
+
project,
|
|
2374
|
+
str(resolved["changeId"]),
|
|
2375
|
+
args,
|
|
2376
|
+
status=terminal_status,
|
|
2377
|
+
)
|
|
2378
|
+
monitor = _sync_after_phase_close(project, change_dir)
|
|
2379
|
+
if not isinstance(handoff, dict) or not handoff.get("ok"):
|
|
2380
|
+
return emit_error(
|
|
2381
|
+
"PHASE_HANDOFF_PENDING",
|
|
2382
|
+
"phase gate is closed, but context handoff still needs attention",
|
|
2383
|
+
as_json=as_json,
|
|
2384
|
+
extra={
|
|
2385
|
+
"localCloseComplete": True,
|
|
2386
|
+
"retryable": True,
|
|
2387
|
+
"contextHandoff": handoff,
|
|
2388
|
+
"platformMonitor": monitor,
|
|
2389
|
+
},
|
|
2390
|
+
)
|
|
2391
|
+
emit(
|
|
2392
|
+
{
|
|
2393
|
+
"ok": True,
|
|
2394
|
+
"code": "PHASE_CLOSE_RESUMED",
|
|
2395
|
+
"phase": args.phase,
|
|
2396
|
+
"status": terminal_status,
|
|
2397
|
+
"changeId": resolved["changeId"],
|
|
2398
|
+
"localCloseComplete": True,
|
|
2399
|
+
"contextHandoff": handoff,
|
|
2400
|
+
"platformMonitor": monitor,
|
|
2401
|
+
},
|
|
2402
|
+
as_json=as_json,
|
|
2403
|
+
)
|
|
2404
|
+
return 0
|
|
2269
2405
|
return emit_error("LEASE_ABSENT", "no active lease for phase close", as_json=as_json)
|
|
2270
2406
|
run_id = explicit_run_id or str(current_lease.get("runId") or "")
|
|
2271
2407
|
if str(current_lease.get("runId")) != run_id or str(current_lease.get("phase")) != args.phase:
|
|
@@ -2597,6 +2733,29 @@ def cmd_close(args: argparse.Namespace) -> int:
|
|
|
2597
2733
|
capsule["closeStatus"] = close_status
|
|
2598
2734
|
write_phase_capsule(change_dir, args.phase, run_id, capsule)
|
|
2599
2735
|
|
|
2736
|
+
context_handoff = _close_context_handoff(
|
|
2737
|
+
project,
|
|
2738
|
+
str(resolved["changeId"]),
|
|
2739
|
+
args,
|
|
2740
|
+
status=close_status,
|
|
2741
|
+
)
|
|
2742
|
+
platform_monitor = _sync_after_phase_close(project, change_dir)
|
|
2743
|
+
if isinstance(context_handoff, dict) and not context_handoff.get("ok"):
|
|
2744
|
+
return emit_error(
|
|
2745
|
+
"PHASE_HANDOFF_PENDING",
|
|
2746
|
+
"phase gate closed locally, but context handoff failed; retry the same close command",
|
|
2747
|
+
as_json=as_json,
|
|
2748
|
+
extra={
|
|
2749
|
+
"localCloseComplete": True,
|
|
2750
|
+
"retryable": True,
|
|
2751
|
+
"phase": args.phase,
|
|
2752
|
+
"status": close_status,
|
|
2753
|
+
"changeId": resolved["changeId"],
|
|
2754
|
+
"contextHandoff": context_handoff,
|
|
2755
|
+
"platformMonitor": platform_monitor,
|
|
2756
|
+
},
|
|
2757
|
+
)
|
|
2758
|
+
|
|
2600
2759
|
payload = {
|
|
2601
2760
|
"ok": True,
|
|
2602
2761
|
"code": close_code,
|
|
@@ -2612,6 +2771,8 @@ def cmd_close(args: argparse.Namespace) -> int:
|
|
|
2612
2771
|
"lease": release,
|
|
2613
2772
|
"gateSeverityMode": severity_mode,
|
|
2614
2773
|
"gateWarnings": gate_warnings,
|
|
2774
|
+
"contextHandoff": context_handoff,
|
|
2775
|
+
"platformMonitor": platform_monitor,
|
|
2615
2776
|
}
|
|
2616
2777
|
emit(payload, as_json=as_json)
|
|
2617
2778
|
return 0
|
|
@@ -2806,6 +2967,9 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
2806
2967
|
p_close.add_argument("--run-id", default=None)
|
|
2807
2968
|
p_close.add_argument("--task", type=int, default=None)
|
|
2808
2969
|
p_close.add_argument("--note", default="")
|
|
2970
|
+
p_close.add_argument("--to-phase", default=None)
|
|
2971
|
+
p_close.add_argument("--executor", default=None)
|
|
2972
|
+
p_close.add_argument("--artifact", action="append", default=[])
|
|
2809
2973
|
p_close.set_defaults(func=cmd_close)
|
|
2810
2974
|
|
|
2811
2975
|
p_classify = sub.add_parser("classify", parents=[shared])
|
|
@@ -1160,6 +1160,8 @@ _DYNAMIC_OWN_DIRS = ("events.ndjson", "logs", "evidence", "reports", "runtime",
|
|
|
1160
1160
|
|
|
1161
1161
|
def _matches_ownership_path(rel: str, declared: Any) -> bool:
|
|
1162
1162
|
scope = str(declared).replace("\\", "/").strip("/")
|
|
1163
|
+
if scope.endswith("/**"):
|
|
1164
|
+
scope = scope[:-3].rstrip("/")
|
|
1163
1165
|
if not scope:
|
|
1164
1166
|
return False
|
|
1165
1167
|
normalized = rel.replace("\\", "/").strip("/")
|
|
@@ -1155,6 +1155,7 @@ def finalize_plan(
|
|
|
1155
1155
|
"artifactsHash": validation["artifactsHash"],
|
|
1156
1156
|
"files": validation["files"],
|
|
1157
1157
|
"receiptPath": str(receipt_path),
|
|
1158
|
+
"artifactRef": "meta/plan-finalization.json",
|
|
1158
1159
|
}
|
|
1159
1160
|
|
|
1160
1161
|
lock_path.parent.mkdir(parents=True, exist_ok=True)
|
|
@@ -1253,6 +1254,7 @@ def finalize_plan(
|
|
|
1253
1254
|
"artifactsHash": validation["artifactsHash"],
|
|
1254
1255
|
"files": validation["files"],
|
|
1255
1256
|
"receiptPath": str(receipt_path),
|
|
1257
|
+
"artifactRef": "meta/plan-finalization.json",
|
|
1256
1258
|
"executionLogPath": str(harness_events.execution_log_path(change_dir)),
|
|
1257
1259
|
}
|
|
1258
1260
|
except OSError as exc:
|
|
@@ -1593,18 +1593,15 @@ def close(project: Path | str, change_dir: Path | str) -> dict[str, Any]:
|
|
|
1593
1593
|
[],
|
|
1594
1594
|
error=str(exc),
|
|
1595
1595
|
)
|
|
1596
|
-
|
|
1596
|
+
profile_changed = bool(
|
|
1597
1597
|
isinstance(snapshot.get("profileFingerprint"), str)
|
|
1598
1598
|
and snapshot["profileFingerprint"] != current_profile_fingerprint
|
|
1599
|
-
)
|
|
1600
|
-
return _result(False, action, "PROFILE_CHANGED", [])
|
|
1599
|
+
)
|
|
1601
1600
|
baseline_commit = _snapshot_baseline_commit(
|
|
1602
1601
|
project_root,
|
|
1603
1602
|
change_root,
|
|
1604
1603
|
snapshot,
|
|
1605
1604
|
)
|
|
1606
|
-
profile_changed = False
|
|
1607
|
-
|
|
1608
1605
|
before_entries = snapshot.get("files")
|
|
1609
1606
|
if not isinstance(before_entries, list):
|
|
1610
1607
|
return _result(False, action, "SNAPSHOT_INVALID", [])
|
|
@@ -1682,11 +1679,21 @@ def close(project: Path | str, change_dir: Path | str) -> dict[str, Any]:
|
|
|
1682
1679
|
return result
|
|
1683
1680
|
|
|
1684
1681
|
recorded = [rel for rel, _ in touched]
|
|
1685
|
-
reconciliation =
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1682
|
+
reconciliation = (
|
|
1683
|
+
{
|
|
1684
|
+
"ok": True,
|
|
1685
|
+
"code": "PROFILE_REBASED_MANIFEST_PRESERVED",
|
|
1686
|
+
"removedCurrentTouches": 0,
|
|
1687
|
+
"deletedEntries": 0,
|
|
1688
|
+
"preservedForeignEntries": 0,
|
|
1689
|
+
}
|
|
1690
|
+
if profile_changed
|
|
1691
|
+
else _reconcile_close_manifest(
|
|
1692
|
+
project_root,
|
|
1693
|
+
change_root,
|
|
1694
|
+
set(recorded),
|
|
1695
|
+
baseline_commit,
|
|
1696
|
+
)
|
|
1690
1697
|
)
|
|
1691
1698
|
if not reconciliation.get("ok"):
|
|
1692
1699
|
return _result(
|
|
@@ -1696,40 +1703,26 @@ def close(project: Path | str, change_dir: Path | str) -> dict[str, Any]:
|
|
|
1696
1703
|
list(reconciliation.get("files") or []),
|
|
1697
1704
|
)
|
|
1698
1705
|
|
|
1699
|
-
#
|
|
1700
|
-
#
|
|
1701
|
-
#
|
|
1702
|
-
|
|
1703
|
-
if
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
or f.get("introducedBy") == change_id
|
|
1720
|
-
or change_id in (f.get("touchedBy") or [])
|
|
1721
|
-
)
|
|
1722
|
-
]
|
|
1723
|
-
if active_entries and not recorded:
|
|
1724
|
-
return _result(
|
|
1725
|
-
False,
|
|
1726
|
-
action,
|
|
1727
|
-
"MANIFEST_DIFF_HASH_MISMATCH",
|
|
1728
|
-
[],
|
|
1729
|
-
manifestEntries=len(active_entries),
|
|
1730
|
-
recordedCount=0,
|
|
1731
|
-
detail="manifest has active entries but close computed 0 recorded tests",
|
|
1732
|
-
)
|
|
1706
|
+
# A manifest is change-wide and intentionally survives phase boundaries.
|
|
1707
|
+
# Therefore pre-existing entries with no new diff are consistent and must
|
|
1708
|
+
# not force a test-guard reset. When the build profile changes mid-phase,
|
|
1709
|
+
# retain the diff result and rebaseline the reusable snapshot atomically.
|
|
1710
|
+
if profile_changed:
|
|
1711
|
+
snapshot.update(
|
|
1712
|
+
{
|
|
1713
|
+
"profileFingerprint": current_profile_fingerprint,
|
|
1714
|
+
"capturedAt": time.strftime("%Y-%m-%dT%H:%M:%S"),
|
|
1715
|
+
"files": [
|
|
1716
|
+
{
|
|
1717
|
+
"path": rel,
|
|
1718
|
+
"sha256": digest,
|
|
1719
|
+
"ignored": _is_ignored(project_root, rel),
|
|
1720
|
+
}
|
|
1721
|
+
for rel, digest in sorted(current.items())
|
|
1722
|
+
],
|
|
1723
|
+
}
|
|
1724
|
+
)
|
|
1725
|
+
_write_json(snapshot_path, snapshot)
|
|
1733
1726
|
|
|
1734
1727
|
return _result(
|
|
1735
1728
|
True,
|
|
@@ -3,7 +3,7 @@ name: harness-archive
|
|
|
3
3
|
description: 归档所有变更产出(计划/测试报告/审查/SQL/API文档)到 .harness/archive/,含归档元数据。仅当用户显式调用
|
|
4
4
|
/harness-archive 时使用;不得被其他阶段 skill 自动接续触发。
|
|
5
5
|
---
|
|
6
|
-
<!-- generated by harness_deploy.py; core=
|
|
6
|
+
<!-- generated by harness_deploy.py; core=b3735c257cbec999; overlay=none; agent=codex; do not edit -->
|
|
7
7
|
# harness-archive — 变更归档
|
|
8
8
|
|
|
9
9
|
## Purpose
|
|
@@ -3,7 +3,7 @@ name: harness-codebase-map
|
|
|
3
3
|
description: 分析现有代码库并生成 .harness/codebase/map/
|
|
4
4
|
下的结构化代码库地图。用于陌生项目理解、重大改造前梳理、harness-sync 提示 map 过期后的人工刷新。
|
|
5
5
|
---
|
|
6
|
-
<!-- generated by harness_deploy.py; core=
|
|
6
|
+
<!-- generated by harness_deploy.py; core=b3735c257cbec999; overlay=none; agent=codex; do not edit -->
|
|
7
7
|
# harness-codebase-map — 代码库地图
|
|
8
8
|
|
|
9
9
|
## Purpose
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: harness-knowledge-ingest
|
|
3
3
|
description: 确认归档 ZIP 已上传并由 Hunter Platform 在服务端解包、校验和入库。客户端不再构建或维护本地知识索引。
|
|
4
4
|
---
|
|
5
|
-
<!-- generated by harness_deploy.py; core=
|
|
5
|
+
<!-- generated by harness_deploy.py; core=b3735c257cbec999; overlay=none; agent=codex; do not edit -->
|
|
6
6
|
# harness-knowledge-ingest
|
|
7
7
|
|
|
8
8
|
知识 ingest 完全由 Hunter Platform 负责。归档完成时,客户端生成一个确定性 ZIP;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: harness-knowledge-query
|
|
3
3
|
description: 在规划、实现或排查前,通过 hunter-harness CLI 查询远端项目知识库。远端不可用时直接报告不可用,不建立本地索引或离线回退。
|
|
4
4
|
---
|
|
5
|
-
<!-- generated by harness_deploy.py; core=
|
|
5
|
+
<!-- generated by harness_deploy.py; core=b3735c257cbec999; overlay=none; agent=codex; do not edit -->
|
|
6
6
|
# harness-knowledge-query
|
|
7
7
|
|
|
8
8
|
项目知识以 Hunter Platform 的服务端索引为唯一真源。客户端只提交查询并消费结果:
|
|
@@ -3,7 +3,7 @@ name: harness-plan
|
|
|
3
3
|
description: 将需求转化为设计文档+实施计划+完整测试场景表,必须在编码前完成。仅当用户显式调用 /harness-plan 或明确要求进入
|
|
4
4
|
Harness 规划阶段时使用;不得因用户描述需求就自动触发。
|
|
5
5
|
---
|
|
6
|
-
<!-- generated by harness_deploy.py; core=
|
|
6
|
+
<!-- generated by harness_deploy.py; core=b3735c257cbec999; overlay=none; agent=codex; do not edit -->
|
|
7
7
|
# harness-plan — 需求规划
|
|
8
8
|
|
|
9
9
|
## Purpose
|
|
@@ -73,7 +73,7 @@ description: 将需求转化为设计文档+实施计划+完整测试场景表
|
|
|
73
73
|
| 4 | **设计审批包** blocking user confirmation;确认事件早于 approved 设计文档和 `meta/worktree.json` |
|
|
74
74
|
| 5–6 | plan + implementation-detail + test-scenarios → `plans/` |
|
|
75
75
|
| 7.5 | 仅 `--adversarial` 对抗评审 |
|
|
76
|
-
| 8 | 在临时产物集上运行 `harness_plan_finalize.py finalize`,随后立即运行 `verify
|
|
76
|
+
| 8 | 在临时产物集上运行 `harness_plan_finalize.py finalize`,随后立即运行 `verify`;成功后把 finalizer 返回的绝对 `receiptPath` 原样传给 `harness_context.py close --from-phase plan --to-phase run --executor <tool> --artifact "<receiptPath>" --json`(也可使用返回的稳定 `artifactRef=meta/plan-finalization.json`),写 append-only handoff receipt;不得手写占位路径;原子发布、派生清单计数对账、完整生命周期、render → `checklist.md` |
|
|
77
77
|
|
|
78
78
|
change-name 范围变更 → 提示重命名或记 🟡WARN(→ `reference.md`)
|
|
79
79
|
|
|
@@ -211,13 +211,14 @@ source: harness-plan
|
|
|
211
211
|
|
|
212
212
|
- [ ] 所有待发布产物先写入 staging,不直接覆盖正式 change 目录
|
|
213
213
|
- [ ] 执行 `harness_plan_finalize.py finalize --change-dir ... --staging-dir ... --change ... --run-id <plan-run-id> --attempt <attempt> --json`;身份必须与本次 `phase.start` 完全相同
|
|
214
|
-
- [ ] finalizer 返回 `ok=true`
|
|
214
|
+
- [ ] finalizer 返回 `ok=true`、`artifactsHash`、绝对 `receiptPath` 与稳定 `artifactRef=meta/plan-finalization.json`;重复执行返回 `idempotent=true`
|
|
215
215
|
- [ ] 紧接着执行 `harness_plan_finalize.py verify --change-dir ... --json`
|
|
216
216
|
- [ ] verify 返回 `phaseStartCount=1`、`phaseEndCount=1`、`phaseEndStatus=OK`、`receiptConsistent=true`
|
|
217
217
|
- [ ] 收据 `files` 完整包含 design / plan / implementation-detail / test-scenarios / gate-policy / worktree 六项标准产物;不得省略、重复或经 symlink/junction/reparse point 引用
|
|
218
218
|
- [ ] verify 的 `taskCount` 等于全部任务表行数,`scenarioCount` 等于 Markdown 中全部唯一场景 ID 数;任一为 0 或不一致即 ❌FAIL
|
|
219
219
|
- [ ] finalizer 失败时正式目录无半发布产物、无成功 `phase.end`、无伪造 execution log
|
|
220
220
|
- [ ] 禁止在 finalizer 之前手工追加成功 `phase.end`
|
|
221
|
+
- [ ] context close 的 `--artifact` 只传 finalizer 原样返回的 `receiptPath` 或 `artifactRef`,不得猜测、拼接或使用 `<plan-finalization>` 占位文本
|
|
221
222
|
|
|
222
223
|
| 文件 | 必须存在 | 检查结果 |
|
|
223
224
|
|------|:---:|:---:|
|
|
@@ -4,7 +4,7 @@ description: 6维度代码审查(架构/安全/规范/兼容/测试/性能)
|
|
|
4
4
|
.harness/context-index.json)和测试场景表,在隔离上下文运行。仅当用户显式调用 /harness-review 时使用;不得在
|
|
5
5
|
test 结束后自动接续执行。
|
|
6
6
|
---
|
|
7
|
-
<!-- generated by harness_deploy.py; core=
|
|
7
|
+
<!-- generated by harness_deploy.py; core=b3735c257cbec999; overlay=none; agent=codex; do not edit -->
|
|
8
8
|
# harness-review — 代码审查
|
|
9
9
|
|
|
10
10
|
## Purpose
|
|
@@ -45,14 +45,14 @@ description: 6维度代码审查(架构/安全/规范/兼容/测试/性能)
|
|
|
45
45
|
|
|
46
46
|
## Workflow
|
|
47
47
|
|
|
48
|
-
先用 `harness_context.py prepare --phase review --executor <tool> [--change <id>] --json` 与 `harness_context.py begin --phase review --change <id> --executor <tool> --json` 解析唯一 active change 并校验 test→review receipt;多个 active change 未显式选择时返回 `ACTIVE_CHANGE_AMBIGUOUS`,禁止再按 Glob/mtime 猜测。阶段门禁使用 `harness_gate.py begin --phase review --change <id> --task <n> --skills-root <skills-root> --executor-tool <tool> --json
|
|
48
|
+
先用 `harness_context.py prepare --phase review --executor <tool> [--change <id>] --json` 与 `harness_context.py begin --phase review --change <id> --executor <tool> --json` 解析唯一 active change 并校验 test→review receipt;多个 active change 未显式选择时返回 `ACTIVE_CHANGE_AMBIGUOUS`,禁止再按 Glob/mtime 猜测。阶段门禁使用 `harness_gate.py begin --phase review --change <id> --task <n> --skills-root <skills-root> --executor-tool <tool> --json`;结束时只执行一次 `harness_gate.py close --phase review --change <id> --status <OK|WARN|FAIL> --run-id <begin-run-id> --to-phase submit|run --executor <tool> --json`,由 gate 完成阶段事件、租约、上下文交接与远端补传。不得手工追加 `phase.start/phase.end` 或再次调用 context close;任一 close 失败不得宣称审查完成。
|
|
49
49
|
|
|
50
50
|
0. **启动准备** — 确定变更名(Glob `.harness/changes/*/plans/*-plan.md`,排除 `.harness/archive/*/`,读 frontmatter 提取 change-name);**append `phase.start` 事件**(不得等审查完成才补)
|
|
51
51
|
1. **读取 worktree 状态(门禁检查)** — 读 `.harness/changes/<change-name>/meta/worktree.json`:`requested=true` 但 worktree 不存在 → 停止并提示先修复 `harness-run`,不得静默回主目录(否则 git diff 为空);`requested=true` 且 worktree 已创建 → spawned agent 用该 worktree 路径执行 `git diff`(确保审查 worktree 变更而非主目录);`requested=false` → 审查主目录变更
|
|
52
52
|
<!-- @section-id review.delegate -->
|
|
53
53
|
### 2. 审查执行(主会话优先)
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
Codex 提供通用隔离任务能力时,默认优先临时委派一次只读审查,**不要求安装固定 `harness-reviewer`**。子任务只读取 diff、规则和测试证据;主会话核验并写报告。无法委派、spawn 失败、空返回或无报告正文 → 不 retry,继续主会话审查。每次都记一条 `decision`,用 `REVIEW_DELEGATED` 或相应 `REVIEW_INLINE_*` 原因码说明是否委派及原因;正常 inline 不显示为故障。
|
|
56
56
|
## Review 定位(重要)
|
|
57
57
|
|
|
58
58
|
**harness-review 是参考性代码审查阶段,不是硬门禁。**
|
|
@@ -3,7 +3,7 @@ name: harness-run
|
|
|
3
3
|
description: 按变更簇执行 TDD 编码循环(RED→GREEN→REFACTOR→编译验证),逐变更簇实现计划中的任务。仅当用户显式调用
|
|
4
4
|
/harness-run 时使用;不得因用户提到编码/实现就自动触发,也不得被其他阶段 skill 自动接续。
|
|
5
5
|
---
|
|
6
|
-
<!-- generated by harness_deploy.py; core=
|
|
6
|
+
<!-- generated by harness_deploy.py; core=b3735c257cbec999; overlay=none; agent=codex; do not edit -->
|
|
7
7
|
# harness-run — 需求编码
|
|
8
8
|
|
|
9
9
|
## Purpose
|
|
@@ -60,11 +60,11 @@ description: 按变更簇执行 TDD 编码循环(RED→GREEN→REFACTOR→编
|
|
|
60
60
|
## Workflow 概要
|
|
61
61
|
|
|
62
62
|
0. 加载上下文:先 `harness_context.py prepare --phase run --executor <tool> [--change <id>] --json`,以返回的唯一 change/executionRoot/transition receipt 为准(多 active 缺参 → `ACTIVE_CHANGE_AMBIGUOUS`,禁止按 mtime 猜测);再 **`harness_context.py begin --phase run --change <id> --executor <tool> --json`** 校验 Plan artifact/hash/HEAD,读 spec/plan/detail/scenarios/ledger/run-task-status/worktree;`--fixback` 读 fixback → **`harness_gate.py begin --phase run --change <id>`**(claim + phase.start + identity;禁止手工 Write `events.ndjson` / 手工 `phase.end`)。**C3 钩子**:`harness_gate.py begin` 在已连接平台时会 best-effort 调用 events-sync(注册 run / `running`);失败只告警,不阻断 begin。
|
|
63
|
-
0.5. **测试基础设施探测**(先写 `CHECKING`,四项证据齐备后再结论)→ `reference.md` Step 0.5
|
|
63
|
+
0.5. **测试基础设施探测**(先写 `CHECKING`,四项证据齐备后再结论)→ `reference.md` Step 0.5;测试基线已由上一步 gate begin 内部建立,不得再次执行 guard begin
|
|
64
64
|
1. **变更簇 TDD** — `protocols.md` `run-tdd-protocol`;批量 RED/GREEN;按需 `change-cluster-review-protocol`(高风险 + reviewer 预检可用)
|
|
65
65
|
2. 构建验证 + **仅**通过 `harness_ledger.py record` 写 ledger(禁止 Write/Edit `verification-ledger.json`);`diff-hash --change-dir` 纳入 ignored tests → `reference.md` Step 2c
|
|
66
66
|
3. **场景覆盖检查**(场景表映射,禁止用用例数冒充场景数)
|
|
67
|
-
4. **关门检查**(10 项)→
|
|
67
|
+
4. **关门检查**(10 项)→ 只执行一次 **`harness_gate.py close --phase run --change <id> --status <OK|WARN|FAIL> --to-phase test --executor <tool> --json`**。该命令内部依次关闭 test guard、写 `phase.end`、释放阶段租约、写 append-only handoff,并立即补传远端事件;不得再单独调用 test-guard/context close。若返回 `PHASE_HANDOFF_PENDING`,修正明确指出的输入后原样重试同一命令,已完成步骤会幂等复用。
|
|
68
68
|
|
|
69
69
|
**Fixback**:`--fixback` 或用户要求时读 `reports/review/fixback-*.md`;RED 优先;未选用则记 `fixback: advisory-not-applied`。
|
|
70
70
|
|
|
@@ -93,7 +93,7 @@ description: 按变更簇执行 TDD 编码循环(RED→GREEN→REFACTOR→编
|
|
|
93
93
|
| **RED/GREEN** | RED 须有效;静态验证 ≠ 测试通过;greenfield 大重写豁免见 reference |
|
|
94
94
|
| **Mapper/DB** | 纯 Mock 不得宣称 DB 验证通过;迁移脚本**永不自动执行** |
|
|
95
95
|
| **探测/ledger** | 基础设施先探测;每次构建/测试经 `harness_ledger.py record`;禁止手写 ledger JSON;用 canonical `diff-hash --change-dir` |
|
|
96
|
-
| **Gate/Guard** | 跨 Agent/阶段先用 `harness_context.py prepare/begin
|
|
96
|
+
| **Gate/Guard** | 跨 Agent/阶段先用 `harness_context.py prepare/begin`;阶段门禁与上下文交接统一用 `harness_gate.py begin/close`;gate 内部负责 guard begin/close,模型只在需要记录测试来源或修复时调用 guard 的 `record/stage/mark` |
|
|
97
97
|
| **预存变更** | 保留 → baseline 隔离;存在则最终 ≥ 🟡WARN |
|
|
98
98
|
| **关门/状态** | 10 项关门检查;持久化 run-task-status;P0 静态-only 不得建议 submit |
|
|
99
99
|
| **Worktree** | `requested=true` 时代码只写 worktree |
|
|
@@ -540,7 +540,7 @@ python <skills-root>/scripts/harness_ledger.py record --change-dir ".harness/cha
|
|
|
540
540
|
>
|
|
541
541
|
> **Ledger v3**:`record` 自动解析并强制顶层身份(`schemaVersion=3/repositoryId/changeName/baseCommit/currentHead/diffHash/ownershipHash`),缺字段非零退出、不写账本;已有 v1/v2 ledger 在写前透明迁移,`migrationHistory` 回执记录原/目标 schema 与迁移前后 evidence identity。无法迁移时返回 `LEDGER_MIGRATION_REQUIRED` 和确定性 `rerecordCommand`,不得半升级。v2 契约下 `--metrics-json` 必须通过 typed schema(unit=`total/passed/failed`,apiContract=`scenariosTotal/passed/failed`,browserE2E=`total/passed/failed`,dbCompatibility=`applicability(+reason)`)。详见 `../protocols/ledger-protocol.md` 第十节。
|
|
542
542
|
|
|
543
|
-
|
|
543
|
+
阶段边界与测试跟踪统一由 `harness_gate.py begin/close` 协调;底层 guard begin/close 不再由模型重复调用。close 失败不得用自然语言覆盖。
|
|
544
544
|
|
|
545
545
|
## 步骤 3:场景覆盖检查
|
|
546
546
|
|
|
@@ -800,7 +800,7 @@ powershell.exe -Command "git -C '<project-path>' diff --check"
|
|
|
800
800
|
```powershell
|
|
801
801
|
# gate begin/close(--task 仅在该 change 启用 checkpoint 时必需;close 不需要 --skills-root)
|
|
802
802
|
python <skills-root>/scripts/harness_gate.py begin --change <cn> --phase run --skills-root <skills-root> [--task N]
|
|
803
|
-
python <skills-root>/scripts/harness_gate.py close --change <cn> --phase run --status OK [--task N]
|
|
803
|
+
python <skills-root>/scripts/harness_gate.py close --change <cn> --phase run --status OK --to-phase test --executor <tool> [--task N]
|
|
804
804
|
|
|
805
805
|
# ledger 记录(status: ok|fail|not_run —— 没有 PASS)
|
|
806
806
|
python <skills-root>/scripts/harness_ledger.py record --change-dir <dir> --verification unitTestFull --status ok --command "<完整命令>" --exit-code 0 --duration-ms 120000 --evidence "Tests run: 155, Failures: 0, Errors: 0, Skipped: 0" --coverage full --files "packages/core/src/index.ts"
|
|
@@ -3,7 +3,7 @@ name: harness-submit
|
|
|
3
3
|
description: 最终提交封装:验证→中文 commit→提交/推送;worktree 模式含 --no-ff 合并回主分支。仅当用户显式调用
|
|
4
4
|
/harness-submit(或 /harness-merge 重入合并段)时使用;用户口头说'提交/commit/push'时必须先确认,不得自动触发。
|
|
5
5
|
---
|
|
6
|
-
<!-- generated by harness_deploy.py; core=
|
|
6
|
+
<!-- generated by harness_deploy.py; core=b3735c257cbec999; overlay=none; agent=codex; do not edit -->
|
|
7
7
|
# harness-submit — 最终提交(含 worktree 合并)
|
|
8
8
|
|
|
9
9
|
## Purpose
|
|
@@ -4,7 +4,7 @@ description: Use when the user asks to synchronize, refresh, or validate Harness
|
|
|
4
4
|
metadata, adapters, remote knowledge ownership, instruction entrypoints,
|
|
5
5
|
config origins, or CodeGraph status.
|
|
6
6
|
---
|
|
7
|
-
<!-- generated by harness_deploy.py; core=
|
|
7
|
+
<!-- generated by harness_deploy.py; core=b3735c257cbec999; overlay=none; agent=codex; do not edit -->
|
|
8
8
|
# harness-sync
|
|
9
9
|
|
|
10
10
|
## Purpose
|
|
@@ -3,7 +3,7 @@ name: harness-test
|
|
|
3
3
|
description: 测试执行:读取场景表,执行单元测试+API接口测试+数据兼容验证,输出测试报告。仅当用户显式调用 /harness-test
|
|
4
4
|
时使用;不得在 run 结束后自动接续执行。
|
|
5
5
|
---
|
|
6
|
-
<!-- generated by harness_deploy.py; core=
|
|
6
|
+
<!-- generated by harness_deploy.py; core=b3735c257cbec999; overlay=none; agent=codex; do not edit -->
|
|
7
7
|
# harness-test — 测试执行
|
|
8
8
|
|
|
9
9
|
## Purpose
|
|
@@ -46,7 +46,9 @@ description: 测试执行:读取场景表,执行单元测试+API接口测试
|
|
|
46
46
|
|
|
47
47
|
## Workflow
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
测试跟踪由 `harness_gate.py begin/close` 统一协调 test guard;不得额外手工执行 guard close。任一失败都必须使用 gate 返回的结构化状态,不得以自然语言覆盖失败状态。
|
|
50
|
+
|
|
51
|
+
兼容实现中的底层命令名是 `harness_test_guard.py begin` 与 `harness_test_guard.py close`,两者只允许由 gate 内部调用;写在这里用于能力审计,不构成模型执行步骤。
|
|
50
52
|
|
|
51
53
|
并行服务测试先运行 `harness_change.py lease-port --change <id> --run-id <run-id> --range <start-end> --json`,再把返回端口传给 `harness_service.py ensure --leased-port <port> --lease-owner <run-id>`。`serviceStart` 的 command/health/overlay 可用 `{leasedPort}` 占位符;用户自启进程仍只进入 Service Gate,禁止 kill。测试清理的 `finally` 中运行 `harness_change.py release-port --change <id> --run-id <run-id> --json`,避免租约池耗尽。
|
|
52
54
|
|
|
@@ -85,7 +87,7 @@ Runner 强制同项目单实例、低调度优先级、逐命令超时、正常
|
|
|
85
87
|
|
|
86
88
|
先 `harness_context.py prepare --phase test --executor <tool> [--change <id>] --json`,再 `harness_context.py begin --phase test --change <id> --executor <tool> --json` 校验最新 run→test receipt 的 artifact/hash/HEAD;然后 **`harness_gate.py begin --phase test --change <id>`**(禁止手工 phase.start / 手写 ledger)。执行各项强制环境检查 + **命令执行模式 preflight (0.1)**;只有首选执行器不可用时,才执行 fallback 执行器探测。
|
|
87
89
|
|
|
88
|
-
验证写入**仅**允许 `harness_ledger.py record` / `can-reuse`;禁止 Write/Edit `verification-ledger.json
|
|
90
|
+
验证写入**仅**允许 `harness_ledger.py record` / `can-reuse`;禁止 Write/Edit `verification-ledger.json`。测试跟踪:gate begin → 执行(可选 `harness_test_guard.py mark stale-test-repair`)→ 单次 `harness_gate.py close --phase test --status ... --to-phase review --executor <tool> --json`。fixback 时由 `harness_fixback.py` 合批记录 RED/GREEN 和 affected verification,再把同一 close 命令的 `--to-phase` 改为 `run`,由 context 失效受影响 target。
|
|
89
91
|
|
|
90
92
|
- **Read `checklist.md`** — 各项检查详情 + 0.1 preflight + Playwright 探测 + 避坑规则指引
|
|
91
93
|
- **失败处理**:任一项检查失败 → 终止流程并报告原因,用户确认修复后才能继续
|
|
@@ -740,7 +740,7 @@ TEST_<change-name>_<timestamp>_<short-random>
|
|
|
740
740
|
```powershell
|
|
741
741
|
# gate begin/close(phase=test;--task 仅 checkpoint 启用时必需;close 不需要 --skills-root)
|
|
742
742
|
python <skills-root>/scripts/harness_gate.py begin --change <cn> --phase test --skills-root <skills-root> [--task N]
|
|
743
|
-
python <skills-root>/scripts/harness_gate.py close --change <cn> --phase test --status OK [--task N]
|
|
743
|
+
python <skills-root>/scripts/harness_gate.py close --change <cn> --phase test --status OK --to-phase review --executor <tool> [--task N]
|
|
744
744
|
|
|
745
745
|
# ledger 记录 / 复用(--profile-input = verification key,不是文件路径)
|
|
746
746
|
python <skills-root>/scripts/harness_ledger.py record --change-dir <dir> --verification unitTestFull --status ok --command "<完整命令>" --exit-code 0 --duration-ms 120000 --evidence "Tests run: N, Failures: 0" --coverage full --files "packages/core/src/index.ts"
|