@hunter-harness/workflow-harness 0.4.9 → 0.4.10
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-execute/SKILL.md +2 -2
- package/harness/bundles/general/claude-code/harness-execute/testing-pitfalls-java.md +7 -0
- package/harness/bundles/general/claude-code/harness-knowledge-ingest/SKILL.md +7 -1
- package/harness/bundles/general/claude-code/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-plan/SKILL.md +3 -3
- package/harness/bundles/general/claude-code/harness-plan/checklist.md +1 -0
- package/harness/bundles/general/claude-code/harness-plan/reference.md +8 -0
- package/harness/bundles/general/claude-code/harness-pull/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-push/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-review/SKILL.md +1 -1
- 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/scripts/harness_context.py +71 -8
- package/harness/bundles/general/claude-code/scripts/harness_events.py +17 -11
- package/harness/bundles/general/claude-code/scripts/harness_gate.py +94 -9
- package/harness/bundles/general/claude-code/scripts/harness_ledger.py +84 -4
- 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-execute/SKILL.md +2 -2
- package/harness/bundles/general/codebuddy/harness-execute/testing-pitfalls-java.md +7 -0
- package/harness/bundles/general/codebuddy/harness-knowledge-ingest/SKILL.md +7 -1
- package/harness/bundles/general/codebuddy/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-plan/SKILL.md +3 -3
- package/harness/bundles/general/codebuddy/harness-plan/checklist.md +1 -0
- package/harness/bundles/general/codebuddy/harness-plan/reference.md +8 -0
- package/harness/bundles/general/codebuddy/harness-pull/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-push/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-review/SKILL.md +1 -1
- 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/scripts/harness_context.py +71 -8
- package/harness/bundles/general/codebuddy/scripts/harness_events.py +17 -11
- package/harness/bundles/general/codebuddy/scripts/harness_gate.py +94 -9
- package/harness/bundles/general/codebuddy/scripts/harness_ledger.py +84 -4
- 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-execute/SKILL.md +2 -2
- package/harness/bundles/general/codex/harness-execute/testing-pitfalls-java.md +7 -0
- package/harness/bundles/general/codex/harness-knowledge-ingest/SKILL.md +7 -1
- package/harness/bundles/general/codex/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-plan/SKILL.md +3 -3
- package/harness/bundles/general/codex/harness-plan/checklist.md +1 -0
- package/harness/bundles/general/codex/harness-plan/reference.md +8 -0
- package/harness/bundles/general/codex/harness-pull/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-push/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-review/SKILL.md +1 -1
- 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/scripts/harness_context.py +71 -8
- package/harness/bundles/general/codex/scripts/harness_events.py +17 -11
- package/harness/bundles/general/codex/scripts/harness_gate.py +94 -9
- package/harness/bundles/general/codex/scripts/harness_ledger.py +84 -4
- 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-execute/SKILL.md +2 -2
- package/harness/bundles/general/cursor/harness-execute/testing-pitfalls-java.md +7 -0
- package/harness/bundles/general/cursor/harness-knowledge-ingest/SKILL.md +7 -1
- package/harness/bundles/general/cursor/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-plan/SKILL.md +3 -3
- package/harness/bundles/general/cursor/harness-plan/checklist.md +1 -0
- package/harness/bundles/general/cursor/harness-plan/reference.md +8 -0
- package/harness/bundles/general/cursor/harness-pull/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-push/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-review/SKILL.md +1 -1
- 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/scripts/harness_context.py +71 -8
- package/harness/bundles/general/cursor/scripts/harness_events.py +17 -11
- package/harness/bundles/general/cursor/scripts/harness_gate.py +94 -9
- package/harness/bundles/general/cursor/scripts/harness_ledger.py +84 -4
- package/harness/bundles/general/pi/.harness-build.json +1 -1
- package/harness/bundles/general/pi/harness-archive/SKILL.md +1 -1
- package/harness/bundles/general/pi/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/general/pi/harness-execute/SKILL.md +2 -2
- package/harness/bundles/general/pi/harness-execute/testing-pitfalls-java.md +7 -0
- package/harness/bundles/general/pi/harness-knowledge-ingest/SKILL.md +7 -1
- package/harness/bundles/general/pi/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/pi/harness-plan/SKILL.md +3 -3
- package/harness/bundles/general/pi/harness-plan/checklist.md +1 -0
- package/harness/bundles/general/pi/harness-plan/reference.md +8 -0
- package/harness/bundles/general/pi/harness-pull/SKILL.md +1 -1
- package/harness/bundles/general/pi/harness-push/SKILL.md +1 -1
- package/harness/bundles/general/pi/harness-review/SKILL.md +1 -1
- package/harness/bundles/general/pi/harness-submit/SKILL.md +1 -1
- package/harness/bundles/general/pi/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/pi/scripts/harness_context.py +71 -8
- package/harness/bundles/general/pi/scripts/harness_events.py +17 -11
- package/harness/bundles/general/pi/scripts/harness_gate.py +94 -9
- package/harness/bundles/general/pi/scripts/harness_ledger.py +84 -4
- 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-execute/SKILL.md +2 -2
- package/harness/bundles/java/claude-code/harness-execute/testing-pitfalls-java.md +1 -0
- package/harness/bundles/java/claude-code/harness-knowledge-ingest/SKILL.md +7 -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 +3 -3
- package/harness/bundles/java/claude-code/harness-plan/checklist.md +1 -0
- package/harness/bundles/java/claude-code/harness-plan/reference.md +8 -0
- package/harness/bundles/java/claude-code/harness-pull/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-push/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-review/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 +1 -1
- package/harness/bundles/java/claude-code/scripts/harness_context.py +71 -8
- package/harness/bundles/java/claude-code/scripts/harness_events.py +17 -11
- package/harness/bundles/java/claude-code/scripts/harness_gate.py +94 -9
- package/harness/bundles/java/claude-code/scripts/harness_ledger.py +84 -4
- 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-execute/SKILL.md +2 -2
- package/harness/bundles/java/codebuddy/harness-execute/testing-pitfalls-java.md +1 -0
- package/harness/bundles/java/codebuddy/harness-knowledge-ingest/SKILL.md +7 -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 +3 -3
- package/harness/bundles/java/codebuddy/harness-plan/checklist.md +1 -0
- package/harness/bundles/java/codebuddy/harness-plan/reference.md +8 -0
- package/harness/bundles/java/codebuddy/harness-pull/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-push/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-review/SKILL.md +1 -1
- 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/scripts/harness_context.py +71 -8
- package/harness/bundles/java/codebuddy/scripts/harness_events.py +17 -11
- package/harness/bundles/java/codebuddy/scripts/harness_gate.py +94 -9
- package/harness/bundles/java/codebuddy/scripts/harness_ledger.py +84 -4
- 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-execute/SKILL.md +2 -2
- package/harness/bundles/java/codex/harness-execute/testing-pitfalls-java.md +1 -0
- package/harness/bundles/java/codex/harness-knowledge-ingest/SKILL.md +7 -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 +3 -3
- package/harness/bundles/java/codex/harness-plan/checklist.md +1 -0
- package/harness/bundles/java/codex/harness-plan/reference.md +8 -0
- package/harness/bundles/java/codex/harness-pull/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-push/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-review/SKILL.md +1 -1
- 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/scripts/harness_context.py +71 -8
- package/harness/bundles/java/codex/scripts/harness_events.py +17 -11
- package/harness/bundles/java/codex/scripts/harness_gate.py +94 -9
- package/harness/bundles/java/codex/scripts/harness_ledger.py +84 -4
- 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-execute/SKILL.md +2 -2
- package/harness/bundles/java/cursor/harness-execute/testing-pitfalls-java.md +1 -0
- package/harness/bundles/java/cursor/harness-knowledge-ingest/SKILL.md +7 -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 +3 -3
- package/harness/bundles/java/cursor/harness-plan/checklist.md +1 -0
- package/harness/bundles/java/cursor/harness-plan/reference.md +8 -0
- package/harness/bundles/java/cursor/harness-pull/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-push/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-review/SKILL.md +1 -1
- 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/scripts/harness_context.py +71 -8
- package/harness/bundles/java/cursor/scripts/harness_events.py +17 -11
- package/harness/bundles/java/cursor/scripts/harness_gate.py +94 -9
- package/harness/bundles/java/cursor/scripts/harness_ledger.py +84 -4
- package/harness/bundles/java/pi/.harness-build.json +1 -1
- package/harness/bundles/java/pi/harness-apidoc/SKILL.md +1 -1
- package/harness/bundles/java/pi/harness-archive/SKILL.md +1 -1
- package/harness/bundles/java/pi/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/java/pi/harness-execute/SKILL.md +2 -2
- package/harness/bundles/java/pi/harness-execute/testing-pitfalls-java.md +1 -0
- package/harness/bundles/java/pi/harness-knowledge-ingest/SKILL.md +7 -1
- package/harness/bundles/java/pi/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/java/pi/harness-package/SKILL.md +1 -1
- package/harness/bundles/java/pi/harness-plan/SKILL.md +3 -3
- package/harness/bundles/java/pi/harness-plan/checklist.md +1 -0
- package/harness/bundles/java/pi/harness-plan/reference.md +8 -0
- package/harness/bundles/java/pi/harness-pull/SKILL.md +1 -1
- package/harness/bundles/java/pi/harness-push/SKILL.md +1 -1
- package/harness/bundles/java/pi/harness-review/SKILL.md +1 -1
- package/harness/bundles/java/pi/harness-submit/SKILL.md +1 -1
- package/harness/bundles/java/pi/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/pi/scripts/harness_context.py +71 -8
- package/harness/bundles/java/pi/scripts/harness_events.py +17 -11
- package/harness/bundles/java/pi/scripts/harness_gate.py +94 -9
- package/harness/bundles/java/pi/scripts/harness_ledger.py +84 -4
- package/harness/manifests/general/claude-code.json +19 -19
- package/harness/manifests/general/codebuddy.json +19 -19
- package/harness/manifests/general/codex.json +19 -19
- package/harness/manifests/general/cursor.json +19 -19
- package/harness/manifests/general/pi.json +19 -19
- package/harness/manifests/java/claude-code.json +21 -21
- package/harness/manifests/java/codebuddy.json +21 -21
- package/harness/manifests/java/codex.json +21 -21
- package/harness/manifests/java/cursor.json +21 -21
- package/harness/manifests/java/pi.json +21 -21
- package/hunter-workflow-family.json +2 -2
- package/package.json +1 -1
|
@@ -478,6 +478,55 @@ def _phase_has_ended(event_paths: list[Path], phase: str) -> bool:
|
|
|
478
478
|
)
|
|
479
479
|
|
|
480
480
|
|
|
481
|
+
def _ensure_phase_end_event(
|
|
482
|
+
contract_root: Path,
|
|
483
|
+
*,
|
|
484
|
+
phase: str,
|
|
485
|
+
status: str,
|
|
486
|
+
executor: str,
|
|
487
|
+
) -> dict[str, Any] | None:
|
|
488
|
+
"""补齐 phase.start/end 事件对(P0-2)。
|
|
489
|
+
|
|
490
|
+
平台 Run 计时由 events-sync 上报的 phase.start/end 事件对驱动。不经
|
|
491
|
+
gate close 收尾的阶段(典型:plan 走 finalize + context close)只写
|
|
492
|
+
transitions 收据,phase.end 永远缺席,平台计时永不停——连续两个 change
|
|
493
|
+
复现,属结构性缺口(2026-08-30 sales-insight-agent demo-datasource 实测)。
|
|
494
|
+
close_transition 是所有跨阶段推进的共同通道,在此按事件配对补齐:
|
|
495
|
+
有 start 无 end → 复用 start 的 run_id/attempt 代写 end;已有 end(gate
|
|
496
|
+
close 先行写入)或连 start 都没有 → 不动,返回 None。
|
|
497
|
+
"""
|
|
498
|
+
import harness_events as he
|
|
499
|
+
|
|
500
|
+
events_file = contract_root / "events.ndjson"
|
|
501
|
+
events = _read_ndjson(events_file)
|
|
502
|
+
starts = [
|
|
503
|
+
item
|
|
504
|
+
for item in events
|
|
505
|
+
if hpaths.resolve_phase_name(item.get("phase")) == phase
|
|
506
|
+
and item.get("type") == "phase.start"
|
|
507
|
+
]
|
|
508
|
+
if not starts or any(
|
|
509
|
+
hpaths.resolve_phase_name(item.get("phase")) == phase
|
|
510
|
+
and item.get("type") == "phase.end"
|
|
511
|
+
for item in events
|
|
512
|
+
):
|
|
513
|
+
return None
|
|
514
|
+
start = starts[-1]
|
|
515
|
+
result = he.append_event(
|
|
516
|
+
contract_root,
|
|
517
|
+
phase=phase,
|
|
518
|
+
type_="phase.end",
|
|
519
|
+
run_id=str(start.get("run_id") or f"{phase}_unknown"),
|
|
520
|
+
attempt=int(start.get("attempt") or 1),
|
|
521
|
+
status=status,
|
|
522
|
+
executor_tool=executor,
|
|
523
|
+
note="context close 自动补齐 phase.end(平台计时事件对配对)",
|
|
524
|
+
)
|
|
525
|
+
if not result.get("ok"):
|
|
526
|
+
return {"code": "PHASE_END_PAIR_FAILED", "detail": result}
|
|
527
|
+
return {"code": "PHASE_END_AUTO_PAIRED", "event": result.get("event")}
|
|
528
|
+
|
|
529
|
+
|
|
481
530
|
def _reselect_review_fixback(
|
|
482
531
|
project: Path,
|
|
483
532
|
change: str,
|
|
@@ -1334,6 +1383,12 @@ def close_transition(
|
|
|
1334
1383
|
and latest.get("artifacts") == artifact_entries
|
|
1335
1384
|
and (executor is None or latest.get("executor") == executor)
|
|
1336
1385
|
):
|
|
1386
|
+
paired = _ensure_phase_end_event(
|
|
1387
|
+
contract_root,
|
|
1388
|
+
phase=from_phase,
|
|
1389
|
+
status=status,
|
|
1390
|
+
executor=executor or str(latest.get("executor") or "unknown"),
|
|
1391
|
+
)
|
|
1337
1392
|
return {
|
|
1338
1393
|
"ok": True,
|
|
1339
1394
|
"code": "TRANSITION_ALREADY_CLOSED",
|
|
@@ -1341,6 +1396,7 @@ def close_transition(
|
|
|
1341
1396
|
"receipt": latest,
|
|
1342
1397
|
"path": str(paths["transitions"]),
|
|
1343
1398
|
"invalidation": None,
|
|
1399
|
+
"phaseEndPair": paired,
|
|
1344
1400
|
}
|
|
1345
1401
|
return {"ok": False, "code": "CONTEXT_LEASE_REQUIRED"}
|
|
1346
1402
|
try:
|
|
@@ -1397,6 +1453,12 @@ def close_transition(
|
|
|
1397
1453
|
receipt["receiptHash"] = _payload_hash(receipt)
|
|
1398
1454
|
_append_ndjson(paths["transitions"], receipt)
|
|
1399
1455
|
paths["lease"].unlink(missing_ok=True)
|
|
1456
|
+
paired = _ensure_phase_end_event(
|
|
1457
|
+
contract_root,
|
|
1458
|
+
phase=from_phase,
|
|
1459
|
+
status=status,
|
|
1460
|
+
executor=effective_executor,
|
|
1461
|
+
)
|
|
1400
1462
|
invalidation = None
|
|
1401
1463
|
if to_phase == "execute" and from_phase in {"execute", "review"}:
|
|
1402
1464
|
invalidation = _invalidate_for_fixback(
|
|
@@ -1411,6 +1473,7 @@ def close_transition(
|
|
|
1411
1473
|
"receipt": receipt,
|
|
1412
1474
|
"path": str(paths["transitions"]),
|
|
1413
1475
|
"invalidation": invalidation,
|
|
1476
|
+
"phaseEndPair": paired,
|
|
1414
1477
|
}
|
|
1415
1478
|
|
|
1416
1479
|
|
|
@@ -1898,7 +1961,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
1898
1961
|
prepare = sub.add_parser("prepare")
|
|
1899
1962
|
prepare.add_argument("--json", action="store_true")
|
|
1900
1963
|
prepare.add_argument("--project", required=True, type=Path)
|
|
1901
|
-
prepare.add_argument("--change")
|
|
1964
|
+
prepare.add_argument("--change", "--change-dir", dest="change")
|
|
1902
1965
|
prepare.add_argument("--phase", required=True)
|
|
1903
1966
|
prepare.add_argument("--executor", required=True)
|
|
1904
1967
|
prepare.add_argument("--title")
|
|
@@ -1907,7 +1970,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
1907
1970
|
bootstrap = sub.add_parser("bootstrap-plan")
|
|
1908
1971
|
bootstrap.add_argument("--json", action="store_true")
|
|
1909
1972
|
bootstrap.add_argument("--project", required=True, type=Path)
|
|
1910
|
-
bootstrap.add_argument("--change", required=True)
|
|
1973
|
+
bootstrap.add_argument("--change", "--change-dir", dest="change", required=True)
|
|
1911
1974
|
bootstrap.add_argument("--executor", required=True)
|
|
1912
1975
|
bootstrap.add_argument("--title")
|
|
1913
1976
|
bootstrap.add_argument("--stage", default="plan", choices=["plan", "post-run"])
|
|
@@ -1915,7 +1978,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
1915
1978
|
close = sub.add_parser("close")
|
|
1916
1979
|
close.add_argument("--json", action="store_true")
|
|
1917
1980
|
close.add_argument("--project", required=True, type=Path)
|
|
1918
|
-
close.add_argument("--change", required=True)
|
|
1981
|
+
close.add_argument("--change", "--change-dir", dest="change", required=True)
|
|
1919
1982
|
close.add_argument("--from-phase", required=True)
|
|
1920
1983
|
close.add_argument("--to-phase", required=True)
|
|
1921
1984
|
close.add_argument("--executor", required=True)
|
|
@@ -1924,7 +1987,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
1924
1987
|
begin = sub.add_parser("begin")
|
|
1925
1988
|
begin.add_argument("--json", action="store_true")
|
|
1926
1989
|
begin.add_argument("--project", required=True, type=Path)
|
|
1927
|
-
begin.add_argument("--change", required=True)
|
|
1990
|
+
begin.add_argument("--change", "--change-dir", dest="change", required=True)
|
|
1928
1991
|
begin.add_argument("--phase", required=True)
|
|
1929
1992
|
begin.add_argument("--executor", required=True)
|
|
1930
1993
|
handoff = sub.add_parser(
|
|
@@ -1933,7 +1996,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
1933
1996
|
)
|
|
1934
1997
|
handoff.add_argument("--json", action="store_true")
|
|
1935
1998
|
handoff.add_argument("--project", required=True, type=Path)
|
|
1936
|
-
handoff.add_argument("--change", required=True)
|
|
1999
|
+
handoff.add_argument("--change", "--change-dir", dest="change", required=True)
|
|
1937
2000
|
handoff.add_argument("--to-phase", required=True)
|
|
1938
2001
|
handoff.add_argument(
|
|
1939
2002
|
"--from-phase",
|
|
@@ -1946,20 +2009,20 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
1946
2009
|
)
|
|
1947
2010
|
renew.add_argument("--json", action="store_true")
|
|
1948
2011
|
renew.add_argument("--project", required=True, type=Path)
|
|
1949
|
-
renew.add_argument("--change", required=True)
|
|
2012
|
+
renew.add_argument("--change", "--change-dir", dest="change", required=True)
|
|
1950
2013
|
renew.add_argument("--executor", required=True)
|
|
1951
2014
|
renew.add_argument("--ttl-seconds", type=int, default=3600)
|
|
1952
2015
|
configure = sub.add_parser("configure-plan")
|
|
1953
2016
|
configure.add_argument("--json", action="store_true")
|
|
1954
2017
|
configure.add_argument("--project", required=True, type=Path)
|
|
1955
|
-
configure.add_argument("--change", required=True)
|
|
2018
|
+
configure.add_argument("--change", "--change-dir", dest="change", required=True)
|
|
1956
2019
|
configure.add_argument("--phases", required=True)
|
|
1957
2020
|
configure.add_argument("--operator", required=True)
|
|
1958
2021
|
configure.add_argument("--reason", required=True)
|
|
1959
2022
|
view = sub.add_parser("view")
|
|
1960
2023
|
view.add_argument("--json", action="store_true")
|
|
1961
2024
|
view.add_argument("--project", required=True, type=Path)
|
|
1962
|
-
view.add_argument("--change", required=True)
|
|
2025
|
+
view.add_argument("--change", "--change-dir", dest="change", required=True)
|
|
1963
2026
|
return parser
|
|
1964
2027
|
|
|
1965
2028
|
|
|
@@ -884,13 +884,19 @@ def build_event(args: argparse.Namespace, existing: list[dict[str, Any]]) -> dic
|
|
|
884
884
|
def validate_append_event(args: argparse.Namespace) -> tuple[str, str] | None:
|
|
885
885
|
"""Validate type-specific append fields before any file is mutated."""
|
|
886
886
|
event_type = str(args.type)
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
887
|
+
missing = [
|
|
888
|
+
f"--{field.replace('_', '-')}"
|
|
889
|
+
for field in _EVENT_REQUIRED_FIELDS.get(event_type, ())
|
|
890
|
+
if getattr(args, field, None) is None
|
|
891
|
+
or (isinstance(getattr(args, field, None), str)
|
|
892
|
+
and not getattr(args, field).strip())
|
|
893
|
+
]
|
|
894
|
+
if missing:
|
|
895
|
+
# 一次性列出全部缺失字段——逐个报错会让调用方每个字段往返一轮(E-3)
|
|
896
|
+
return (
|
|
897
|
+
"EVENT_REQUIRED_FIELD",
|
|
898
|
+
f"EVENT_REQUIRED_FIELD: {event_type} requires " + ", ".join(missing),
|
|
899
|
+
)
|
|
894
900
|
allowed = _EVENT_ALLOWED_FIELDS[event_type]
|
|
895
901
|
for field in OPTIONAL_FIELDS:
|
|
896
902
|
value = getattr(args, field, None)
|
|
@@ -2146,7 +2152,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
2146
2152
|
parents=[common],
|
|
2147
2153
|
help="append one event and auto-render execution-log",
|
|
2148
2154
|
)
|
|
2149
|
-
p_append.add_argument("--change-dir", required=True)
|
|
2155
|
+
p_append.add_argument("--change-dir", "--change", dest="change_dir", required=True)
|
|
2150
2156
|
p_append.add_argument("--phase", required=True)
|
|
2151
2157
|
p_append.add_argument("--type", required=True)
|
|
2152
2158
|
p_append.add_argument("--command", default=None)
|
|
@@ -2200,7 +2206,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
2200
2206
|
parents=[common],
|
|
2201
2207
|
help="validate and append many events under one lock (H-15)",
|
|
2202
2208
|
)
|
|
2203
|
-
p_batch.add_argument("--change-dir", required=True)
|
|
2209
|
+
p_batch.add_argument("--change-dir", "--change", dest="change_dir", required=True)
|
|
2204
2210
|
p_batch.add_argument(
|
|
2205
2211
|
"--file",
|
|
2206
2212
|
required=True,
|
|
@@ -2213,7 +2219,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
2213
2219
|
parents=[common],
|
|
2214
2220
|
help="re-render execution-log.md from events.ndjson",
|
|
2215
2221
|
)
|
|
2216
|
-
p_render.add_argument("--change-dir", required=True)
|
|
2222
|
+
p_render.add_argument("--change-dir", "--change", dest="change_dir", required=True)
|
|
2217
2223
|
p_render.set_defaults(func=cmd_render)
|
|
2218
2224
|
|
|
2219
2225
|
p_summary = sub.add_parser(
|
|
@@ -2221,7 +2227,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
2221
2227
|
parents=[common],
|
|
2222
2228
|
help="summarize phases/issues from events.ndjson",
|
|
2223
2229
|
)
|
|
2224
|
-
p_summary.add_argument("--change-dir", required=True)
|
|
2230
|
+
p_summary.add_argument("--change-dir", "--change", dest="change_dir", required=True)
|
|
2225
2231
|
p_summary.set_defaults(func=cmd_summary)
|
|
2226
2232
|
|
|
2227
2233
|
return parser
|
|
@@ -2594,6 +2594,64 @@ def _normalize_gate_phase(args: argparse.Namespace, as_json: bool) -> int | None
|
|
|
2594
2594
|
)
|
|
2595
2595
|
|
|
2596
2596
|
|
|
2597
|
+
def _verification_target_coverage_warning(
|
|
2598
|
+
change_dir: Path, phase: str
|
|
2599
|
+
) -> dict[str, Any] | None:
|
|
2600
|
+
"""E-5:场景表引用未声明的 verification target 时提前 WARN。
|
|
2601
|
+
|
|
2602
|
+
场景带 execution_level(unit/integration/…),但 build-profile 的
|
|
2603
|
+
verificationGraph.targets 未声明对应 target 时,问题要等到 ledger record
|
|
2604
|
+
才以 unsupported verification 报错——太迟。gate begin 提前列出待补清单。
|
|
2605
|
+
映射约定:unit → unitTest;其他 level → 任一以其名开头的 target。
|
|
2606
|
+
"""
|
|
2607
|
+
if phase != "execute":
|
|
2608
|
+
return None
|
|
2609
|
+
manifest_path = change_dir / "meta" / "scenario-manifest.json"
|
|
2610
|
+
profile_path = change_dir / "meta" / "build-profile.json"
|
|
2611
|
+
if not profile_path.is_file():
|
|
2612
|
+
# build-profile 常规位置在项目 .harness/config/;change meta 下优先
|
|
2613
|
+
profile_path = change_dir.parents[2] / ".harness" / "config" / "build-profile.json"
|
|
2614
|
+
if not manifest_path.is_file() or not profile_path.is_file():
|
|
2615
|
+
return None
|
|
2616
|
+
try:
|
|
2617
|
+
manifest = json.loads(manifest_path.read_text(encoding="utf-8-sig"))
|
|
2618
|
+
profile = json.loads(profile_path.read_text(encoding="utf-8-sig"))
|
|
2619
|
+
except (OSError, json.JSONDecodeError):
|
|
2620
|
+
return None
|
|
2621
|
+
content = manifest.get("content") if isinstance(manifest, dict) else None
|
|
2622
|
+
scenarios = content.get("scenarios") if isinstance(content, dict) else None
|
|
2623
|
+
if not isinstance(scenarios, list):
|
|
2624
|
+
return None
|
|
2625
|
+
levels = sorted({
|
|
2626
|
+
str(s.get("execution_level")).strip()
|
|
2627
|
+
for s in scenarios
|
|
2628
|
+
if isinstance(s, dict) and str(s.get("execution_level") or "").strip()
|
|
2629
|
+
})
|
|
2630
|
+
graph = profile.get("verificationGraph")
|
|
2631
|
+
targets = graph.get("targets") if isinstance(graph, dict) else None
|
|
2632
|
+
target_names = set(targets.keys()) if isinstance(targets, dict) else set()
|
|
2633
|
+
uncovered = [
|
|
2634
|
+
level for level in levels
|
|
2635
|
+
if level != "unit"
|
|
2636
|
+
and not any(name.lower().startswith(level.lower()) for name in target_names)
|
|
2637
|
+
]
|
|
2638
|
+
# unit 场景由 unitTest 覆盖;只在完全没有 unitTest 时才算缺口
|
|
2639
|
+
if "unit" in levels and "unitTest" not in target_names:
|
|
2640
|
+
uncovered.append("unit(→unitTest)")
|
|
2641
|
+
if not uncovered:
|
|
2642
|
+
return None
|
|
2643
|
+
return {
|
|
2644
|
+
"code": "VERIFICATION_TARGETS_UNDECLARED",
|
|
2645
|
+
"message": (
|
|
2646
|
+
"场景表涉及未声明的验证级别: " + ", ".join(uncovered) +
|
|
2647
|
+
";ledger record 将报 unsupported verification。请在 "
|
|
2648
|
+
".harness/config/build-profile.json 的 verificationGraph.targets "
|
|
2649
|
+
"声明对应 target(如 integrationTest)"
|
|
2650
|
+
),
|
|
2651
|
+
"uncoveredLevels": uncovered,
|
|
2652
|
+
}
|
|
2653
|
+
|
|
2654
|
+
|
|
2597
2655
|
def cmd_begin(args: argparse.Namespace) -> int:
|
|
2598
2656
|
as_json = bool(args.json)
|
|
2599
2657
|
normalized = _normalize_gate_phase(args, as_json)
|
|
@@ -2911,6 +2969,14 @@ def cmd_begin(args: argparse.Namespace) -> int:
|
|
|
2911
2969
|
)
|
|
2912
2970
|
raise
|
|
2913
2971
|
|
|
2972
|
+
coverage_warning = _verification_target_coverage_warning(change_dir, args.phase)
|
|
2973
|
+
if coverage_warning is not None:
|
|
2974
|
+
gate_warnings.append(coverage_warning)
|
|
2975
|
+
print(
|
|
2976
|
+
f"[harness-gate] WARNING {coverage_warning['code']}: "
|
|
2977
|
+
f"{coverage_warning['message']}",
|
|
2978
|
+
file=sys.stderr,
|
|
2979
|
+
)
|
|
2914
2980
|
payload = {
|
|
2915
2981
|
"ok": True,
|
|
2916
2982
|
"code": "PHASE_BEGUN",
|
|
@@ -3800,11 +3866,25 @@ def cmd_classify(args: argparse.Namespace) -> int:
|
|
|
3800
3866
|
payload["classifiedAt"] = classified_at
|
|
3801
3867
|
|
|
3802
3868
|
if change_dir is not None and change_dir.is_dir():
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3869
|
+
finalized = (change_dir / "meta" / "plan-profile.json").is_file()
|
|
3870
|
+
if finalized and not getattr(args, "force", False):
|
|
3871
|
+
# P1-1:classify 形态像只读查询,实际是写操作。已发布的 change
|
|
3872
|
+
# 被误跑 classify 会覆盖 configure-plan 写入的工作副本(2026-08-30
|
|
3873
|
+
# demo-datasource 实测 plannedPhases 被清)。默认拒写并显式提示。
|
|
3874
|
+
payload["policyPersisted"] = False
|
|
3875
|
+
payload["warning"] = (
|
|
3876
|
+
"change 已发布(meta/plan-profile.json 存在),classify 不再重写 "
|
|
3877
|
+
"meta/gate-policy.json 工作副本;确需重算请显式传 --force"
|
|
3878
|
+
)
|
|
3879
|
+
payload["writeGuarded"] = True
|
|
3880
|
+
else:
|
|
3881
|
+
policy_doc = gate_policy_document(payload)
|
|
3882
|
+
policy_path = change_dir / "meta" / "gate-policy.json"
|
|
3883
|
+
_write_json(policy_path, policy_doc)
|
|
3884
|
+
payload["policyPersisted"] = True
|
|
3885
|
+
payload["policyPath"] = str(policy_path)
|
|
3886
|
+
if finalized:
|
|
3887
|
+
payload["warning"] = "--force:已发布 change 的工作副本已被重写"
|
|
3808
3888
|
else:
|
|
3809
3889
|
payload["policyPersisted"] = False
|
|
3810
3890
|
payload["warning"] = (
|
|
@@ -3930,7 +4010,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
3930
4010
|
|
|
3931
4011
|
p_begin = sub.add_parser("begin", parents=[shared])
|
|
3932
4012
|
p_begin.add_argument("--phase", required=True)
|
|
3933
|
-
p_begin.add_argument("--change", default=None)
|
|
4013
|
+
p_begin.add_argument("--change", "--change-dir", dest="change", default=None)
|
|
3934
4014
|
p_begin.add_argument(
|
|
3935
4015
|
"--project",
|
|
3936
4016
|
default=None,
|
|
@@ -3960,7 +4040,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
3960
4040
|
help="关闭阶段门禁(关门只调本命令;上下文交接由 --to-phase 内联完成,不要再单独调 harness_context.py close)",
|
|
3961
4041
|
)
|
|
3962
4042
|
p_close.add_argument("--phase", required=True)
|
|
3963
|
-
p_close.add_argument("--change", default=None)
|
|
4043
|
+
p_close.add_argument("--change", "--change-dir", dest="change", default=None)
|
|
3964
4044
|
p_close.add_argument(
|
|
3965
4045
|
"--project",
|
|
3966
4046
|
default=None,
|
|
@@ -3985,7 +4065,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
3985
4065
|
|
|
3986
4066
|
p_classify = sub.add_parser("classify", parents=[shared])
|
|
3987
4067
|
p_classify.add_argument("--project", default=None, type=Path, help=_PROJECT_ROOT_HELP)
|
|
3988
|
-
p_classify.add_argument("--change", default=None)
|
|
4068
|
+
p_classify.add_argument("--change", "--change-dir", dest="change", default=None)
|
|
3989
4069
|
p_classify.add_argument("--stage", required=True, choices=["plan", "post-run"])
|
|
3990
4070
|
p_classify.add_argument(
|
|
3991
4071
|
"--tier-override",
|
|
@@ -3993,13 +4073,18 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
3993
4073
|
choices=["fast", "standard", "full"],
|
|
3994
4074
|
)
|
|
3995
4075
|
p_classify.add_argument("--override-by", default="user")
|
|
4076
|
+
p_classify.add_argument(
|
|
4077
|
+
"--force",
|
|
4078
|
+
action="store_true",
|
|
4079
|
+
help="已发布 change 也强制重写 gate-policy.json 工作副本",
|
|
4080
|
+
)
|
|
3996
4081
|
p_classify.set_defaults(func=cmd_classify)
|
|
3997
4082
|
|
|
3998
4083
|
p_checkpoint = sub.add_parser("checkpoint", parents=[shared])
|
|
3999
4084
|
p_checkpoint.add_argument("--project", default=None, type=Path, help=_PROJECT_ROOT_HELP)
|
|
4000
4085
|
p_checkpoint.add_argument("checkpoint_action", choices=["status", "approve"])
|
|
4001
4086
|
p_checkpoint.add_argument("--id", required=True)
|
|
4002
|
-
p_checkpoint.add_argument("--change", default=None)
|
|
4087
|
+
p_checkpoint.add_argument("--change", "--change-dir", dest="change", default=None)
|
|
4003
4088
|
p_checkpoint.add_argument("--reviewer", default=None)
|
|
4004
4089
|
p_checkpoint.set_defaults(func=cmd_checkpoint)
|
|
4005
4090
|
|
|
@@ -1624,6 +1624,15 @@ def evidence_summary(entry: dict[str, Any]) -> dict[str, Any]:
|
|
|
1624
1624
|
}
|
|
1625
1625
|
|
|
1626
1626
|
|
|
1627
|
+
def _is_relative_to(path: Path, base: Path) -> bool:
|
|
1628
|
+
"""Python 3.9 兼容的 is_relative_to。"""
|
|
1629
|
+
try:
|
|
1630
|
+
path.relative_to(base)
|
|
1631
|
+
return True
|
|
1632
|
+
except ValueError:
|
|
1633
|
+
return False
|
|
1634
|
+
|
|
1635
|
+
|
|
1627
1636
|
def _nonempty_str(value: Any) -> bool:
|
|
1628
1637
|
return isinstance(value, str) and bool(value.strip())
|
|
1629
1638
|
|
|
@@ -3056,6 +3065,12 @@ def cmd_record(args: argparse.Namespace) -> int:
|
|
|
3056
3065
|
"scenarioCoverage": entry.get("scenarioCoverage"),
|
|
3057
3066
|
"migrationReceipt": migration_receipt,
|
|
3058
3067
|
}
|
|
3068
|
+
zero_warn = _zero_tests_with_selector_warning(
|
|
3069
|
+
str(args.command), args.evidence, project_root
|
|
3070
|
+
)
|
|
3071
|
+
if zero_warn is not None:
|
|
3072
|
+
payload["warnings"] = [zero_warn]
|
|
3073
|
+
print(f"[harness-ledger] WARNING {zero_warn}", file=sys.stderr)
|
|
3059
3074
|
emit_compact_or_verbose(
|
|
3060
3075
|
payload, as_json=as_json, verbose=verbose,
|
|
3061
3076
|
compact_fn=_compact_record_payload,
|
|
@@ -3063,6 +3078,47 @@ def cmd_record(args: argparse.Namespace) -> int:
|
|
|
3063
3078
|
return 0
|
|
3064
3079
|
|
|
3065
3080
|
|
|
3081
|
+
def _zero_tests_with_selector_warning(
|
|
3082
|
+
command: str, evidence: str | None, project_root: Path | None
|
|
3083
|
+
) -> str | None:
|
|
3084
|
+
"""选择器存在却 0 命中 → WARN(E-2)。
|
|
3085
|
+
|
|
3086
|
+
surefire 插件级 ``<excludedGroups>`` 会压过命令行空值覆盖,
|
|
3087
|
+
`-Dgroups=X` 选择器 0 命中仍 exit 0——只看退出码就是假阳性
|
|
3088
|
+
(2026-08-30 demo-datasource 集成测试静默 0 执行实测)。
|
|
3089
|
+
证据文本出现 Tests run: 0 且命令含选择器时,record 给出醒目警告。
|
|
3090
|
+
"""
|
|
3091
|
+
if not evidence:
|
|
3092
|
+
return None
|
|
3093
|
+
selectors = ("-Dgroups=", "-Dtest=", "--groups", "--filter", "-DfailIfNoSpecifiedTests")
|
|
3094
|
+
if not any(selector in str(command) for selector in selectors):
|
|
3095
|
+
return None
|
|
3096
|
+
candidates = [Path(evidence)]
|
|
3097
|
+
if project_root is not None and not Path(evidence).is_absolute():
|
|
3098
|
+
candidates.insert(0, project_root / evidence)
|
|
3099
|
+
text: str | None = None
|
|
3100
|
+
for candidate in candidates:
|
|
3101
|
+
try:
|
|
3102
|
+
if candidate.is_file() and candidate.stat().st_size <= 4 * 1024 * 1024:
|
|
3103
|
+
text = candidate.read_text(encoding="utf-8", errors="replace")
|
|
3104
|
+
break
|
|
3105
|
+
except OSError:
|
|
3106
|
+
continue
|
|
3107
|
+
if text is None:
|
|
3108
|
+
return None
|
|
3109
|
+
zero = re.search(
|
|
3110
|
+
r"Tests run:\s*0,\s*Failures:\s*0,\s*Errors:\s*0", text
|
|
3111
|
+
)
|
|
3112
|
+
ran_something = re.search(r"Tests run:\s*[1-9]", text)
|
|
3113
|
+
if zero is not None and ran_something is None:
|
|
3114
|
+
return (
|
|
3115
|
+
"ZERO_TESTS_WITH_SELECTOR: 命令含测试选择器但 Tests run=0(exit 0 不代表通过);"
|
|
3116
|
+
"常见于 surefire 插件级 excludedGroups 压过命令行覆盖——检查 pom 是否写死 "
|
|
3117
|
+
"<excludedGroups>(应改为 ${excludedGroups} 属性占位)"
|
|
3118
|
+
)
|
|
3119
|
+
return None
|
|
3120
|
+
|
|
3121
|
+
|
|
3066
3122
|
def cmd_scenario_receipt_template(args: argparse.Namespace) -> int:
|
|
3067
3123
|
"""Emit a schema-v2 receipt skeleton built from the scenario manifest.
|
|
3068
3124
|
|
|
@@ -3202,8 +3258,32 @@ def cmd_scenario_receipt_template(args: argparse.Namespace) -> int:
|
|
|
3202
3258
|
return 0
|
|
3203
3259
|
out_path = Path(str(out_raw)).expanduser()
|
|
3204
3260
|
if not out_path.is_absolute():
|
|
3205
|
-
|
|
3261
|
+
# 防双重拼接(E-1):调用方按 CLI 惯例传 cwd 相对路径时(如
|
|
3262
|
+
# .harness/changes/<cn>/evidence/x.json),直接拼 change_dir 会产生嵌套
|
|
3263
|
+
# 幽灵目录。相对路径若以 change-dir 自身前缀开头,按 cwd 解析;否则按
|
|
3264
|
+
# 文档规则相对 change-dir 解析。
|
|
3265
|
+
cwd = Path.cwd().resolve()
|
|
3266
|
+
change_resolved = change_dir.resolve()
|
|
3267
|
+
if _is_relative_to(change_resolved, cwd):
|
|
3268
|
+
change_rel = change_resolved.relative_to(cwd)
|
|
3269
|
+
if out_path.parts[: len(change_rel.parts)] == change_rel.parts:
|
|
3270
|
+
out_path = cwd / out_path
|
|
3271
|
+
else:
|
|
3272
|
+
out_path = change_dir / out_path
|
|
3273
|
+
else:
|
|
3274
|
+
out_path = change_dir / out_path
|
|
3206
3275
|
out_path = out_path.resolve()
|
|
3276
|
+
change_resolved = change_dir.resolve()
|
|
3277
|
+
cwd = Path.cwd().resolve()
|
|
3278
|
+
if not (
|
|
3279
|
+
_is_relative_to(out_path, change_resolved)
|
|
3280
|
+
or _is_relative_to(out_path, cwd)
|
|
3281
|
+
):
|
|
3282
|
+
return emit_error(
|
|
3283
|
+
"--out 解析后越出项目目录: " + str(out_path),
|
|
3284
|
+
as_json=as_json,
|
|
3285
|
+
error_code="SCENARIO_RECEIPT_OUT_OF_PROJECT",
|
|
3286
|
+
)
|
|
3207
3287
|
try:
|
|
3208
3288
|
out_path.parent.mkdir(parents=True, exist_ok=True)
|
|
3209
3289
|
out_path.write_text(body, encoding="utf-8", newline="\n")
|
|
@@ -3321,7 +3401,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
3321
3401
|
p_hash.set_defaults(func=cmd_hash)
|
|
3322
3402
|
|
|
3323
3403
|
p_reuse = sub.add_parser("can-reuse", parents=[shared_json], help="decide whether a verification can be reused")
|
|
3324
|
-
p_reuse.add_argument("--change-dir", required=True)
|
|
3404
|
+
p_reuse.add_argument("--change-dir", "--change", dest="change_dir", required=True)
|
|
3325
3405
|
p_reuse.add_argument(
|
|
3326
3406
|
"--verification",
|
|
3327
3407
|
required=True,
|
|
@@ -3389,7 +3469,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
3389
3469
|
p_reuse.set_defaults(func=cmd_can_reuse)
|
|
3390
3470
|
|
|
3391
3471
|
p_record = sub.add_parser("record", parents=[shared_json], help="write validation result into ledger")
|
|
3392
|
-
p_record.add_argument("--change-dir", required=True)
|
|
3472
|
+
p_record.add_argument("--change-dir", "--change", dest="change_dir", required=True)
|
|
3393
3473
|
p_record.add_argument("--verification", required=True)
|
|
3394
3474
|
p_record.add_argument("--status", required=True)
|
|
3395
3475
|
p_record.add_argument("--command", required=True)
|
|
@@ -3515,7 +3595,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
3515
3595
|
"pre-filled from meta/scenario-manifest.json"
|
|
3516
3596
|
),
|
|
3517
3597
|
)
|
|
3518
|
-
p_receipt.add_argument("--change-dir", required=True)
|
|
3598
|
+
p_receipt.add_argument("--change-dir", "--change", dest="change_dir", required=True)
|
|
3519
3599
|
p_receipt.add_argument(
|
|
3520
3600
|
"--scenario-ids",
|
|
3521
3601
|
required=True,
|
|
@@ -3,7 +3,7 @@ name: harness-apidoc
|
|
|
3
3
|
description: 从 Java 后端代码生成结构化前端 API 文档(Controller/VO/ErrorCode)。仅当用户显式调用
|
|
4
4
|
/harness-apidoc 时使用,不得自动触发。
|
|
5
5
|
---
|
|
6
|
-
<!-- generated by harness_deploy.py; core=
|
|
6
|
+
<!-- generated by harness_deploy.py; core=04743280fadbe12e; overlay=java; agent=codex; do not edit -->
|
|
7
7
|
# harness-apidoc — 前端接口文档(Java overlay)
|
|
8
8
|
|
|
9
9
|
## Purpose
|
|
@@ -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=04743280fadbe12e; overlay=java; 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=04743280fadbe12e; overlay=java; agent=codex; do not edit -->
|
|
7
7
|
# harness-codebase-map — 代码库地图
|
|
8
8
|
|
|
9
9
|
## Purpose
|
|
@@ -4,7 +4,7 @@ description: 执行阶段:按变更簇执行 TDD
|
|
|
4
4
|
编码循环(RED→GREEN→REFACTOR→编译验证),随后完成单元/接口/数据兼容验证并输出测试报告。仅当用户显式调用
|
|
5
5
|
/harness-execute 时使用;不得因用户提到编码/实现/测试就自动触发,也不得被其他阶段 skill 自动接续。
|
|
6
6
|
---
|
|
7
|
-
<!-- generated by harness_deploy.py; core=
|
|
7
|
+
<!-- generated by harness_deploy.py; core=04743280fadbe12e; overlay=java; agent=codex; do not edit -->
|
|
8
8
|
# harness-execute — 执行(编码 + 验证)
|
|
9
9
|
|
|
10
10
|
> 2026-08 阶段合并:原 `run`(编码)与 `test`(验证)合并为单一 `execute` 阶段;
|
|
@@ -61,7 +61,7 @@ description: 执行阶段:按变更簇执行 TDD
|
|
|
61
61
|
|
|
62
62
|
## Workflow 概要
|
|
63
63
|
|
|
64
|
-
0. 加载上下文:先 `harness_context.py prepare --project . --change <id> --phase execute --executor <tool> --json`(`--project` 必填),再 `harness_context.py begin --project . --change <id> --phase execute --executor <tool> --json` 校验交接,最后运行 **`harness_gate.py begin --phase execute --change <id>`**。旧名 `--phase run` / `--phase test` 仍被接受并归一为 execute。<br>**交接凭证不必手工补**:v2 计划的 `plan finalize` 不写 context 事务,`prepare` 会在检测到 committed 的 `meta/publication-journals/*.json` 时自动补录 `plan → execute` 凭证(凭证带 `bootstrapSource=plan_publication_journal` 留痕)。仍报 `HANDOFF_REQUIRED`/`LEGACY_BOOTSTRAP_REQUIRED` 说明**没有**这份发布证据——回到 plan 阶段确认发布是否真的完成,**不得**自己拼 `classify + configure-plan + close` 造凭证。<br>**Fixback** 不得拼装底层步骤,必须只调用一次 `harness_fixback.py launch-review --project . --change <id> --change-dir <change-dir> --executor <tool> --skills-root <skills-root> --product-identity <当前产品身份> --json`;返回 `FIXBACK_NOTHING_TO_APPLY` 时直接报告并停止。禁止手写 `events.ndjson` / `phase.end`。
|
|
64
|
+
0. 加载上下文:先 `harness_context.py prepare --project . --change <id> --phase execute --executor <tool> --json`(`--project` 必填),再 `harness_context.py begin --project . --change <id> --phase execute --executor <tool> --json` 校验交接,最后运行 **`harness_gate.py begin --phase execute --change <id>`**。旧名 `--phase run` / `--phase test` 仍被接受并归一为 execute。<br>Git Bash 注意:`--note` 等字符串参数**不要以 `/` 开头**(MSYS 会把它当路径改写成 `C:/Program Files/Git/...`);以 `/` 开头时先 `MSYS_NO_PATHCONV=1`。<br>**交接凭证不必手工补**:v2 计划的 `plan finalize` 不写 context 事务,`prepare` 会在检测到 committed 的 `meta/publication-journals/*.json` 时自动补录 `plan → execute` 凭证(凭证带 `bootstrapSource=plan_publication_journal` 留痕)。仍报 `HANDOFF_REQUIRED`/`LEGACY_BOOTSTRAP_REQUIRED` 说明**没有**这份发布证据——回到 plan 阶段确认发布是否真的完成,**不得**自己拼 `classify + configure-plan + close` 造凭证。<br>**Fixback** 不得拼装底层步骤,必须只调用一次 `harness_fixback.py launch-review --project . --change <id> --change-dir <change-dir> --executor <tool> --skills-root <skills-root> --product-identity <当前产品身份> --json`;返回 `FIXBACK_NOTHING_TO_APPLY` 时直接报告并停止。禁止手写 `events.ndjson` / `phase.end`。
|
|
65
65
|
0.5. **测试基础设施探测**与**命令执行模式 preflight** → `testing-reference.md`「命令执行模式 preflight」;测试基线已由 gate begin 内部建立,不得再次执行 guard begin
|
|
66
66
|
1. **变更簇 TDD** — `protocols.md` `run-tdd-protocol`;批量 RED/GREEN;按需 `change-cluster-review-protocol`
|
|
67
67
|
2. 构建验证 + **仅**通过 `harness_ledger.py record` 写 ledger(禁止 Write/Edit `verification-ledger.json`);profile 缺失或陈旧时先 `harness_preflight.py detect --project . --json`;`record --project . --profile-input <key>` 从同一 target 推导 scope、coverage、规范命令与输入闭包
|
|
@@ -14,3 +14,4 @@
|
|
|
14
14
|
| 8 | 唯一字段加前缀 | 409/冲突 BLOCKED | 测试数据撞库 | `TEST_<change>_<ts>_<rand>` |
|
|
15
15
|
| 9 | runtime overlay | tracked 配置被改 | 直接 Edit yml | additional-location 叠加 |
|
|
16
16
|
| 10 | Service Gate 先决策 | 旧服务 500 | 端口已有进程 | ensure → needs-user-decision 才问 |
|
|
17
|
+
| 11 | surefire 写死 excludedGroups | `-Dgroups=` 0 命中 + exit 0 假绿 | 插件级配置压过命令行空值覆盖 | pom 改 `${excludedGroups}` 占位;ledger record 会报 ZERO_TESTS_WITH_SELECTOR |
|
|
@@ -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=04743280fadbe12e; overlay=java; agent=codex; do not edit -->
|
|
6
6
|
# harness-knowledge-ingest
|
|
7
7
|
|
|
8
8
|
知识 ingest 完全由 Hunter Platform 负责。归档完成时,客户端生成一个确定性 ZIP;
|
|
@@ -40,6 +40,12 @@ powershell.exe -Command "npx hunter-harness archive upload --file '<archive.zip>
|
|
|
40
40
|
```
|
|
41
41
|
|
|
42
42
|
4. 检查响应中的包哈希、服务端保存状态和 `knowledge_status`。
|
|
43
|
+
4.5. **查询面回读验证(强制)**:`knowledgeStatus=ready` 只是入库回执,不代表可查。
|
|
44
|
+
执行 `npx hunter-harness knowledge status --json` 确认 `pipeline.results_count`
|
|
45
|
+
符合预期(有候选的归档应 > 0);再用归档中的已知关键词跑一次
|
|
46
|
+
`npx hunter-harness knowledge query "<已知关键词>" --json`,`count=0` 视为
|
|
47
|
+
ingest 未真正完成——报告 `pipeline.jobs` 状态(queued/extracting/failed)
|
|
48
|
+
并停止宣称 ready,不得仅凭上传回执收尾。
|
|
43
49
|
5. 只有服务端确认原 ZIP 已持久保存且知识状态为 ready,才删除本地待上传 ZIP。
|
|
44
50
|
6. 上传或 ingest 失败时保留 ZIP 与失败收据;修复连接后重试同一个 ZIP,不重新拼散文件。
|
|
45
51
|
|
|
@@ -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=04743280fadbe12e; overlay=java; agent=codex; do not edit -->
|
|
6
6
|
# harness-knowledge-query
|
|
7
7
|
|
|
8
8
|
项目知识以 Hunter Platform 的服务端索引为唯一真源。客户端只提交查询并消费结果:
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: harness-package
|
|
3
3
|
description: 增量模块打包:拉取最新→编译验证→mvn package→汇总 jar/war。仅当用户显式调用 /harness-package 时使用,不得自动触发。
|
|
4
4
|
---
|
|
5
|
-
<!-- generated by harness_deploy.py; core=
|
|
5
|
+
<!-- generated by harness_deploy.py; core=04743280fadbe12e; overlay=java; agent=codex; do not edit -->
|
|
6
6
|
# harness-package — 增量模块打包(Java overlay)
|
|
7
7
|
|
|
8
8
|
## Purpose
|