@hunter-harness/workflow-harness 0.4.9 → 0.4.11
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 +5 -3
- 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 +12 -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 +29 -19
- package/harness/bundles/general/claude-code/scripts/harness_fixback.py +37 -1
- package/harness/bundles/general/claude-code/scripts/harness_gate.py +124 -10
- package/harness/bundles/general/claude-code/scripts/harness_ledger.py +84 -4
- package/harness/bundles/general/claude-code/scripts/harness_process.py +18 -0
- package/harness/bundles/general/claude-code/scripts/harness_runtime.py +47 -1
- 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 +5 -3
- 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 +12 -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 +29 -19
- package/harness/bundles/general/codebuddy/scripts/harness_fixback.py +37 -1
- package/harness/bundles/general/codebuddy/scripts/harness_gate.py +124 -10
- package/harness/bundles/general/codebuddy/scripts/harness_ledger.py +84 -4
- package/harness/bundles/general/codebuddy/scripts/harness_process.py +18 -0
- package/harness/bundles/general/codebuddy/scripts/harness_runtime.py +47 -1
- 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 +5 -3
- 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 +29 -19
- package/harness/bundles/general/codex/scripts/harness_fixback.py +37 -1
- package/harness/bundles/general/codex/scripts/harness_gate.py +124 -10
- package/harness/bundles/general/codex/scripts/harness_ledger.py +84 -4
- package/harness/bundles/general/codex/scripts/harness_process.py +18 -0
- package/harness/bundles/general/codex/scripts/harness_runtime.py +47 -1
- 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 +5 -3
- 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 +29 -19
- package/harness/bundles/general/cursor/scripts/harness_fixback.py +37 -1
- package/harness/bundles/general/cursor/scripts/harness_gate.py +124 -10
- package/harness/bundles/general/cursor/scripts/harness_ledger.py +84 -4
- package/harness/bundles/general/cursor/scripts/harness_process.py +18 -0
- package/harness/bundles/general/cursor/scripts/harness_runtime.py +47 -1
- 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 +5 -3
- 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 +29 -19
- package/harness/bundles/general/pi/scripts/harness_fixback.py +37 -1
- package/harness/bundles/general/pi/scripts/harness_gate.py +124 -10
- package/harness/bundles/general/pi/scripts/harness_ledger.py +84 -4
- package/harness/bundles/general/pi/scripts/harness_process.py +18 -0
- package/harness/bundles/general/pi/scripts/harness_runtime.py +47 -1
- 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 +5 -3
- 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 +12 -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 +29 -19
- package/harness/bundles/java/claude-code/scripts/harness_fixback.py +37 -1
- package/harness/bundles/java/claude-code/scripts/harness_gate.py +124 -10
- package/harness/bundles/java/claude-code/scripts/harness_ledger.py +84 -4
- package/harness/bundles/java/claude-code/scripts/harness_process.py +18 -0
- package/harness/bundles/java/claude-code/scripts/harness_runtime.py +47 -1
- 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 +5 -3
- 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 +12 -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 +29 -19
- package/harness/bundles/java/codebuddy/scripts/harness_fixback.py +37 -1
- package/harness/bundles/java/codebuddy/scripts/harness_gate.py +124 -10
- package/harness/bundles/java/codebuddy/scripts/harness_ledger.py +84 -4
- package/harness/bundles/java/codebuddy/scripts/harness_process.py +18 -0
- package/harness/bundles/java/codebuddy/scripts/harness_runtime.py +47 -1
- 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 +5 -3
- 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 +29 -19
- package/harness/bundles/java/codex/scripts/harness_fixback.py +37 -1
- package/harness/bundles/java/codex/scripts/harness_gate.py +124 -10
- package/harness/bundles/java/codex/scripts/harness_ledger.py +84 -4
- package/harness/bundles/java/codex/scripts/harness_process.py +18 -0
- package/harness/bundles/java/codex/scripts/harness_runtime.py +47 -1
- 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 +5 -3
- 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 +29 -19
- package/harness/bundles/java/cursor/scripts/harness_fixback.py +37 -1
- package/harness/bundles/java/cursor/scripts/harness_gate.py +124 -10
- package/harness/bundles/java/cursor/scripts/harness_ledger.py +84 -4
- package/harness/bundles/java/cursor/scripts/harness_process.py +18 -0
- package/harness/bundles/java/cursor/scripts/harness_runtime.py +47 -1
- 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 +5 -3
- 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 +29 -19
- package/harness/bundles/java/pi/scripts/harness_fixback.py +37 -1
- package/harness/bundles/java/pi/scripts/harness_gate.py +124 -10
- package/harness/bundles/java/pi/scripts/harness_ledger.py +84 -4
- package/harness/bundles/java/pi/scripts/harness_process.py +18 -0
- package/harness/bundles/java/pi/scripts/harness_runtime.py +47 -1
- package/harness/manifests/general/claude-code.json +22 -22
- package/harness/manifests/general/codebuddy.json +22 -22
- package/harness/manifests/general/codex.json +22 -22
- package/harness/manifests/general/cursor.json +22 -22
- package/harness/manifests/general/pi.json +22 -22
- 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/harness/manifests/java/pi.json +24 -24
- package/hunter-workflow-family.json +2 -2
- package/package.json +1 -1
|
@@ -884,22 +884,32 @@ 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
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
901
|
+
disallowed = [
|
|
902
|
+
f"--{field.replace('_', '-')}"
|
|
903
|
+
for field in OPTIONAL_FIELDS
|
|
904
|
+
if getattr(args, field, None) is not None and field not in allowed
|
|
905
|
+
]
|
|
906
|
+
if disallowed:
|
|
907
|
+
# 一次列出全部不被接受的字段——逐个报错会让调用方每字段往返一轮(R-4)
|
|
908
|
+
return (
|
|
909
|
+
"EVENT_FIELD_NOT_ALLOWED",
|
|
910
|
+
f"EVENT_FIELD_NOT_ALLOWED: {event_type} does not accept "
|
|
911
|
+
+ ", ".join(disallowed),
|
|
912
|
+
)
|
|
903
913
|
for field, length in (("trace_id", 32), ("span_id", 16), ("parent_span_id", 16)):
|
|
904
914
|
value = getattr(args, field, None)
|
|
905
915
|
if value is not None and not re.fullmatch(rf"[0-9a-f]{{{length}}}", str(value)):
|
|
@@ -2146,7 +2156,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
2146
2156
|
parents=[common],
|
|
2147
2157
|
help="append one event and auto-render execution-log",
|
|
2148
2158
|
)
|
|
2149
|
-
p_append.add_argument("--change-dir", required=True)
|
|
2159
|
+
p_append.add_argument("--change-dir", "--change", dest="change_dir", required=True)
|
|
2150
2160
|
p_append.add_argument("--phase", required=True)
|
|
2151
2161
|
p_append.add_argument("--type", required=True)
|
|
2152
2162
|
p_append.add_argument("--command", default=None)
|
|
@@ -2200,7 +2210,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
2200
2210
|
parents=[common],
|
|
2201
2211
|
help="validate and append many events under one lock (H-15)",
|
|
2202
2212
|
)
|
|
2203
|
-
p_batch.add_argument("--change-dir", required=True)
|
|
2213
|
+
p_batch.add_argument("--change-dir", "--change", dest="change_dir", required=True)
|
|
2204
2214
|
p_batch.add_argument(
|
|
2205
2215
|
"--file",
|
|
2206
2216
|
required=True,
|
|
@@ -2213,7 +2223,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
2213
2223
|
parents=[common],
|
|
2214
2224
|
help="re-render execution-log.md from events.ndjson",
|
|
2215
2225
|
)
|
|
2216
|
-
p_render.add_argument("--change-dir", required=True)
|
|
2226
|
+
p_render.add_argument("--change-dir", "--change", dest="change_dir", required=True)
|
|
2217
2227
|
p_render.set_defaults(func=cmd_render)
|
|
2218
2228
|
|
|
2219
2229
|
p_summary = sub.add_parser(
|
|
@@ -2221,7 +2231,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
2221
2231
|
parents=[common],
|
|
2222
2232
|
help="summarize phases/issues from events.ndjson",
|
|
2223
2233
|
)
|
|
2224
|
-
p_summary.add_argument("--change-dir", required=True)
|
|
2234
|
+
p_summary.add_argument("--change-dir", "--change", dest="change_dir", required=True)
|
|
2225
2235
|
p_summary.set_defaults(func=cmd_summary)
|
|
2226
2236
|
|
|
2227
2237
|
return parser
|
|
@@ -75,6 +75,14 @@ def _write_json(path: Path, value: dict[str, Any]) -> None:
|
|
|
75
75
|
temporary.unlink(missing_ok=True)
|
|
76
76
|
|
|
77
77
|
|
|
78
|
+
def _read_json(path: Path) -> Any:
|
|
79
|
+
try:
|
|
80
|
+
value = json.loads(path.read_text(encoding="utf-8-sig"))
|
|
81
|
+
except (OSError, json.JSONDecodeError):
|
|
82
|
+
return None
|
|
83
|
+
return value
|
|
84
|
+
|
|
85
|
+
|
|
78
86
|
def _load(change_dir: Path, batch_id: str) -> dict[str, Any]:
|
|
79
87
|
path = _batch_path(change_dir, batch_id)
|
|
80
88
|
try:
|
|
@@ -1186,7 +1194,21 @@ def evidence_template(
|
|
|
1186
1194
|
"provenance": provenance,
|
|
1187
1195
|
}
|
|
1188
1196
|
target = Path(out) if out else Path("runtime") / f"fixback-{resolved_id}.json"
|
|
1189
|
-
|
|
1197
|
+
if target.is_absolute():
|
|
1198
|
+
path = target
|
|
1199
|
+
else:
|
|
1200
|
+
# F-3:与 ledger --out 同一类坑——相对路径已含 change-dir 前缀时直接拼
|
|
1201
|
+
# change_dir 会产生嵌套幽灵目录(2026-08-31 实测)。前缀命中按 cwd 解析。
|
|
1202
|
+
cwd = Path.cwd().resolve()
|
|
1203
|
+
change_resolved = change_dir.resolve()
|
|
1204
|
+
if _is_within(change_resolved, [cwd]):
|
|
1205
|
+
change_rel = change_resolved.relative_to(cwd)
|
|
1206
|
+
if target.parts[: len(change_rel.parts)] == change_rel.parts:
|
|
1207
|
+
path = cwd / target
|
|
1208
|
+
else:
|
|
1209
|
+
path = change_dir / target
|
|
1210
|
+
else:
|
|
1211
|
+
path = change_dir / target
|
|
1190
1212
|
path = path.resolve()
|
|
1191
1213
|
if not _is_within(path, [change_dir.resolve(), state_root, _project_root(change_dir)]):
|
|
1192
1214
|
return {
|
|
@@ -1708,6 +1730,20 @@ def close_batch(
|
|
|
1708
1730
|
}
|
|
1709
1731
|
)
|
|
1710
1732
|
_write_json(_batch_path(change_dir, batch_id), batch)
|
|
1733
|
+
# F-5:批次关闭会同步把托管会话置为 CLOSED——否则 session 永远 ACTIVE,
|
|
1734
|
+
# 后续 launch-review 被 CONTEXT_PREPARATION_ACTIVE 幂等保护误拦(2026-08-31
|
|
1735
|
+
# demo-datasource 实测)。
|
|
1736
|
+
session_file = _session_path(change_dir)
|
|
1737
|
+
session = _read_json(session_file)
|
|
1738
|
+
if (
|
|
1739
|
+
isinstance(session, dict)
|
|
1740
|
+
and session.get("batchId") == batch_id
|
|
1741
|
+
and session.get("status") == "ACTIVE"
|
|
1742
|
+
):
|
|
1743
|
+
session["status"] = "CLOSED"
|
|
1744
|
+
session["nextStep"] = "done"
|
|
1745
|
+
session["updatedAt"] = closed_at
|
|
1746
|
+
_write_json(session_file, session)
|
|
1711
1747
|
return batch
|
|
1712
1748
|
|
|
1713
1749
|
|
|
@@ -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",
|
|
@@ -3039,6 +3105,20 @@ def _resume_closed_phase(
|
|
|
3039
3105
|
for candidate in hctx.allowed_next_phases(project, change_id, args.phase)
|
|
3040
3106
|
if hp.resolve_phase_name(candidate) != args.phase
|
|
3041
3107
|
]
|
|
3108
|
+
# F-4:续跑路径同样——fixback 回环的 execute 关门应回 review 的后继
|
|
3109
|
+
if args.phase == "execute" and isinstance(transitions, list) and transitions:
|
|
3110
|
+
latest_t = transitions[-1]
|
|
3111
|
+
if (
|
|
3112
|
+
isinstance(latest_t, dict)
|
|
3113
|
+
and hp.resolve_phase_name(latest_t.get("fromPhase")) == "review"
|
|
3114
|
+
and hp.resolve_phase_name(latest_t.get("toPhase")) == "execute"
|
|
3115
|
+
and latest_t.get("trigger") == "review-fixback"
|
|
3116
|
+
):
|
|
3117
|
+
candidates = [
|
|
3118
|
+
candidate
|
|
3119
|
+
for candidate in hctx.allowed_next_phases(project, change_id, "review")
|
|
3120
|
+
if hp.resolve_phase_name(candidate) != args.phase
|
|
3121
|
+
]
|
|
3042
3122
|
if to_phase is None and len(candidates) == 1:
|
|
3043
3123
|
# 计划后继唯一(排除 fixback 自环):close 的意图没有歧义,直接补跑 handoff
|
|
3044
3124
|
to_phase = candidates[0]
|
|
@@ -3625,10 +3705,25 @@ def cmd_close(args: argparse.Namespace) -> int:
|
|
|
3625
3705
|
and context_view.get("ok")
|
|
3626
3706
|
and isinstance(context_view.get("current"), dict)
|
|
3627
3707
|
):
|
|
3708
|
+
# F-4:fixback 回环(最新交接是 review→execute 且 trigger=
|
|
3709
|
+
# review-fixback)关门时,后继不是再来一轮 review,而是 review
|
|
3710
|
+
# 当时的计划后继(submit)。不按此处理会把 transitions 链写成
|
|
3711
|
+
# …→review→execute→review 的语义错乱(2026-08-31 实测)。
|
|
3712
|
+
source_phase = args.phase
|
|
3713
|
+
transitions = context_view.get("transitions") or []
|
|
3714
|
+
if args.phase == "execute" and transitions:
|
|
3715
|
+
latest = transitions[-1]
|
|
3716
|
+
if (
|
|
3717
|
+
isinstance(latest, dict)
|
|
3718
|
+
and hp.resolve_phase_name(latest.get("fromPhase")) == "review"
|
|
3719
|
+
and hp.resolve_phase_name(latest.get("toPhase")) == "execute"
|
|
3720
|
+
and latest.get("trigger") == "review-fixback"
|
|
3721
|
+
):
|
|
3722
|
+
source_phase = "review"
|
|
3628
3723
|
forward = [
|
|
3629
3724
|
candidate
|
|
3630
3725
|
for candidate in hctx.allowed_next_phases(
|
|
3631
|
-
project, str(resolved["changeId"]),
|
|
3726
|
+
project, str(resolved["changeId"]), source_phase
|
|
3632
3727
|
)
|
|
3633
3728
|
if hp.resolve_phase_name(candidate) != args.phase
|
|
3634
3729
|
]
|
|
@@ -3800,11 +3895,25 @@ def cmd_classify(args: argparse.Namespace) -> int:
|
|
|
3800
3895
|
payload["classifiedAt"] = classified_at
|
|
3801
3896
|
|
|
3802
3897
|
if change_dir is not None and change_dir.is_dir():
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3898
|
+
finalized = (change_dir / "meta" / "plan-profile.json").is_file()
|
|
3899
|
+
if finalized and not getattr(args, "force", False):
|
|
3900
|
+
# P1-1:classify 形态像只读查询,实际是写操作。已发布的 change
|
|
3901
|
+
# 被误跑 classify 会覆盖 configure-plan 写入的工作副本(2026-08-30
|
|
3902
|
+
# demo-datasource 实测 plannedPhases 被清)。默认拒写并显式提示。
|
|
3903
|
+
payload["policyPersisted"] = False
|
|
3904
|
+
payload["warning"] = (
|
|
3905
|
+
"change 已发布(meta/plan-profile.json 存在),classify 不再重写 "
|
|
3906
|
+
"meta/gate-policy.json 工作副本;确需重算请显式传 --force"
|
|
3907
|
+
)
|
|
3908
|
+
payload["writeGuarded"] = True
|
|
3909
|
+
else:
|
|
3910
|
+
policy_doc = gate_policy_document(payload)
|
|
3911
|
+
policy_path = change_dir / "meta" / "gate-policy.json"
|
|
3912
|
+
_write_json(policy_path, policy_doc)
|
|
3913
|
+
payload["policyPersisted"] = True
|
|
3914
|
+
payload["policyPath"] = str(policy_path)
|
|
3915
|
+
if finalized:
|
|
3916
|
+
payload["warning"] = "--force:已发布 change 的工作副本已被重写"
|
|
3808
3917
|
else:
|
|
3809
3918
|
payload["policyPersisted"] = False
|
|
3810
3919
|
payload["warning"] = (
|
|
@@ -3930,7 +4039,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
3930
4039
|
|
|
3931
4040
|
p_begin = sub.add_parser("begin", parents=[shared])
|
|
3932
4041
|
p_begin.add_argument("--phase", required=True)
|
|
3933
|
-
p_begin.add_argument("--change", default=None)
|
|
4042
|
+
p_begin.add_argument("--change", "--change-dir", dest="change", default=None)
|
|
3934
4043
|
p_begin.add_argument(
|
|
3935
4044
|
"--project",
|
|
3936
4045
|
default=None,
|
|
@@ -3960,7 +4069,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
3960
4069
|
help="关闭阶段门禁(关门只调本命令;上下文交接由 --to-phase 内联完成,不要再单独调 harness_context.py close)",
|
|
3961
4070
|
)
|
|
3962
4071
|
p_close.add_argument("--phase", required=True)
|
|
3963
|
-
p_close.add_argument("--change", default=None)
|
|
4072
|
+
p_close.add_argument("--change", "--change-dir", dest="change", default=None)
|
|
3964
4073
|
p_close.add_argument(
|
|
3965
4074
|
"--project",
|
|
3966
4075
|
default=None,
|
|
@@ -3985,7 +4094,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
3985
4094
|
|
|
3986
4095
|
p_classify = sub.add_parser("classify", parents=[shared])
|
|
3987
4096
|
p_classify.add_argument("--project", default=None, type=Path, help=_PROJECT_ROOT_HELP)
|
|
3988
|
-
p_classify.add_argument("--change", default=None)
|
|
4097
|
+
p_classify.add_argument("--change", "--change-dir", dest="change", default=None)
|
|
3989
4098
|
p_classify.add_argument("--stage", required=True, choices=["plan", "post-run"])
|
|
3990
4099
|
p_classify.add_argument(
|
|
3991
4100
|
"--tier-override",
|
|
@@ -3993,13 +4102,18 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
3993
4102
|
choices=["fast", "standard", "full"],
|
|
3994
4103
|
)
|
|
3995
4104
|
p_classify.add_argument("--override-by", default="user")
|
|
4105
|
+
p_classify.add_argument(
|
|
4106
|
+
"--force",
|
|
4107
|
+
action="store_true",
|
|
4108
|
+
help="已发布 change 也强制重写 gate-policy.json 工作副本",
|
|
4109
|
+
)
|
|
3996
4110
|
p_classify.set_defaults(func=cmd_classify)
|
|
3997
4111
|
|
|
3998
4112
|
p_checkpoint = sub.add_parser("checkpoint", parents=[shared])
|
|
3999
4113
|
p_checkpoint.add_argument("--project", default=None, type=Path, help=_PROJECT_ROOT_HELP)
|
|
4000
4114
|
p_checkpoint.add_argument("checkpoint_action", choices=["status", "approve"])
|
|
4001
4115
|
p_checkpoint.add_argument("--id", required=True)
|
|
4002
|
-
p_checkpoint.add_argument("--change", default=None)
|
|
4116
|
+
p_checkpoint.add_argument("--change", "--change-dir", dest="change", default=None)
|
|
4003
4117
|
p_checkpoint.add_argument("--reviewer", default=None)
|
|
4004
4118
|
p_checkpoint.set_defaults(func=cmd_checkpoint)
|
|
4005
4119
|
|
|
@@ -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,
|
|
@@ -15,6 +15,7 @@ import json
|
|
|
15
15
|
import os
|
|
16
16
|
from pathlib import Path
|
|
17
17
|
import signal
|
|
18
|
+
import shutil
|
|
18
19
|
import subprocess
|
|
19
20
|
import sys
|
|
20
21
|
import time
|
|
@@ -946,6 +947,23 @@ def capture_owned_members(spawned: SpawnedProcess) -> dict[str, Any] | None:
|
|
|
946
947
|
return proof
|
|
947
948
|
|
|
948
949
|
|
|
950
|
+
def resolve_windows_executable(argv: Sequence[str]) -> list[str]:
|
|
951
|
+
"""Windows 无扩展名命令解析为实际可执行文件(F-1)。
|
|
952
|
+
|
|
953
|
+
CreateProcess 不走 shell,`mvn`/`npm` 这类 .cmd 包装会报 WinError 2;
|
|
954
|
+
shutil.which 遵循 PATHEXT 能找到 mvn.cmd。POSIX 或未命中时原样返回。
|
|
955
|
+
"""
|
|
956
|
+
if os.name != "nt" or not argv:
|
|
957
|
+
return list(argv)
|
|
958
|
+
head = argv[0]
|
|
959
|
+
if not isinstance(head, str) or not head or os.path.splitext(head)[1]:
|
|
960
|
+
return list(argv)
|
|
961
|
+
resolved = shutil.which(head)
|
|
962
|
+
if resolved is None:
|
|
963
|
+
return list(argv)
|
|
964
|
+
return [resolved, *argv[1:]]
|
|
965
|
+
|
|
966
|
+
|
|
949
967
|
def spawn_structured_argv(
|
|
950
968
|
argv: Sequence[str],
|
|
951
969
|
*,
|
|
@@ -1099,6 +1099,9 @@ def _spawn_detached_worker(
|
|
|
1099
1099
|
env: dict[str, str],
|
|
1100
1100
|
output: Any = subprocess.DEVNULL,
|
|
1101
1101
|
) -> subprocess.Popen[bytes]:
|
|
1102
|
+
from harness_process import resolve_windows_executable
|
|
1103
|
+
|
|
1104
|
+
argv = resolve_windows_executable(argv)
|
|
1102
1105
|
kwargs: dict[str, Any] = {
|
|
1103
1106
|
"cwd": str(cwd),
|
|
1104
1107
|
"env": env,
|
|
@@ -1171,6 +1174,40 @@ def start_run_session(
|
|
|
1171
1174
|
working_directory = working_directory.expanduser().resolve()
|
|
1172
1175
|
if not working_directory.is_dir():
|
|
1173
1176
|
raise ValueError(f"RUN_SESSION_WORKDIR_MISSING: {working_directory}")
|
|
1177
|
+
# F-2:fixback 会话缺 product-identity 时白跑一轮、register 才拒。批次开着
|
|
1178
|
+
# 就从 baseProductIdentity 自动注入;没有 OPEN 批次则当场拒绝(2026-08-31
|
|
1179
|
+
# demo-datasource 实测)。
|
|
1180
|
+
if verification.startswith("fixback") and not (
|
|
1181
|
+
product_identity and product_identity.strip()
|
|
1182
|
+
):
|
|
1183
|
+
batches_dir = state_root / "fixback" / "batches"
|
|
1184
|
+
open_identity: str | None = None
|
|
1185
|
+
open_batch_id: str | None = None
|
|
1186
|
+
if batches_dir.is_dir():
|
|
1187
|
+
for batch_file in sorted(batches_dir.glob("*.json")):
|
|
1188
|
+
try:
|
|
1189
|
+
batch = json.loads(batch_file.read_text(encoding="utf-8-sig"))
|
|
1190
|
+
except (OSError, json.JSONDecodeError):
|
|
1191
|
+
continue
|
|
1192
|
+
if (
|
|
1193
|
+
isinstance(batch, dict)
|
|
1194
|
+
and batch.get("status") == "OPEN"
|
|
1195
|
+
and isinstance(batch.get("baseProductIdentity"), str)
|
|
1196
|
+
and batch["baseProductIdentity"].strip()
|
|
1197
|
+
):
|
|
1198
|
+
open_identity = str(batch["baseProductIdentity"]).strip()
|
|
1199
|
+
open_batch_id = str(batch.get("batchId") or "")
|
|
1200
|
+
break
|
|
1201
|
+
if open_identity is None:
|
|
1202
|
+
raise ValueError(
|
|
1203
|
+
"FIXBACK_PRODUCT_IDENTITY_REQUIRED: fixback 会话必须有产品身份——"
|
|
1204
|
+
"传 --product-identity,或先启动 fixback 批次(其 baseProductIdentity "
|
|
1205
|
+
"会被自动注入)"
|
|
1206
|
+
)
|
|
1207
|
+
product_identity = open_identity
|
|
1208
|
+
product_identity_source = f"fixback-batch:{open_batch_id}"
|
|
1209
|
+
else:
|
|
1210
|
+
product_identity_source = None
|
|
1174
1211
|
session_id = session_id or f"run-{uuid.uuid4().hex}"
|
|
1175
1212
|
run_id = run_id or session_id
|
|
1176
1213
|
session_root = _run_session_root(state_root, session_id)
|
|
@@ -1215,6 +1252,11 @@ def start_run_session(
|
|
|
1215
1252
|
"heartbeatSeconds": heartbeat_seconds,
|
|
1216
1253
|
"expectedDurationSeconds": expected_duration_seconds,
|
|
1217
1254
|
"productIdentity": product_identity,
|
|
1255
|
+
**(
|
|
1256
|
+
{"productIdentitySource": product_identity_source}
|
|
1257
|
+
if product_identity_source is not None
|
|
1258
|
+
else {}
|
|
1259
|
+
),
|
|
1218
1260
|
"resourceLocks": normalized_resource_locks,
|
|
1219
1261
|
"resourceLockTokens": {},
|
|
1220
1262
|
"resourceWaitMs": 0,
|
|
@@ -1427,8 +1469,12 @@ def _run_session_worker(state_root: Path, session_id: str) -> int:
|
|
|
1427
1469
|
"wb", buffering=0
|
|
1428
1470
|
) as stderr:
|
|
1429
1471
|
try:
|
|
1430
|
-
from harness_process import
|
|
1472
|
+
from harness_process import (
|
|
1473
|
+
resolve_windows_executable,
|
|
1474
|
+
spawn_structured_argv,
|
|
1475
|
+
)
|
|
1431
1476
|
|
|
1477
|
+
argv = resolve_windows_executable(argv)
|
|
1432
1478
|
spawned = spawn_structured_argv(
|
|
1433
1479
|
argv,
|
|
1434
1480
|
cwd=Path(str(receipt["workingDirectory"])),
|
|
@@ -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=55a60d625b7500ac; overlay=none; agent=cursor; 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=55a60d625b7500ac; overlay=none; agent=cursor; 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=55a60d625b7500ac; overlay=none; agent=cursor; do not edit -->
|
|
8
8
|
# harness-execute — 执行(编码 + 验证)
|
|
9
9
|
|
|
10
10
|
> 2026-08 阶段合并:原 `run`(编码)与 `test`(验证)合并为单一 `execute` 阶段;
|
|
@@ -61,13 +61,15 @@ 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
|
+
|
|
66
|
+
**Fixback 步骤序列**(0.4.11 起文档化;每步失败都有 recoveryAction):launch-review 启动批次 → 托管 RED 会话(`harness_runtime.py run-start --verification fixback-red --product-identity <批次 baseProductIdentity,缺省自动注入> -- ...`)→ `harness_fixback.py evidence-template` 生成证据 + `register-evidence` 注册 → 修复 → GREEN 会话同法 → 逐 issue `resolve-issue` → `harness_review.py write-dispositions` 处置 → affected 验证会话 + review 收据 → `harness_fixback.py close --change-dir <change-dir> --batch-id <批次> --final-product-identity <身份>`(批次关闭会同步把 fixback-session 置 CLOSED)→ `harness_gate.py close --phase execute`(fixback 回环自动派生回 submit)。Windows 上命令名写 `mvn` 会 WinError 2——0.4.11 起 launcher 自动解析 .cmd/.exe,更早版本请写全路径。
|
|
65
67
|
0.5. **测试基础设施探测**与**命令执行模式 preflight** → `testing-reference.md`「命令执行模式 preflight」;测试基线已由 gate begin 内部建立,不得再次执行 guard begin
|
|
66
68
|
1. **变更簇 TDD** — `protocols.md` `run-tdd-protocol`;批量 RED/GREEN;按需 `change-cluster-review-protocol`
|
|
67
69
|
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、规范命令与输入闭包
|
|
68
70
|
3. **验证执行**:单元测试可复用则跳过(`harness_ledger.py can-reuse`);接口测试**强制批量执行器**一次跑完全部场景;数据兼容验证按场景表执行 → `testing-reference.md`
|
|
69
71
|
4. **场景覆盖检查**(场景表映射,禁止用用例数冒充场景数)
|
|
70
|
-
5. **关门检查**(10 项)→ 只执行一次 `harness_gate.py close`;`--to-phase` 可省略——计划后继唯一(排除 fixback 自环)时自动派生并交接(输出含 `derivedToPhase`)。仅 fixback 回环时显式传 `--to-phase execute
|
|
72
|
+
5. **关门检查**(10 项)→ 只执行一次 `harness_gate.py close`;`--to-phase` 可省略——计划后继唯一(排除 fixback 自环)时自动派生并交接(输出含 `derivedToPhase`)。仅 fixback 回环时显式传 `--to-phase execute`;fixback 批次的 execute 关门自动派生 review 的后继(submit),不会错误地再回一轮 review(0.4.11 起)。该命令内部关闭 test guard、写 `phase.end`、释放租约、写 handoff 并补传事件;不得再单独调用 test-guard/context close。失败时按结构化 `recoveryAction` 原样重试,已完成步骤幂等复用。
|
|
71
73
|
|
|
72
74
|
**阶段归属规则**:只用 `ownerPhase=execute` 的任务和场景判定本阶段结果。`ownerPhase=review`/`submit` 的场景按计划留给后续阶段属于正常移交,出现在关门返回值的 `deferred` 里,不阻断 execute。合并前 `ownerPhase=run`/`test` 的旧清单经别名表归一为 execute,在 execute 关门时一并到期。
|
|
73
75
|
|