@hunter-harness/workflow-harness 0.2.84 → 0.2.86
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 +11 -4
- package/harness/bundles/general/claude-code/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-plan/SKILL.md +3 -2
- package/harness/bundles/general/claude-code/harness-plan/checklist.md +18 -79
- package/harness/bundles/general/claude-code/harness-plan/protocols.md +3 -1
- package/harness/bundles/general/claude-code/harness-plan/reference.md +37 -8
- 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-run/SKILL.md +6 -4
- package/harness/bundles/general/claude-code/harness-submit/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-test/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/scripts/harness_archive.py +58 -25
- package/harness/bundles/general/claude-code/scripts/harness_change.py +25 -7
- package/harness/bundles/general/claude-code/scripts/harness_context.py +62 -19
- package/harness/bundles/general/claude-code/scripts/harness_fixback.py +4 -0
- package/harness/bundles/general/claude-code/scripts/harness_gate.py +208 -72
- package/harness/bundles/general/claude-code/scripts/harness_ledger.py +43 -0
- package/harness/bundles/general/claude-code/scripts/harness_paths.py +60 -0
- package/harness/bundles/general/claude-code/scripts/harness_phase.py +24 -6
- package/harness/bundles/general/claude-code/scripts/harness_plan_finalize.py +124 -1
- package/harness/bundles/general/claude-code/scripts/harness_review.py +25 -4
- package/harness/bundles/general/codebuddy/.harness-build.json +1 -1
- package/harness/bundles/general/codebuddy/harness-archive/SKILL.md +11 -4
- package/harness/bundles/general/codebuddy/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-plan/SKILL.md +3 -2
- package/harness/bundles/general/codebuddy/harness-plan/checklist.md +18 -79
- package/harness/bundles/general/codebuddy/harness-plan/protocols.md +3 -1
- package/harness/bundles/general/codebuddy/harness-plan/reference.md +37 -8
- 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-run/SKILL.md +6 -4
- package/harness/bundles/general/codebuddy/harness-submit/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-test/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/scripts/harness_archive.py +58 -25
- package/harness/bundles/general/codebuddy/scripts/harness_change.py +25 -7
- package/harness/bundles/general/codebuddy/scripts/harness_context.py +62 -19
- package/harness/bundles/general/codebuddy/scripts/harness_fixback.py +4 -0
- package/harness/bundles/general/codebuddy/scripts/harness_gate.py +208 -72
- package/harness/bundles/general/codebuddy/scripts/harness_ledger.py +43 -0
- package/harness/bundles/general/codebuddy/scripts/harness_paths.py +60 -0
- package/harness/bundles/general/codebuddy/scripts/harness_phase.py +24 -6
- package/harness/bundles/general/codebuddy/scripts/harness_plan_finalize.py +124 -1
- package/harness/bundles/general/codebuddy/scripts/harness_review.py +25 -4
- package/harness/bundles/general/codex/.harness-build.json +1 -1
- package/harness/bundles/general/codex/harness-archive/SKILL.md +11 -4
- package/harness/bundles/general/codex/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-plan/SKILL.md +3 -2
- package/harness/bundles/general/codex/harness-plan/checklist.md +18 -79
- package/harness/bundles/general/codex/harness-plan/protocols.md +3 -1
- package/harness/bundles/general/codex/harness-plan/reference.md +37 -8
- 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-run/SKILL.md +6 -4
- package/harness/bundles/general/codex/harness-submit/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-test/SKILL.md +1 -1
- package/harness/bundles/general/codex/scripts/harness_archive.py +58 -25
- package/harness/bundles/general/codex/scripts/harness_change.py +25 -7
- package/harness/bundles/general/codex/scripts/harness_context.py +62 -19
- package/harness/bundles/general/codex/scripts/harness_fixback.py +4 -0
- package/harness/bundles/general/codex/scripts/harness_gate.py +208 -72
- package/harness/bundles/general/codex/scripts/harness_ledger.py +43 -0
- package/harness/bundles/general/codex/scripts/harness_paths.py +60 -0
- package/harness/bundles/general/codex/scripts/harness_phase.py +24 -6
- package/harness/bundles/general/codex/scripts/harness_plan_finalize.py +124 -1
- package/harness/bundles/general/codex/scripts/harness_review.py +25 -4
- package/harness/bundles/general/cursor/.harness-build.json +1 -1
- package/harness/bundles/general/cursor/harness-archive/SKILL.md +11 -4
- package/harness/bundles/general/cursor/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-plan/SKILL.md +3 -2
- package/harness/bundles/general/cursor/harness-plan/checklist.md +18 -79
- package/harness/bundles/general/cursor/harness-plan/protocols.md +3 -1
- package/harness/bundles/general/cursor/harness-plan/reference.md +37 -8
- 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-run/SKILL.md +6 -4
- package/harness/bundles/general/cursor/harness-submit/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-test/SKILL.md +1 -1
- package/harness/bundles/general/cursor/scripts/harness_archive.py +58 -25
- package/harness/bundles/general/cursor/scripts/harness_change.py +25 -7
- package/harness/bundles/general/cursor/scripts/harness_context.py +62 -19
- package/harness/bundles/general/cursor/scripts/harness_fixback.py +4 -0
- package/harness/bundles/general/cursor/scripts/harness_gate.py +208 -72
- package/harness/bundles/general/cursor/scripts/harness_ledger.py +43 -0
- package/harness/bundles/general/cursor/scripts/harness_paths.py +60 -0
- package/harness/bundles/general/cursor/scripts/harness_phase.py +24 -6
- package/harness/bundles/general/cursor/scripts/harness_plan_finalize.py +124 -1
- package/harness/bundles/general/cursor/scripts/harness_review.py +25 -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 +11 -4
- package/harness/bundles/java/claude-code/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-package/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-plan/SKILL.md +3 -2
- package/harness/bundles/java/claude-code/harness-plan/checklist.md +18 -79
- package/harness/bundles/java/claude-code/harness-plan/protocols.md +3 -1
- package/harness/bundles/java/claude-code/harness-plan/reference.md +37 -8
- 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-run/SKILL.md +6 -4
- package/harness/bundles/java/claude-code/harness-submit/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-test/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/scripts/harness_archive.py +58 -25
- package/harness/bundles/java/claude-code/scripts/harness_change.py +25 -7
- package/harness/bundles/java/claude-code/scripts/harness_context.py +62 -19
- package/harness/bundles/java/claude-code/scripts/harness_fixback.py +4 -0
- package/harness/bundles/java/claude-code/scripts/harness_gate.py +208 -72
- package/harness/bundles/java/claude-code/scripts/harness_ledger.py +43 -0
- package/harness/bundles/java/claude-code/scripts/harness_paths.py +60 -0
- package/harness/bundles/java/claude-code/scripts/harness_phase.py +24 -6
- package/harness/bundles/java/claude-code/scripts/harness_plan_finalize.py +124 -1
- package/harness/bundles/java/claude-code/scripts/harness_review.py +25 -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 +11 -4
- package/harness/bundles/java/codebuddy/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-package/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-plan/SKILL.md +3 -2
- package/harness/bundles/java/codebuddy/harness-plan/checklist.md +18 -79
- package/harness/bundles/java/codebuddy/harness-plan/protocols.md +3 -1
- package/harness/bundles/java/codebuddy/harness-plan/reference.md +37 -8
- 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-run/SKILL.md +6 -4
- package/harness/bundles/java/codebuddy/harness-submit/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-test/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/scripts/harness_archive.py +58 -25
- package/harness/bundles/java/codebuddy/scripts/harness_change.py +25 -7
- package/harness/bundles/java/codebuddy/scripts/harness_context.py +62 -19
- package/harness/bundles/java/codebuddy/scripts/harness_fixback.py +4 -0
- package/harness/bundles/java/codebuddy/scripts/harness_gate.py +208 -72
- package/harness/bundles/java/codebuddy/scripts/harness_ledger.py +43 -0
- package/harness/bundles/java/codebuddy/scripts/harness_paths.py +60 -0
- package/harness/bundles/java/codebuddy/scripts/harness_phase.py +24 -6
- package/harness/bundles/java/codebuddy/scripts/harness_plan_finalize.py +124 -1
- package/harness/bundles/java/codebuddy/scripts/harness_review.py +25 -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 +11 -4
- package/harness/bundles/java/codex/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-package/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-plan/SKILL.md +3 -2
- package/harness/bundles/java/codex/harness-plan/checklist.md +18 -79
- package/harness/bundles/java/codex/harness-plan/protocols.md +3 -1
- package/harness/bundles/java/codex/harness-plan/reference.md +37 -8
- 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-run/SKILL.md +6 -4
- package/harness/bundles/java/codex/harness-submit/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-test/SKILL.md +1 -1
- package/harness/bundles/java/codex/scripts/harness_archive.py +58 -25
- package/harness/bundles/java/codex/scripts/harness_change.py +25 -7
- package/harness/bundles/java/codex/scripts/harness_context.py +62 -19
- package/harness/bundles/java/codex/scripts/harness_fixback.py +4 -0
- package/harness/bundles/java/codex/scripts/harness_gate.py +208 -72
- package/harness/bundles/java/codex/scripts/harness_ledger.py +43 -0
- package/harness/bundles/java/codex/scripts/harness_paths.py +60 -0
- package/harness/bundles/java/codex/scripts/harness_phase.py +24 -6
- package/harness/bundles/java/codex/scripts/harness_plan_finalize.py +124 -1
- package/harness/bundles/java/codex/scripts/harness_review.py +25 -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 +11 -4
- package/harness/bundles/java/cursor/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-package/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-plan/SKILL.md +3 -2
- package/harness/bundles/java/cursor/harness-plan/checklist.md +18 -79
- package/harness/bundles/java/cursor/harness-plan/protocols.md +3 -1
- package/harness/bundles/java/cursor/harness-plan/reference.md +37 -8
- 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-run/SKILL.md +6 -4
- package/harness/bundles/java/cursor/harness-submit/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-test/SKILL.md +1 -1
- package/harness/bundles/java/cursor/scripts/harness_archive.py +58 -25
- package/harness/bundles/java/cursor/scripts/harness_change.py +25 -7
- package/harness/bundles/java/cursor/scripts/harness_context.py +62 -19
- package/harness/bundles/java/cursor/scripts/harness_fixback.py +4 -0
- package/harness/bundles/java/cursor/scripts/harness_gate.py +208 -72
- package/harness/bundles/java/cursor/scripts/harness_ledger.py +43 -0
- package/harness/bundles/java/cursor/scripts/harness_paths.py +60 -0
- package/harness/bundles/java/cursor/scripts/harness_phase.py +24 -6
- package/harness/bundles/java/cursor/scripts/harness_plan_finalize.py +124 -1
- package/harness/bundles/java/cursor/scripts/harness_review.py +25 -4
- package/harness/manifests/general/claude-code.json +27 -27
- package/harness/manifests/general/codebuddy.json +27 -27
- package/harness/manifests/general/codex.json +27 -27
- package/harness/manifests/general/cursor.json +27 -27
- package/harness/manifests/java/claude-code.json +29 -29
- package/harness/manifests/java/codebuddy.json +29 -29
- package/harness/manifests/java/codex.json +29 -29
- package/harness/manifests/java/cursor.json +29 -29
- package/hunter-workflow-family.json +4 -4
- package/package.json +1 -1
|
@@ -28,6 +28,7 @@ if str(SCRIPTS_DIR) not in sys.path:
|
|
|
28
28
|
sys.path.insert(0, str(SCRIPTS_DIR))
|
|
29
29
|
|
|
30
30
|
import harness_paths # noqa: E402
|
|
31
|
+
import harness_plan_finalize as hpf # noqa: E402
|
|
31
32
|
import harness_profile # noqa: E402
|
|
32
33
|
|
|
33
34
|
|
|
@@ -1629,6 +1630,24 @@ def _scenario_receipt_error(code: str, message: str) -> dict[str, Any]:
|
|
|
1629
1630
|
return {"ok": False, "code": code, "error": message}
|
|
1630
1631
|
|
|
1631
1632
|
|
|
1633
|
+
def resolve_scenario_manifest(manifest: Any) -> dict[str, Any]:
|
|
1634
|
+
"""把 v2 artifact 包装体解成 legacy 形状;legacy 输入原样返回。
|
|
1635
|
+
|
|
1636
|
+
解包规则住在 harness_plan_finalize(它定义了 legacy manifest 的 schema),
|
|
1637
|
+
门禁与本模块共用同一份,两边不会各推一套。
|
|
1638
|
+
|
|
1639
|
+
此前这里只判包装体、一律 fail-closed;更早的时候连判都不判——cmd_record
|
|
1640
|
+
的版本探测初值是 0,包装体探测不出 schemaVersion 就停在 0,
|
|
1641
|
+
`manifest_schema >= 2` 判假,--scenario-receipt-file 的强制要求被静默跳过。
|
|
1642
|
+
|
|
1643
|
+
返回 ``{"ok": True, "manifest": ...}`` 或 ``{"ok": False, "code": ...}``。
|
|
1644
|
+
"""
|
|
1645
|
+
unpacked = hpf.unpack_v2_scenario_manifest(manifest)
|
|
1646
|
+
if unpacked is None:
|
|
1647
|
+
return {"ok": True, "manifest": manifest}
|
|
1648
|
+
return unpacked
|
|
1649
|
+
|
|
1650
|
+
|
|
1632
1651
|
def _resolve_receipt_path(
|
|
1633
1652
|
raw: str, change_dir: Path
|
|
1634
1653
|
) -> tuple[Path | None, list[Path]]:
|
|
@@ -1694,6 +1713,12 @@ def validate_scenario_execution_receipt(
|
|
|
1694
1713
|
)
|
|
1695
1714
|
except (OSError, json.JSONDecodeError) as exc:
|
|
1696
1715
|
return _scenario_receipt_error("SCENARIO_MANIFEST_INVALID", str(exc))
|
|
1716
|
+
resolved = resolve_scenario_manifest(manifest)
|
|
1717
|
+
if not resolved["ok"]:
|
|
1718
|
+
return _scenario_receipt_error(
|
|
1719
|
+
str(resolved["code"]), str(resolved.get("message") or "")
|
|
1720
|
+
)
|
|
1721
|
+
manifest = resolved["manifest"]
|
|
1697
1722
|
if not isinstance(manifest, dict) or not isinstance(manifest.get("scenarios"), list):
|
|
1698
1723
|
return _scenario_receipt_error(
|
|
1699
1724
|
"SCENARIO_MANIFEST_INVALID",
|
|
@@ -2827,6 +2852,15 @@ def cmd_record(args: argparse.Namespace) -> int:
|
|
|
2827
2852
|
as_json=as_json,
|
|
2828
2853
|
error_code="SCENARIO_MANIFEST_INVALID",
|
|
2829
2854
|
)
|
|
2855
|
+
resolved_probe = resolve_scenario_manifest(manifest_probe)
|
|
2856
|
+
if not resolved_probe["ok"]:
|
|
2857
|
+
return emit_error(
|
|
2858
|
+
str(resolved_probe.get("message") or ""),
|
|
2859
|
+
as_json=as_json,
|
|
2860
|
+
error_code=str(resolved_probe["code"]),
|
|
2861
|
+
extra={"artifactType": "scenario_manifest"},
|
|
2862
|
+
)
|
|
2863
|
+
manifest_probe = resolved_probe["manifest"]
|
|
2830
2864
|
raw_schema = (
|
|
2831
2865
|
manifest_probe.get("schemaVersion")
|
|
2832
2866
|
if isinstance(manifest_probe, dict)
|
|
@@ -3064,6 +3098,15 @@ def cmd_scenario_receipt_template(args: argparse.Namespace) -> int:
|
|
|
3064
3098
|
as_json=as_json,
|
|
3065
3099
|
error_code="SCENARIO_MANIFEST_INVALID",
|
|
3066
3100
|
)
|
|
3101
|
+
resolved = resolve_scenario_manifest(manifest)
|
|
3102
|
+
if not resolved["ok"]:
|
|
3103
|
+
return emit_error(
|
|
3104
|
+
str(resolved.get("message") or ""),
|
|
3105
|
+
as_json=as_json,
|
|
3106
|
+
error_code=str(resolved["code"]),
|
|
3107
|
+
extra={"artifactType": "scenario_manifest"},
|
|
3108
|
+
)
|
|
3109
|
+
manifest = resolved["manifest"]
|
|
3067
3110
|
scenarios = manifest.get("scenarios") if isinstance(manifest, dict) else None
|
|
3068
3111
|
if not isinstance(scenarios, list):
|
|
3069
3112
|
return emit_error(
|
|
@@ -34,6 +34,66 @@ if hasattr(sys.stderr, "reconfigure"):
|
|
|
34
34
|
LIFECYCLE_STATUSES = {"draft", "active", "superseded", "archived", "cancelled"}
|
|
35
35
|
CHANGE_CONTEXT_REL = Path("meta") / "change-context.json"
|
|
36
36
|
|
|
37
|
+
# 工作流阶段的唯一权威清单。这里是叶子模块(不 import 任何 harness_*),所以
|
|
38
|
+
# harness_context 与 harness_phase 都能引它而不成环。
|
|
39
|
+
#
|
|
40
|
+
# 此前这份清单在两个模块各写一遍,靠约定保持同步——直到 harness_phase 少了一个
|
|
41
|
+
# merge,worktree 变更走到 merge 阶段就在 target_required_dag 里直接 raise
|
|
42
|
+
# "unsupported reconcile target phase"。同一份事实不该有两个副本。
|
|
43
|
+
WORKFLOW_PHASES = (
|
|
44
|
+
"plan",
|
|
45
|
+
"run",
|
|
46
|
+
"test",
|
|
47
|
+
"review",
|
|
48
|
+
"package",
|
|
49
|
+
"apidoc",
|
|
50
|
+
"submit",
|
|
51
|
+
"merge",
|
|
52
|
+
"archive",
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
# 出现在阶段位置、但不是工作流阶段的名字。它们是真实存在的:
|
|
56
|
+
# workflow-policy.json 的 skills.*.phase 用 sync/codebase-map/knowledge-query 标注
|
|
57
|
+
# 技能归属,harness_gate.GATE_RELEASE_PHASES 含 release/deploy。把它们与"拼错的
|
|
58
|
+
# 阶段名"区分开,调用方才能既不误判也不硬崩。
|
|
59
|
+
KNOWN_NON_WORKFLOW_PHASES = frozenset({
|
|
60
|
+
"release",
|
|
61
|
+
"deploy",
|
|
62
|
+
"sync",
|
|
63
|
+
"codebase-map",
|
|
64
|
+
"knowledge-query",
|
|
65
|
+
"knowledge-ingest",
|
|
66
|
+
"push",
|
|
67
|
+
"pull",
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
# 旧阶段名 → 现阶段名。阶段合并/改名时在这里登记,读时映射即可让历史
|
|
71
|
+
# gate-policy.json 与 plannedPhases 继续可读,不必迁移已落盘的 change。
|
|
72
|
+
# 目前为空:还没有发生过阶段改名。
|
|
73
|
+
LEGACY_PHASE_ALIASES: dict[str, str] = {}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def resolve_phase_name(phase: Any) -> str | None:
|
|
77
|
+
"""把可能是旧名的阶段解析成当前的工作流阶段名;不是工作流阶段则返回 None。"""
|
|
78
|
+
name = str(phase or "").strip()
|
|
79
|
+
if name in WORKFLOW_PHASES:
|
|
80
|
+
return name
|
|
81
|
+
mapped = LEGACY_PHASE_ALIASES.get(name)
|
|
82
|
+
return mapped if mapped in WORKFLOW_PHASES else None
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def classify_phase_name(phase: Any) -> str:
|
|
86
|
+
"""三态判定:``workflow`` | ``non_workflow`` | ``unknown``。
|
|
87
|
+
|
|
88
|
+
调用方需要区分这三者:``workflow`` 照常处理;``non_workflow`` 是别的子系统
|
|
89
|
+
的标注,跳过而不是报错;只有 ``unknown`` 才是真的有问题,值得让人看见。
|
|
90
|
+
"""
|
|
91
|
+
if resolve_phase_name(phase) is not None:
|
|
92
|
+
return "workflow"
|
|
93
|
+
if str(phase or "").strip() in KNOWN_NON_WORKFLOW_PHASES:
|
|
94
|
+
return "non_workflow"
|
|
95
|
+
return "unknown"
|
|
96
|
+
|
|
37
97
|
|
|
38
98
|
def _git_text(cwd: Path, *args: str) -> str | None:
|
|
39
99
|
proc = subprocess.run(
|
|
@@ -33,7 +33,9 @@ import harness_paths as hpaths # noqa: E402
|
|
|
33
33
|
|
|
34
34
|
SCHEMA_VERSION = 1
|
|
35
35
|
CI_METRICS_SCHEMA_VERSION = 1
|
|
36
|
-
|
|
36
|
+
# 唯一权威清单在 harness_paths,harness_context 引的是同一个对象。此前这里另有
|
|
37
|
+
# 一份拷贝,少了 merge,worktree 变更走到 merge 就在 target_required_dag 里硬 raise。
|
|
38
|
+
PHASE_ORDER = hpaths.WORKFLOW_PHASES
|
|
37
39
|
VALIDATION_PHASES = {
|
|
38
40
|
"compile": "run",
|
|
39
41
|
"unitTest": "run",
|
|
@@ -700,8 +702,18 @@ def target_required_dag(policy: dict[str, Any], target_phase: str) -> dict[str,
|
|
|
700
702
|
stage nodes for the target itself are deliberately excluded because their
|
|
701
703
|
``phase.end`` is the result of close, not evidence required before close.
|
|
702
704
|
"""
|
|
703
|
-
|
|
704
|
-
|
|
705
|
+
# 未知阶段名先过读时映射:阶段改名后,历史 change 的 gate-policy.json 与
|
|
706
|
+
# plannedPhases 里仍然是旧名,靠 LEGACY_PHASE_ALIASES 继续可读,不必迁移已落盘
|
|
707
|
+
# 的 change(本仓库没有 change 级 schema 迁移机制,硬 raise 等于让在途变更报废)。
|
|
708
|
+
resolved_target = hpaths.resolve_phase_name(target_phase)
|
|
709
|
+
if resolved_target is None:
|
|
710
|
+
raise ValueError(
|
|
711
|
+
f"unsupported reconcile target phase: {target_phase}; "
|
|
712
|
+
f"已知阶段 {', '.join(PHASE_ORDER)}。"
|
|
713
|
+
"若这是改名前的旧阶段,在 harness_paths.LEGACY_PHASE_ALIASES 登记映射;"
|
|
714
|
+
"若这份 gate-policy.json 本身过期,重跑 harness_gate.py classify 重建"
|
|
715
|
+
)
|
|
716
|
+
target_phase = resolved_target
|
|
705
717
|
dag = policy.get("requiredGateDag")
|
|
706
718
|
if not isinstance(dag, dict) or not isinstance(dag.get("nodes"), list):
|
|
707
719
|
raise ValueError("gate policy missing requiredGateDag; run harness_gate.py classify")
|
|
@@ -718,9 +730,15 @@ def target_required_dag(policy: dict[str, Any], target_phase: str) -> dict[str,
|
|
|
718
730
|
if node_kind == "validation"
|
|
719
731
|
else node_name
|
|
720
732
|
)
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
733
|
+
raw_phase = str(node.get("phase") or inferred_phase or "")
|
|
734
|
+
node_phase = hpaths.resolve_phase_name(raw_phase)
|
|
735
|
+
if node_phase is None:
|
|
736
|
+
raise ValueError(
|
|
737
|
+
f"requiredGateDag node has unsupported phase: {node_id} (phase={raw_phase!r});"
|
|
738
|
+
"旧阶段名在 harness_paths.LEGACY_PHASE_ALIASES 登记映射,"
|
|
739
|
+
"或重跑 harness_gate.py classify 重建这份 gate-policy.json"
|
|
740
|
+
)
|
|
741
|
+
node["phase"] = node_phase
|
|
724
742
|
node_rank = PHASE_ORDER.index(node_phase)
|
|
725
743
|
is_validation = node.get("kind") == "validation" or node_id.startswith(
|
|
726
744
|
"validation:"
|
|
@@ -261,6 +261,127 @@ def scenario_manifest_schema_version(scenarios: list[dict[str, Any]]) -> int:
|
|
|
261
261
|
return 1
|
|
262
262
|
|
|
263
263
|
|
|
264
|
+
# v2 场景键 → 门禁与 ledger 消费的 legacy 键。可执行三元是可选的:缺了只是把
|
|
265
|
+
# manifest 降到 schemaVersion 1(绑不上结构化执行收据),不算缺口。
|
|
266
|
+
_V2_SCENARIO_FIELD_MAP = (
|
|
267
|
+
("scenario_id", "id"),
|
|
268
|
+
("priority", "priority"),
|
|
269
|
+
("required_evidence_kind", "requiredEvidenceKind"),
|
|
270
|
+
("owner_phase", "ownerPhase"),
|
|
271
|
+
)
|
|
272
|
+
_V2_SCENARIO_OPTIONAL_FIELD_MAP = (
|
|
273
|
+
("executable_test_id", "executableTestId"),
|
|
274
|
+
("test_file", "testFile"),
|
|
275
|
+
("test_title", "testTitle"),
|
|
276
|
+
)
|
|
277
|
+
V2_MANIFEST_UNSUPPORTED = "SCENARIO_MANIFEST_V2_UNSUPPORTED"
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def is_v2_scenario_manifest(manifest: Any) -> bool:
|
|
281
|
+
"""v2 plan finalize 派生的 scenario_manifest artifact 包装体。
|
|
282
|
+
|
|
283
|
+
它没有顶层 schemaVersion,场景字段全在 content.scenarios 下且改了名。
|
|
284
|
+
"""
|
|
285
|
+
return (
|
|
286
|
+
isinstance(manifest, dict)
|
|
287
|
+
and manifest.get("artifact_type") == "scenario_manifest"
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def unpack_v2_scenario_manifest(manifest: Any) -> dict[str, Any] | None:
|
|
292
|
+
"""v2 artifact 包装体 → 消费端的 legacy 形状;字段不齐时 fail-closed。
|
|
293
|
+
|
|
294
|
+
返回 None 表示"这不是 v2 包装体",调用方按 legacy 原样处理。否则返回
|
|
295
|
+
``{"ok": True, "manifest": {...}}``(已是 legacy 形状,后续判定逻辑对
|
|
296
|
+
v2 与 legacy 完全一致)或 ``{"ok": False, "code": V2_MANIFEST_UNSUPPORTED}``。
|
|
297
|
+
|
|
298
|
+
这个函数住在 finalizer 里,是因为 legacy manifest 的 schema 本来就由本模块
|
|
299
|
+
定义(``scenario_manifest_schema_version`` / ``PRIORITY_EVIDENCE_KIND``)。
|
|
300
|
+
门禁与 ledger 都 import 它,两边不会各推一套解包规则。
|
|
301
|
+
|
|
302
|
+
为什么必须逐场景校验、不能取键的并集:并集只要有**一条**场景带了
|
|
303
|
+
priority 就算"present",其余缺 priority 的场景会静默落进非必需集,
|
|
304
|
+
``required_ids`` 随之缩水——那正是把证据门禁悄悄关掉的老路子。
|
|
305
|
+
"""
|
|
306
|
+
if not is_v2_scenario_manifest(manifest):
|
|
307
|
+
return None
|
|
308
|
+
content = manifest.get("content")
|
|
309
|
+
raw = content.get("scenarios") if isinstance(content, dict) else None
|
|
310
|
+
if not isinstance(raw, list):
|
|
311
|
+
return {
|
|
312
|
+
"ok": False,
|
|
313
|
+
"code": V2_MANIFEST_UNSUPPORTED,
|
|
314
|
+
"message": "v2 scenario-manifest 的 content.scenarios 必须是数组",
|
|
315
|
+
"missingFields": [legacy for _, legacy in _V2_SCENARIO_FIELD_MAP],
|
|
316
|
+
"artifactType": "scenario_manifest",
|
|
317
|
+
}
|
|
318
|
+
scenarios: list[dict[str, Any]] = []
|
|
319
|
+
missing: set[str] = set()
|
|
320
|
+
for item in raw:
|
|
321
|
+
if not isinstance(item, dict):
|
|
322
|
+
missing.update(legacy for _, legacy in _V2_SCENARIO_FIELD_MAP)
|
|
323
|
+
continue
|
|
324
|
+
mapped: dict[str, Any] = {}
|
|
325
|
+
for source, legacy in _V2_SCENARIO_FIELD_MAP:
|
|
326
|
+
value = item.get(source)
|
|
327
|
+
if not str(value or "").strip():
|
|
328
|
+
missing.add(legacy)
|
|
329
|
+
continue
|
|
330
|
+
mapped[legacy] = value
|
|
331
|
+
for source, legacy in _V2_SCENARIO_OPTIONAL_FIELD_MAP:
|
|
332
|
+
value = item.get(source)
|
|
333
|
+
if str(value or "").strip():
|
|
334
|
+
mapped[legacy] = value
|
|
335
|
+
scenarios.append(mapped)
|
|
336
|
+
if missing:
|
|
337
|
+
return {
|
|
338
|
+
"ok": False,
|
|
339
|
+
"code": V2_MANIFEST_UNSUPPORTED,
|
|
340
|
+
"message": (
|
|
341
|
+
"meta/scenario-manifest.json 是 v2 plan artifact 包装体,"
|
|
342
|
+
"部分场景缺少消费端需要的字段;请在规划阶段补齐后重新发布,"
|
|
343
|
+
"不要手改派生产物"
|
|
344
|
+
),
|
|
345
|
+
"missingFields": sorted(missing),
|
|
346
|
+
"artifactType": "scenario_manifest",
|
|
347
|
+
}
|
|
348
|
+
return {
|
|
349
|
+
"ok": True,
|
|
350
|
+
"manifest": {
|
|
351
|
+
# 与 legacy 同一条规则判版本,不另立一套。
|
|
352
|
+
"schemaVersion": scenario_manifest_schema_version(scenarios),
|
|
353
|
+
"scenarios": scenarios,
|
|
354
|
+
},
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def unpack_v2_implementation_checkpoints(document: Any) -> dict[str, Any] | None:
|
|
359
|
+
"""v2 的 implementation_checkpoints artifact 包装体 → 门禁消费的形状。
|
|
360
|
+
|
|
361
|
+
返回 None 表示"这不是 v2 包装体",调用方按原样处理。
|
|
362
|
+
|
|
363
|
+
v2 的 content 是 ``{tasks, foundation_gate}``,而 ``checkpoint_status`` 找的是
|
|
364
|
+
``checkpoints: [{id, status}]``——找不到就返回 "missing",``foundation_gate_blocks``
|
|
365
|
+
随即放行。也就是说 foundation-gate 对**所有 v2 计划**是静默关闭的。信息本身是够
|
|
366
|
+
的(``foundation_gate`` 就是那个状态),只是形状不同,所以这里解包而不是改文件名
|
|
367
|
+
——与 scenario-manifest 同一模式。
|
|
368
|
+
"""
|
|
369
|
+
if (
|
|
370
|
+
not isinstance(document, dict)
|
|
371
|
+
or document.get("artifact_type") != "implementation_checkpoints"
|
|
372
|
+
):
|
|
373
|
+
return None
|
|
374
|
+
content = document.get("content")
|
|
375
|
+
content = content if isinstance(content, dict) else {}
|
|
376
|
+
status = str(content.get("foundation_gate") or "").strip() or "pending"
|
|
377
|
+
tasks = content.get("tasks")
|
|
378
|
+
return {
|
|
379
|
+
"schemaVersion": 1,
|
|
380
|
+
"checkpoints": [{"id": "foundation-gate", "status": status, "blocking": True}],
|
|
381
|
+
"tasks": tasks if isinstance(tasks, list) else [],
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
|
|
264
385
|
def parse_plan_tasks(plan_path: Path) -> list[dict[str, str]]:
|
|
265
386
|
"""C8: parse plan.md task table rows, extracting optional ownerPhase/implementationDoneWhen/verificationPhase columns.
|
|
266
387
|
|
|
@@ -877,7 +998,9 @@ def _verify_plan_v2(change_dir: Path) -> dict[str, Any] | None:
|
|
|
877
998
|
f"plans/{change_dir.name}-plan.md",
|
|
878
999
|
f"plans/{change_dir.name}-implementation-detail.md",
|
|
879
1000
|
f"plans/{change_dir.name}-test-scenarios.md",
|
|
880
|
-
|
|
1001
|
+
# v2 发布的是派生视图 meta/plan-profile.json,不是 Python classify 写的
|
|
1002
|
+
# meta/gate-policy.json——后者是 run/test 门禁的权威输入,不能被发布覆盖。
|
|
1003
|
+
"meta/plan-profile.json",
|
|
881
1004
|
"meta/worktree.json",
|
|
882
1005
|
}
|
|
883
1006
|
missing_required = sorted(required - set(ownership_paths))
|
|
@@ -194,14 +194,30 @@ def _load_findings(change_dir: Path) -> dict[str, Any] | None:
|
|
|
194
194
|
|
|
195
195
|
|
|
196
196
|
def validate_dispositions(
|
|
197
|
-
doc: Any, known_ids: set[str]
|
|
197
|
+
doc: Any, known_ids: set[str], findings_run_id: str | None = None
|
|
198
198
|
) -> list[str]:
|
|
199
|
+
"""校验处置文档。``findings_run_id`` 给出时,还要求两份 sidecar 同属一轮。
|
|
200
|
+
|
|
201
|
+
runId 以前完全不校验——``write_dispositions`` 直接透传 ``doc.get("runId")``,
|
|
202
|
+
写 ``None`` 都能落盘,整条 sidecar 的强制力全压在 gate 关门时的一行断言上。
|
|
203
|
+
而这条断言防的是 fixback 循环里的跨轮重放:sidecar 是 per-change 单文件、不带
|
|
204
|
+
runId 后缀,第二轮 review 关门时磁盘上躺着第一轮那份(finding 全已 FIXED)。
|
|
205
|
+
在写入端就挡住,比等到关门再拒绝早一整轮。
|
|
206
|
+
"""
|
|
199
207
|
problems: list[str] = []
|
|
200
208
|
if not isinstance(doc, dict):
|
|
201
209
|
return ["dispositions document must be an object"]
|
|
210
|
+
run_id = doc.get("runId")
|
|
211
|
+
if not isinstance(run_id, str) or not run_id.strip():
|
|
212
|
+
problems.append("runId is required")
|
|
213
|
+
elif findings_run_id is not None and run_id != findings_run_id:
|
|
214
|
+
problems.append(
|
|
215
|
+
f"runId {run_id} does not match review findings runId {findings_run_id}"
|
|
216
|
+
)
|
|
202
217
|
dispositions = doc.get("dispositions")
|
|
203
218
|
if not isinstance(dispositions, list):
|
|
204
|
-
|
|
219
|
+
problems.append("dispositions must be a list")
|
|
220
|
+
return problems
|
|
205
221
|
for index, item in enumerate(dispositions):
|
|
206
222
|
if not isinstance(item, dict):
|
|
207
223
|
problems.append(f"dispositions[{index}] must be an object")
|
|
@@ -226,12 +242,17 @@ def write_dispositions(change_dir: Path, doc: dict[str, Any]) -> dict[str, Any]:
|
|
|
226
242
|
f.get("id") for f in (findings_doc or {}).get("findings", [])
|
|
227
243
|
if isinstance(f, dict)
|
|
228
244
|
}
|
|
229
|
-
|
|
245
|
+
findings_run_id = (findings_doc or {}).get("runId")
|
|
246
|
+
problems = validate_dispositions(
|
|
247
|
+
doc,
|
|
248
|
+
known_ids,
|
|
249
|
+
findings_run_id if isinstance(findings_run_id, str) else None,
|
|
250
|
+
)
|
|
230
251
|
if problems:
|
|
231
252
|
return {"ok": False, "code": "DISPOSITIONS_INVALID", "problems": problems}
|
|
232
253
|
payload = {
|
|
233
254
|
"schemaVersion": 1,
|
|
234
|
-
"runId": doc
|
|
255
|
+
"runId": doc["runId"],
|
|
235
256
|
"dispositions": doc["dispositions"],
|
|
236
257
|
}
|
|
237
258
|
out = dispositions_path(change_dir)
|
|
@@ -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=76790449b05e0e2e; overlay=none; agent=codebuddy; do not edit -->
|
|
7
7
|
# harness-archive — 变更归档
|
|
8
8
|
|
|
9
9
|
## Purpose
|
|
@@ -160,9 +160,16 @@ description: 归档所有变更产出(计划/测试报告/审查/SQL/API文档
|
|
|
160
160
|
> ⚠️ 行内标注是**申报**,不是绕过:它把"这是设计决策"写进文档本身,可审计、可追溯。
|
|
161
161
|
> **不要**为了过扫描去删改设计文档的事实内容——那是篡改证据。
|
|
162
162
|
>
|
|
163
|
-
>
|
|
164
|
-
>
|
|
165
|
-
>
|
|
163
|
+
> ✅ 行内标注**服务端确认生效**:平台归档入口跑的是同一个 `scanSensitiveFiles`,
|
|
164
|
+
> 它直接从文件内容里解析 `hunter-harness-ignore`,命中转为 `overridden`、不再阻断。
|
|
165
|
+
> 上传端点只收裸 ZIP、没有独立申报通道,靠的就是这条写在文档里的标注。
|
|
166
|
+
>
|
|
167
|
+
> ⚠️ 一处已知的服务端滞后:**不带凭据的连接串**(如 `postgres://dbhost:5432/appdb`)
|
|
168
|
+
> 本地预检判 `low`/可豁免,而线上服务端目前仍判 `high`——high 不接受行内标注,
|
|
169
|
+
> 这类设计文档暂时无法上传。平台侧修复已提交(见 hunter-platform
|
|
170
|
+
> `fix(archive): 归档 422 给出可执行出路`),**部署后**两侧一致。在那之前遇到这一条:
|
|
171
|
+
> 等平台部署,不要为了过扫描删掉连接串。带凭据的连接串(`user:pass@host`)两侧都是
|
|
172
|
+
> `high`,必须真正脱敏。
|
|
166
173
|
|
|
167
174
|
### 二·B、归档补传(上传失败或历史归档缺条目)
|
|
168
175
|
|
|
@@ -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=76790449b05e0e2e; overlay=none; agent=codebuddy; do not edit -->
|
|
7
7
|
# harness-codebase-map — 代码库地图
|
|
8
8
|
|
|
9
9
|
## Purpose
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: harness-knowledge-ingest
|
|
3
3
|
description: 确认归档 ZIP 已上传并由 Hunter Platform 在服务端解包、校验和入库。客户端不再构建或维护本地知识索引。
|
|
4
4
|
---
|
|
5
|
-
<!-- generated by harness_deploy.py; core=
|
|
5
|
+
<!-- generated by harness_deploy.py; core=76790449b05e0e2e; overlay=none; agent=codebuddy; do not edit -->
|
|
6
6
|
# harness-knowledge-ingest
|
|
7
7
|
|
|
8
8
|
知识 ingest 完全由 Hunter Platform 负责。归档完成时,客户端生成一个确定性 ZIP;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: harness-knowledge-query
|
|
3
3
|
description: 在规划、实现或排查前,通过 hunter-harness CLI 查询远端项目知识库。远端不可用时直接报告不可用,不建立本地索引或离线回退。
|
|
4
4
|
---
|
|
5
|
-
<!-- generated by harness_deploy.py; core=
|
|
5
|
+
<!-- generated by harness_deploy.py; core=76790449b05e0e2e; overlay=none; agent=codebuddy; do not edit -->
|
|
6
6
|
# harness-knowledge-query
|
|
7
7
|
|
|
8
8
|
项目知识以 Hunter Platform 的服务端索引为唯一真源。客户端只提交查询并消费结果:
|
|
@@ -3,7 +3,7 @@ name: harness-plan
|
|
|
3
3
|
description: 将需求转化为设计文档+实施计划+完整测试场景表,必须在编码前完成。仅当用户显式调用 /harness-plan 或明确要求进入
|
|
4
4
|
Harness 规划阶段时使用;不得因用户描述需求就自动触发。
|
|
5
5
|
---
|
|
6
|
-
<!-- generated by harness_deploy.py; core=
|
|
6
|
+
<!-- generated by harness_deploy.py; core=76790449b05e0e2e; overlay=none; agent=codebuddy; do not edit -->
|
|
7
7
|
# harness-plan — 需求规划
|
|
8
8
|
|
|
9
9
|
## Purpose
|
|
@@ -70,7 +70,7 @@ description: 将需求转化为设计文档+实施计划+完整测试场景表
|
|
|
70
70
|
| 0 | git status;脏工作区 → baseline 隔离 + `decision`,不询问。环境体检并入 0.5 的引导命令,不再单独跑 doctor |
|
|
71
71
|
| 0.5 | 先定英文 `change-name`(kebab-case,`^[a-z0-9]+(-[a-z0-9]+)*$`)与一次性中文展示标题(建议 6~24 个可见字符,保留必要产品名),然后**一条命令完成引导**:<br>`python <skills-root>/scripts/harness_context.py bootstrap-plan --project . --change <cn> --executor <tool> --title "<中文标题>" --json`<br>它按序做完 doctor → 建 change 骨架 → prepare → state capture(首次把当时 HEAD 固定为不可变 `changeBase`)→ classify(落 `meta/gate-policy.json`)→ 生成合规 `plan_<uuid>` run-id 并追加 `phase.start`,返回紧凑摘要:`runId`/`attempt`/`tier`/`defaultPhases`/`conditionalPhases`/`requiredValidations`/`changeBase`/`head`/`executionRoot`。**重跑复用同一 run-id、不重复写 `phase.start`**(换 run-id 会让 finalize 按生命周期身份 fail-closed)。finalizer 必须复用该 `runId`/`attempt` |
|
|
72
72
|
| 0.5b | 仅在引导失败需要单步排查时用等价分解——**每条的参数都是必填,少一个就是白跑一轮**:<br>`harness_runtime.py doctor --project . --change-dir ".harness/changes/<cn>" --agent <claude-code\|codebuddy\|codex\|cursor> --json`<br>`harness_context.py prepare --project . --change <cn> --phase plan --executor <tool> --title "<中文标题>" --json`<br>`harness_state.py capture --project . --change-dir ".harness/changes/<cn>" --json`<br>`harness_gate.py classify --change <cn> --stage plan --json`<br>`harness_events.py append --change-dir ".harness/changes/<cn>" --phase plan --type phase.start --run-id plan_<uuid> --attempt 1`<br>⚠️ `--change-dir` 一律是 `.harness/changes/<cn>`(状态目录),**不是** prepare 返回的 `executionRoot`(代码执行根,无 worktree 时等于项目根)——填错会把 gate-policy/events 写到项目根 |
|
|
73
|
-
| 0.6 | 用引导返回的 `tier`/`defaultPhases`/`requiredValidations` 生成 `plannedPhases`,向用户用中文说明可选阶段;确认后运行 `harness_context.py configure-plan --project . --change <cn> --phases "plan,run,...,archive" --operator <tool> --reason "<中文原因>" --json`。无 Git 或不需要提交时不得加入 `submit`;快速迭代默认 `plan,run,archive`。省略项由脚本写入 `skippedPhases
|
|
73
|
+
| 0.6 | 用引导返回的 `tier`/`defaultPhases`/`requiredValidations` 生成 `plannedPhases`,向用户用中文说明可选阶段;确认后运行 `harness_context.py configure-plan --project . --change <cn> --phases "plan,run,...,archive" --operator <tool> --reason "<中文原因>" --json`。无 Git 或不需要提交时不得加入 `submit`;快速迭代默认 `plan,run,archive`。省略项由脚本写入 `skippedPhases`,不得伪造阶段事件。<br>⚠️ 无风险信号时 classify 默认 `standard`(`plan,run,test,submit,archive` + compile/unitTest/unitTestFull),**不再默认 full**。需要 review 阶段与 apiTest 的高风险变更,在计划文档里写「风险等级: full」或用 `classify --tier-override full` 显式升档 |
|
|
74
74
|
| 1 | 直接执行一次 `npx hunter-harness knowledge query "<用户需求原文>" --limit 10 --json`。这是唯一执行入口,不扫描技能目录、不查找其他脚本;失败记 `issue` 并继续,不建立本地索引或离线回退 |
|
|
75
75
|
| 2 | 歧义优先检查 + 复杂度分级;先确认会改变实现方向的语义歧义 |
|
|
76
76
|
| 3 | 按复杂度执行有预算的代码探索;简单修复不得扩散到无关模块 |
|
|
@@ -106,6 +106,7 @@ change-name 范围变更 → 提示重命名或记 🟡WARN(→ `reference.md`
|
|
|
106
106
|
| 设计审批包 | 一次 blocking user confirmation 含 worktree(读 `harness.json` `defaultWorktree`)。必须同时展示 **in_scope 与 out_of_scope 两个列表**——只展示"做什么"会让范围误判活到发布之后,代价是整份计划 republish |
|
|
107
107
|
| 引用即追问 | 需求引用了外部设计文档章节(贴段落、指 `### Bn`、说"之前设计的时候如…")时,阶段 2 必须确认该章节是否纳入本次范围,落到 in_scope 或 out_of_scope;引用 ≠ 纳入,也 ≠ 排除 |
|
|
108
108
|
| 阶段 8 | 二选一且不得混用:**v2** = `hunter-harness plan finalize`(证据包 → 八 target + journal committed + plan-events.ndjson);**legacy** = 六项标准产物先进入 staging,仅 finalizer 校验成功后发布并写唯一 `phase.end`/log,随后 `verify` 确认 start/end、收据完整覆盖六项标准产物、哈希、全部任务表和非空场景清单一致。失败均不得手工补终态 |
|
|
109
|
+
| 选 v2 还是 legacy | **默认 v2**;legacy 已 deprecated,将在 workflow-harness 0.3.0 移除,只用于自然输入不完整(例如缺真实审批记录)的回退。v2 的场景契约已补齐 `priority`/`owner_phase` 与可执行测试三元,派生的 `meta/scenario-manifest.json` 能被 run/test 门禁与 `harness_ledger.py record` 解包消费,证据闭环走得通。P0/P1 场景必须给全可执行三元(要么整组给全、要么整组省略;省略则 manifest 降为 schemaVersion 1,关门绑不上结构化执行收据) |
|
|
109
110
|
| 发布后改产物 | 用 `harness_plan_finalize.py republish --run-id <全新> --reason "<why>"` 一次完成(新 attempt + 换收据 + 重新派生 manifest)。重跑 `finalize` 会报 `PLAN_FINALIZATION_HASH_CONFLICT`;**绝不手改 `meta/scenario-manifest.json`**(派生物,手改必致 `ARTIFACT_HASH_DRIFT`)→ `reference.md`「发布后修订计划」 |
|
|
110
111
|
| v2 输入骨架 | 不要猜 `plan-evidence-input.json` 结构:`npx hunter-harness plan evidence-pack --print-template` 给出**一个字不改就能通过 evidence-pack** 的骨架(结构合法;`change_key`/`run_id` 仍须换成真实身份才能 finalize),逐项替换即可。结构不符时命令返回 `PLAN_EVIDENCE_INPUT_INVALID`,带 `field_path` 与 `problems[]`(缺失/多余键、枚举取值)——按 problems 改完重跑;**不得**为找契约去反编译 `dist/bin.js` 或翻 npx 缓存。带不了 `<>` 的占位字段与硬约束清单 → `reference.md` 阶段 8 v2 路径 |
|
|
111
112
|
| Plan 结束 | **禁止**询问执行模式;只提示 `/harness-run` |
|
|
@@ -184,8 +184,8 @@ source: harness-plan
|
|
|
184
184
|
□ 已用 decision / issue 事件 note 记录五类输出:风险识别 / 复用机会 / 替代方案 / 推荐方案 / 关键决策
|
|
185
185
|
□ 已叠加项目架构约束(分层规范、数据模型、接口规范)
|
|
186
186
|
□ 需求澄清结论已追加到 events.ndjson,阶段结束后执行日志由渲染器生成
|
|
187
|
-
□
|
|
188
|
-
□
|
|
187
|
+
□ 提问方式与问题预算符合 protocols.md 的 decision-grilling-protocol(预算数值以那份为准,此处不复述)
|
|
188
|
+
□ 能由 context pack / 阶段3代码探索 / CodeGraph 自答的问题已自答,未打扰用户
|
|
189
189
|
□ 每个需要用户决策的问题,AI 先给出了推荐答案、理由和取舍,用户仅确认或修正
|
|
190
190
|
□ 高风险/业务语义决策(范围、权限、安全、支付、迁移、删除、API契约、用户可见行为)已显式等待用户确认
|
|
191
191
|
```
|
|
@@ -199,97 +199,34 @@ source: harness-plan
|
|
|
199
199
|
```
|
|
200
200
|
□ 已读取 protocols.md,并按 implementation-planning-protocol 执行
|
|
201
201
|
□ 输入为阶段4已审核设计文档
|
|
202
|
-
□
|
|
202
|
+
□ 已用 artifact 事件 note 记录任务拆分摘要
|
|
203
203
|
□ 已叠加项目层序依赖(数据/契约→业务层→接口层)
|
|
204
204
|
□ 已生成 4 维度场景表(单元/接口/数据兼容/集成)
|
|
205
205
|
□ 每个自动化场景均标注执行层级、预计时长、资源预算、超时、可复用证据;快速反馈不默认扫描全仓库
|
|
206
206
|
□ 已确定变更名(kebab-case)
|
|
207
|
-
□ 产物已写入 .harness/changes/<change-name>/plans/:
|
|
208
|
-
- <change-name>-plan.md(简洁任务表)
|
|
209
|
-
- <change-name>-implementation-detail.md(自适应详细执行参考)
|
|
210
|
-
- <change-name>-test-scenarios.md(测试场景表)
|
|
211
207
|
□ implementation-detail.md 按复杂度自适应:简单任务不过度展开,复杂任务写清接口/数据/顺序/风险/测试策略
|
|
212
208
|
□ plan / implementation-detail / test-scenarios 三件套互相引用一致,无 TBD/TODO/空泛占位
|
|
213
209
|
```
|
|
214
210
|
|
|
211
|
+
> 产物是否齐全、任务表与场景表是否非空、优先级与 ownerPhase 取值是否合法,由 finalizer
|
|
212
|
+
> fail-closed 判定(`PLAN_ARTIFACT_MISSING` / `PLAN_TASKS_EMPTY` / `PLAN_SCENARIOS_EMPTY` /
|
|
213
|
+
> `PLAN_SCENARIO_PRIORITY_INVALID`),此处不重复勾选。上面留下的都是机器判不了的:
|
|
214
|
+
> 层序依赖是否合理、维度是否真被覆盖、详略是否配得上复杂度、三件套是否自洽。
|
|
215
|
+
|
|
215
216
|
> 不再检查 Superpowers writing-plans 是否安装或调用;阶段 6 是 harness 原生协议,不存在 `docs/superpowers/` 同步分支。
|
|
216
217
|
|
|
217
218
|
## 阶段 8:结束前产物完整性检查 ⚠️ 强制
|
|
218
219
|
|
|
219
|
-
>
|
|
220
|
-
|
|
221
|
-
> **先认路径再对表**:v2 与 legacy 的完整性口径不同,混用会得出错误结论。
|
|
222
|
-
|
|
223
|
-
### v2 路径(新 change 默认)
|
|
224
|
-
|
|
225
|
-
- [ ] 只手写 `meta/plan-evidence-input.json`;`plans/*.md` 由 finalize 派生,**不得**手写后再被覆盖
|
|
226
|
-
- [ ] `evidence-pack` 返回 `code:"PLAN_EVIDENCE_PACK_BUILT"`;结构报错按 `field_path`/`problems[]` 修正后重跑
|
|
227
|
-
- [ ] `finalize` exit 0 且 `code:"PLAN_FINALIZED"`
|
|
228
|
-
- [ ] 八 target 齐全:`plans/` ×4(design / implementation-detail / plan / test-scenarios)+ `meta/` ×4(gate-policy / implementation-checkpoints / scenario-manifest / worktree)
|
|
229
|
-
- [ ] `meta/publication-journals/<op>.json` 的 `state === "committed"`
|
|
230
|
-
- [ ] `meta/plan-events.ndjson` 含 `artifact_published` 与 `phase_ended` 两类终态事件
|
|
231
|
-
- [ ] 以上任一缺失都不得手工补写;回到对应阶段改自然输入后重跑
|
|
232
|
-
|
|
233
|
-
| 文件(v2) | 必须存在 | 检查结果 |
|
|
234
|
-
|------|:---:|:---:|
|
|
235
|
-
| `.harness/changes/<change>/meta/plan-evidence-input.json` | ✅ | □ |
|
|
236
|
-
| `.harness/changes/<change>/plans/<change>-design.md` | ✅ | □ |
|
|
237
|
-
| `.harness/changes/<change>/plans/<change>-plan.md` | ✅ | □ |
|
|
238
|
-
| `.harness/changes/<change>/plans/<change>-implementation-detail.md` | ✅ | □ |
|
|
239
|
-
| `.harness/changes/<change>/plans/<change>-test-scenarios.md` | ✅ | □ |
|
|
240
|
-
| `.harness/changes/<change>/meta/gate-policy.json` | ✅ | □ |
|
|
241
|
-
| `.harness/changes/<change>/meta/worktree.json` | ✅ | □ |
|
|
242
|
-
| `.harness/changes/<change>/meta/implementation-checkpoints.json` | ✅ | □ |
|
|
243
|
-
| `.harness/changes/<change>/meta/scenario-manifest.json`(非空且计数一致) | ✅ | □ |
|
|
244
|
-
| `.harness/changes/<change>/meta/publication-journals/<op>.json`(committed) | ✅ | □ |
|
|
245
|
-
| `.harness/changes/<change>/meta/plan-events.ndjson` | ✅ | □ |
|
|
246
|
-
| `.harness/changes/<change>/events.ndjson` | ✅ | □ |
|
|
247
|
-
|
|
248
|
-
> v2 过渡期**不写** `meta/plan-finalization.json` 与 `logs/execution-log.md`;缺这两项不算失败,不得为凑表手工补。
|
|
249
|
-
|
|
250
|
-
### legacy 路径(自然输入不完整时的回退)
|
|
251
|
-
|
|
252
|
-
- [ ] 所有待发布产物先写入 staging,不直接覆盖正式 change 目录
|
|
253
|
-
- [ ] 执行 `harness_plan_finalize.py finalize --change-dir ... --staging-dir ... --change ... --run-id <plan-run-id> --attempt <attempt> --json`;身份必须与本次 `phase.start` 完全相同
|
|
254
|
-
- [ ] finalizer 返回 `ok=true`、`artifactsHash`、绝对 `receiptPath` 与稳定 `artifactRef=meta/plan-finalization.json`;重复执行返回 `idempotent=true`
|
|
255
|
-
- [ ] 紧接着执行 `harness_plan_finalize.py verify --change-dir ... --json`
|
|
256
|
-
- [ ] verify 返回 `phaseStartCount=1`、`phaseEndCount=1`、`phaseEndStatus=OK`、`receiptConsistent=true`
|
|
257
|
-
- [ ] 收据 `files` 完整包含 design / plan / implementation-detail / test-scenarios / gate-policy / worktree 六项标准产物;不得省略、重复或经 symlink/junction/reparse point 引用
|
|
258
|
-
- [ ] verify 的 `taskCount` 等于全部任务表行数,`scenarioCount` 等于 Markdown 中全部唯一场景 ID 数;任一为 0 或不一致即 ❌FAIL
|
|
259
|
-
- [ ] finalizer 失败时正式目录无半发布产物、无成功 `phase.end`、无伪造 execution log
|
|
260
|
-
- [ ] 禁止在 finalizer 之前手工追加成功 `phase.end`
|
|
261
|
-
- [ ] context close 的 `--artifact` 只传 finalizer 原样返回的 `receiptPath` 或 `artifactRef`,不得猜测、拼接或使用 `<plan-finalization>` 占位文本
|
|
262
|
-
|
|
263
|
-
| 文件(legacy) | 必须存在 | 检查结果 |
|
|
264
|
-
|------|:---:|:---:|
|
|
265
|
-
| `.harness/changes/<change>/spec/<change>-design.md` | ✅ | □ |
|
|
266
|
-
| `.harness/changes/<change>/plans/<change>-plan.md` | ✅ | □ |
|
|
267
|
-
| `.harness/changes/<change>/plans/<change>-implementation-detail.md` | ✅ | □ |
|
|
268
|
-
| `.harness/changes/<change>/plans/<change>-test-scenarios.md` | ✅ | □ |
|
|
269
|
-
| `.harness/changes/<change>/meta/gate-policy.json` | ✅ | □ |
|
|
270
|
-
| `.harness/changes/<change>/meta/worktree.json` | ✅ | □ |
|
|
271
|
-
| `.harness/changes/<change>/meta/implementation-checkpoints.json` | ✅ | □ |
|
|
272
|
-
| `.harness/changes/<change>/meta/scenario-manifest.json`(非空且计数一致) | ✅ | □ |
|
|
273
|
-
| `.harness/changes/<change>/meta/plan-finalization.json`(finalized) | ✅ | □ |
|
|
274
|
-
| `.harness/changes/<change>/logs/execution-log.md` | ✅ | □ |
|
|
275
|
-
| `.harness/changes/<change>/events.ndjson` | ✅ | □ |
|
|
276
|
-
|
|
277
|
-
### Plan 结束行为检查
|
|
220
|
+
> **先认路径**:v2 与 legacy 的完整性口径不同,混用会得出错误结论。两条路径各自的必需文件清单 → `reference.md`「阶段 8」。
|
|
278
221
|
|
|
279
|
-
|
|
280
|
-
□ 未询问 Subagent-Driven / Inline Execution 等执行模式
|
|
281
|
-
□ 最终输出只提示了产出物路径和下一步 /harness-run
|
|
282
|
-
□ 未将 docs/superpowers/ 列为最终产物路径
|
|
283
|
-
```
|
|
222
|
+
文件是否齐全、哈希是否一致、身份是否匹配、计数是否对得上——这些 **finalizer 与 verify 已经 fail-closed 判定**,逐条复述不产生新结论,只会把一份事实变成两份。命令失败时按返回的 `code` 查 `reference.md`,不要对着清单猜。
|
|
284
223
|
|
|
285
|
-
|
|
224
|
+
下面三条不在机器判定范围内,必须自己守:
|
|
225
|
+
|
|
226
|
+
- [ ] **v2**:只手写 `meta/plan-evidence-input.json`。`plans/*.md` 由 finalize 派生,手写的会被渲染覆盖,只是白写
|
|
227
|
+
- [ ] 任何缺失**都不得手工补写**(包括 `phase.end`);回到对应阶段改自然输入/staging 后重跑
|
|
228
|
+
- [ ] v2 过渡期**不写** `meta/plan-finalization.json` 与 `logs/execution-log.md`;缺这两项不算失败,不得为凑表手工补
|
|
286
229
|
|
|
287
|
-
```
|
|
288
|
-
□ 已确认 plan 文件 frontmatter 存在
|
|
289
|
-
□ 如不存在 → 已从路径推断 change-name 和 plan-name
|
|
290
|
-
□ 如不存在 → 执行日志中已标记 🟡 legacy-plan
|
|
291
|
-
□ 旧 plan 不因 frontmatter 缺失而 FAIL
|
|
292
|
-
```
|
|
293
230
|
|
|
294
231
|
## 关键原则
|
|
295
232
|
|
|
@@ -300,7 +237,9 @@ source: harness-plan
|
|
|
300
237
|
- 场景表是后续所有步骤的真相源——宁可多花时间打磨,不要草草了事
|
|
301
238
|
- 如果需求不明确,优先提问而不是猜测后继续设计
|
|
302
239
|
- 任务拆分粒度按复杂度调整——plan 简表保持可追踪,implementation-detail 按风险和复杂度自适应展开
|
|
303
|
-
|
|
240
|
+
|
|
241
|
+
> Plan 的结束行为(禁止询问执行模式、只提示 `/harness-run`)由 `SKILL.md` 的关键规则表定义,
|
|
242
|
+
> 详细规则见 `reference.md`「Plan 结束行为规则」。此处不再复述。
|
|
304
243
|
|
|
305
244
|
## 事件记录(前置规则)
|
|
306
245
|
|
|
@@ -128,4 +128,6 @@ description: harness-plan 的原生规划协议。吸收 brainstorming、grill-m
|
|
|
128
128
|
- implementation-planning-protocol:plan 简表、implementation-detail、test-scenarios 三件套一致,无占位符
|
|
129
129
|
```
|
|
130
130
|
|
|
131
|
-
|
|
131
|
+
自检结论直接展示给用户即可,**不要**再追加一条 `verification` 事件——「协议自检通过」不改变
|
|
132
|
+
任何结论,只增加监控噪声(roadmap 12 号「事件规则」)。真正影响行为的东西照常留痕:关键决策
|
|
133
|
+
追加 `decision`,发现的冲突与阻塞追加 `issue`。渲染器在 `phase.end` 后生成执行日志。
|