@hunter-harness/workflow-harness 0.2.72 → 0.2.74
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 +3 -3
- 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 +14 -10
- package/harness/bundles/general/claude-code/harness-plan/checklist.md +49 -9
- package/harness/bundles/general/claude-code/harness-plan/reference.md +83 -33
- 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 +3 -3
- package/harness/bundles/general/claude-code/harness-run/SKILL.md +6 -6
- package/harness/bundles/general/claude-code/harness-run/checklist.md +4 -1
- package/harness/bundles/general/claude-code/harness-run/reference.md +3 -3
- package/harness/bundles/general/claude-code/harness-submit/SKILL.md +5 -4
- package/harness/bundles/general/claude-code/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-test/SKILL.md +4 -4
- package/harness/bundles/general/claude-code/harness-test/checklist.md +2 -0
- package/harness/bundles/general/claude-code/scripts/harness_archive.py +82 -1
- package/harness/bundles/general/claude-code/scripts/harness_context.py +270 -0
- package/harness/bundles/general/claude-code/scripts/harness_gate.py +71 -2
- package/harness/bundles/general/claude-code/scripts/harness_ledger.py +25 -4
- package/harness/bundles/general/claude-code/scripts/harness_profile.py +45 -0
- package/harness/bundles/general/claude-code/scripts/harness_test_guard.py +8 -5
- package/harness/bundles/general/codebuddy/.harness-build.json +1 -1
- package/harness/bundles/general/codebuddy/harness-archive/SKILL.md +3 -3
- 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 +14 -10
- package/harness/bundles/general/codebuddy/harness-plan/checklist.md +49 -9
- package/harness/bundles/general/codebuddy/harness-plan/reference.md +83 -33
- 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 +3 -3
- package/harness/bundles/general/codebuddy/harness-run/SKILL.md +6 -6
- package/harness/bundles/general/codebuddy/harness-run/checklist.md +4 -1
- package/harness/bundles/general/codebuddy/harness-run/reference.md +3 -3
- package/harness/bundles/general/codebuddy/harness-submit/SKILL.md +5 -4
- package/harness/bundles/general/codebuddy/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-test/SKILL.md +4 -4
- package/harness/bundles/general/codebuddy/harness-test/checklist.md +2 -0
- package/harness/bundles/general/codebuddy/scripts/harness_archive.py +82 -1
- package/harness/bundles/general/codebuddy/scripts/harness_context.py +270 -0
- package/harness/bundles/general/codebuddy/scripts/harness_gate.py +71 -2
- package/harness/bundles/general/codebuddy/scripts/harness_ledger.py +25 -4
- package/harness/bundles/general/codebuddy/scripts/harness_profile.py +45 -0
- package/harness/bundles/general/codebuddy/scripts/harness_test_guard.py +8 -5
- package/harness/bundles/general/codex/.harness-build.json +1 -1
- package/harness/bundles/general/codex/harness-archive/SKILL.md +3 -3
- 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 +14 -10
- package/harness/bundles/general/codex/harness-plan/checklist.md +49 -9
- package/harness/bundles/general/codex/harness-plan/reference.md +83 -33
- 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 +3 -3
- package/harness/bundles/general/codex/harness-run/SKILL.md +6 -6
- package/harness/bundles/general/codex/harness-run/checklist.md +4 -1
- package/harness/bundles/general/codex/harness-run/reference.md +3 -3
- package/harness/bundles/general/codex/harness-submit/SKILL.md +5 -4
- package/harness/bundles/general/codex/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-test/SKILL.md +4 -4
- package/harness/bundles/general/codex/harness-test/checklist.md +2 -0
- package/harness/bundles/general/codex/scripts/harness_archive.py +82 -1
- package/harness/bundles/general/codex/scripts/harness_context.py +270 -0
- package/harness/bundles/general/codex/scripts/harness_gate.py +71 -2
- package/harness/bundles/general/codex/scripts/harness_ledger.py +25 -4
- package/harness/bundles/general/codex/scripts/harness_profile.py +45 -0
- package/harness/bundles/general/codex/scripts/harness_test_guard.py +8 -5
- package/harness/bundles/general/cursor/.harness-build.json +1 -1
- package/harness/bundles/general/cursor/harness-archive/SKILL.md +3 -3
- 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 +14 -10
- package/harness/bundles/general/cursor/harness-plan/checklist.md +49 -9
- package/harness/bundles/general/cursor/harness-plan/reference.md +83 -33
- 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 +3 -3
- package/harness/bundles/general/cursor/harness-run/SKILL.md +6 -6
- package/harness/bundles/general/cursor/harness-run/checklist.md +4 -1
- package/harness/bundles/general/cursor/harness-run/reference.md +3 -3
- package/harness/bundles/general/cursor/harness-submit/SKILL.md +5 -4
- package/harness/bundles/general/cursor/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-test/SKILL.md +4 -4
- package/harness/bundles/general/cursor/harness-test/checklist.md +2 -0
- package/harness/bundles/general/cursor/scripts/harness_archive.py +82 -1
- package/harness/bundles/general/cursor/scripts/harness_context.py +270 -0
- package/harness/bundles/general/cursor/scripts/harness_gate.py +71 -2
- package/harness/bundles/general/cursor/scripts/harness_ledger.py +25 -4
- package/harness/bundles/general/cursor/scripts/harness_profile.py +45 -0
- package/harness/bundles/general/cursor/scripts/harness_test_guard.py +8 -5
- package/harness/bundles/java/claude-code/.harness-build.json +1 -1
- package/harness/bundles/java/claude-code/harness-apidoc/SKILL.md +3 -3
- package/harness/bundles/java/claude-code/harness-archive/SKILL.md +3 -3
- 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 +3 -3
- package/harness/bundles/java/claude-code/harness-plan/SKILL.md +14 -10
- package/harness/bundles/java/claude-code/harness-plan/checklist.md +49 -9
- package/harness/bundles/java/claude-code/harness-plan/reference.md +83 -33
- 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 +3 -3
- package/harness/bundles/java/claude-code/harness-run/SKILL.md +6 -6
- package/harness/bundles/java/claude-code/harness-run/checklist.md +1 -1
- package/harness/bundles/java/claude-code/harness-run/reference.md +348 -348
- package/harness/bundles/java/claude-code/harness-submit/SKILL.md +5 -4
- package/harness/bundles/java/claude-code/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-test/SKILL.md +4 -4
- package/harness/bundles/java/claude-code/scripts/harness_archive.py +82 -1
- package/harness/bundles/java/claude-code/scripts/harness_context.py +270 -0
- package/harness/bundles/java/claude-code/scripts/harness_gate.py +71 -2
- package/harness/bundles/java/claude-code/scripts/harness_ledger.py +25 -4
- package/harness/bundles/java/claude-code/scripts/harness_profile.py +45 -0
- package/harness/bundles/java/claude-code/scripts/harness_test_guard.py +8 -5
- package/harness/bundles/java/codebuddy/.harness-build.json +1 -1
- package/harness/bundles/java/codebuddy/harness-apidoc/SKILL.md +3 -3
- package/harness/bundles/java/codebuddy/harness-archive/SKILL.md +3 -3
- 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 +3 -3
- package/harness/bundles/java/codebuddy/harness-plan/SKILL.md +14 -10
- package/harness/bundles/java/codebuddy/harness-plan/checklist.md +49 -9
- package/harness/bundles/java/codebuddy/harness-plan/reference.md +83 -33
- 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 +3 -3
- package/harness/bundles/java/codebuddy/harness-run/SKILL.md +6 -6
- package/harness/bundles/java/codebuddy/harness-run/checklist.md +1 -1
- package/harness/bundles/java/codebuddy/harness-run/reference.md +348 -348
- package/harness/bundles/java/codebuddy/harness-submit/SKILL.md +5 -4
- package/harness/bundles/java/codebuddy/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-test/SKILL.md +4 -4
- package/harness/bundles/java/codebuddy/scripts/harness_archive.py +82 -1
- package/harness/bundles/java/codebuddy/scripts/harness_context.py +270 -0
- package/harness/bundles/java/codebuddy/scripts/harness_gate.py +71 -2
- package/harness/bundles/java/codebuddy/scripts/harness_ledger.py +25 -4
- package/harness/bundles/java/codebuddy/scripts/harness_profile.py +45 -0
- package/harness/bundles/java/codebuddy/scripts/harness_test_guard.py +8 -5
- package/harness/bundles/java/codex/.harness-build.json +1 -1
- package/harness/bundles/java/codex/harness-apidoc/SKILL.md +3 -3
- package/harness/bundles/java/codex/harness-archive/SKILL.md +3 -3
- 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 +3 -3
- package/harness/bundles/java/codex/harness-plan/SKILL.md +14 -10
- package/harness/bundles/java/codex/harness-plan/checklist.md +49 -9
- package/harness/bundles/java/codex/harness-plan/reference.md +83 -33
- 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 +3 -3
- package/harness/bundles/java/codex/harness-run/SKILL.md +6 -6
- package/harness/bundles/java/codex/harness-run/checklist.md +1 -1
- package/harness/bundles/java/codex/harness-run/reference.md +348 -348
- package/harness/bundles/java/codex/harness-submit/SKILL.md +5 -4
- package/harness/bundles/java/codex/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-test/SKILL.md +4 -4
- package/harness/bundles/java/codex/scripts/harness_archive.py +82 -1
- package/harness/bundles/java/codex/scripts/harness_context.py +270 -0
- package/harness/bundles/java/codex/scripts/harness_gate.py +71 -2
- package/harness/bundles/java/codex/scripts/harness_ledger.py +25 -4
- package/harness/bundles/java/codex/scripts/harness_profile.py +45 -0
- package/harness/bundles/java/codex/scripts/harness_test_guard.py +8 -5
- package/harness/bundles/java/cursor/.harness-build.json +1 -1
- package/harness/bundles/java/cursor/harness-apidoc/SKILL.md +3 -3
- package/harness/bundles/java/cursor/harness-archive/SKILL.md +3 -3
- 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 +3 -3
- package/harness/bundles/java/cursor/harness-plan/SKILL.md +14 -10
- package/harness/bundles/java/cursor/harness-plan/checklist.md +49 -9
- package/harness/bundles/java/cursor/harness-plan/reference.md +83 -33
- 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 +3 -3
- package/harness/bundles/java/cursor/harness-run/SKILL.md +6 -6
- package/harness/bundles/java/cursor/harness-run/checklist.md +1 -1
- package/harness/bundles/java/cursor/harness-run/reference.md +348 -348
- package/harness/bundles/java/cursor/harness-submit/SKILL.md +5 -4
- package/harness/bundles/java/cursor/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-test/SKILL.md +4 -4
- package/harness/bundles/java/cursor/scripts/harness_archive.py +82 -1
- package/harness/bundles/java/cursor/scripts/harness_context.py +270 -0
- package/harness/bundles/java/cursor/scripts/harness_gate.py +71 -2
- package/harness/bundles/java/cursor/scripts/harness_ledger.py +25 -4
- package/harness/bundles/java/cursor/scripts/harness_profile.py +45 -0
- package/harness/bundles/java/cursor/scripts/harness_test_guard.py +8 -5
- package/harness/manifests/general/claude-code.json +26 -26
- package/harness/manifests/general/codebuddy.json +26 -26
- package/harness/manifests/general/codex.json +26 -26
- package/harness/manifests/general/cursor.json +26 -26
- package/harness/manifests/java/claude-code.json +27 -27
- package/harness/manifests/java/codebuddy.json +27 -27
- package/harness/manifests/java/codex.json +27 -27
- package/harness/manifests/java/cursor.json +27 -27
- package/hunter-workflow-family.json +5 -5
- package/package.json +1 -1
|
@@ -732,6 +732,16 @@ def prepare_context(
|
|
|
732
732
|
)
|
|
733
733
|
if not reselection.get("ok"):
|
|
734
734
|
return reselection
|
|
735
|
+
# v2 计划的交接凭证由此补录:finalize 不写 context 事务,缺它 run 阶段进不去
|
|
736
|
+
if phase != "plan":
|
|
737
|
+
_bootstrap_v2_plan_transition(
|
|
738
|
+
project,
|
|
739
|
+
change,
|
|
740
|
+
contract_root=contract_root,
|
|
741
|
+
state_root=state_root,
|
|
742
|
+
to_phase=phase,
|
|
743
|
+
executor=executor,
|
|
744
|
+
)
|
|
735
745
|
claim = _claim_prepared_context(
|
|
736
746
|
project,
|
|
737
747
|
change,
|
|
@@ -779,6 +789,150 @@ def prepare_context(
|
|
|
779
789
|
}
|
|
780
790
|
|
|
781
791
|
|
|
792
|
+
def _plan_run_identity(change_dir: Path) -> tuple[str, int] | None:
|
|
793
|
+
"""已存在的 plan `phase.start` 身份(run-id, attempt)。
|
|
794
|
+
|
|
795
|
+
finalize 按生命周期身份 fail-closed:重跑引导时换一个 run-id,整条链路就对不上。
|
|
796
|
+
因此引导必须复用已有身份,而不是每次新生成。
|
|
797
|
+
"""
|
|
798
|
+
for item in reversed(_read_ndjson(change_dir / "events.ndjson")):
|
|
799
|
+
if item.get("phase") != "plan" or item.get("type") != "phase.start":
|
|
800
|
+
continue
|
|
801
|
+
run_id = item.get("run_id")
|
|
802
|
+
if isinstance(run_id, str) and run_id:
|
|
803
|
+
attempt = item.get("attempt")
|
|
804
|
+
return run_id, attempt if isinstance(attempt, int) and attempt > 0 else 1
|
|
805
|
+
return None
|
|
806
|
+
|
|
807
|
+
|
|
808
|
+
def bootstrap_plan(
|
|
809
|
+
project: Path,
|
|
810
|
+
*,
|
|
811
|
+
change: str,
|
|
812
|
+
executor: str,
|
|
813
|
+
display_title: str | None = None,
|
|
814
|
+
stage: str = "plan",
|
|
815
|
+
ttl_seconds: int = 3600,
|
|
816
|
+
) -> dict[str, Any]:
|
|
817
|
+
"""阶段 0 一次性引导:doctor + prepare + capture + classify + phase.start。
|
|
818
|
+
|
|
819
|
+
拆成五条子进程调用时,每条的完整 argv 都要调用方记住(少 `--project` 或
|
|
820
|
+
`--change-dir` 就白跑一轮),run-id 还要调用方自己保证小写字母开头。这些都是
|
|
821
|
+
确定性工作,交给脚本做;调用方只需要一次调用和一份紧凑摘要。
|
|
822
|
+
|
|
823
|
+
重跑安全:已有 plan `phase.start` 时复用同一 run-id / attempt,不追加第二条。
|
|
824
|
+
"""
|
|
825
|
+
import uuid
|
|
826
|
+
|
|
827
|
+
import harness_change as hchg
|
|
828
|
+
import harness_events as he
|
|
829
|
+
import harness_gate as hg
|
|
830
|
+
import harness_runtime as hr
|
|
831
|
+
import harness_state as hs
|
|
832
|
+
|
|
833
|
+
project = Path(project).resolve()
|
|
834
|
+
changes_root = project / ".harness" / "changes"
|
|
835
|
+
if not changes_root.is_dir():
|
|
836
|
+
return {
|
|
837
|
+
"ok": False,
|
|
838
|
+
"code": "PROJECT_ROOT_INVALID",
|
|
839
|
+
"error": f"{changes_root} 不存在——先在该项目运行 hunter-harness init",
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
# 新 change 的目录此前没有任何脚本负责创建,靠的是后续 Write 的副作用;
|
|
843
|
+
# 引导阶段显式建立骨架,prepare 才有 contract 可读
|
|
844
|
+
change_dir = changes_root / change
|
|
845
|
+
created_change = not change_dir.is_dir()
|
|
846
|
+
if created_change:
|
|
847
|
+
(change_dir / "meta").mkdir(parents=True, exist_ok=True)
|
|
848
|
+
hchg.migrate_change(project, change)
|
|
849
|
+
|
|
850
|
+
prepared = prepare_context(
|
|
851
|
+
project,
|
|
852
|
+
phase="plan",
|
|
853
|
+
executor=executor,
|
|
854
|
+
change=change,
|
|
855
|
+
display_title=display_title,
|
|
856
|
+
ttl_seconds=ttl_seconds,
|
|
857
|
+
)
|
|
858
|
+
if not prepared.get("ok"):
|
|
859
|
+
return prepared
|
|
860
|
+
|
|
861
|
+
change_name = str(prepared["changeName"])
|
|
862
|
+
# executionRoot 是代码执行根(无 worktree 时等于项目根),change_dir 才是状态目录。
|
|
863
|
+
# 计划产物一律落 change_dir,混用会把 gate-policy/events 写到项目根去。
|
|
864
|
+
change_dir = (changes_root / change_name).resolve()
|
|
865
|
+
execution_root = Path(prepared["executionRoot"])
|
|
866
|
+
|
|
867
|
+
# doctor 失败不阻断引导:它是环境体检,结论随摘要返回给调用方判断
|
|
868
|
+
try:
|
|
869
|
+
health = hr.doctor(project, change_dir, agent=executor)
|
|
870
|
+
except Exception as exc: # noqa: BLE001 - 体检失败要报告而不是中断引导
|
|
871
|
+
health = {"ok": False, "code": "DOCTOR_FAILED", "error": str(exc)}
|
|
872
|
+
|
|
873
|
+
# 首次 capture 把当时的 HEAD 固定为不可变 changeBase(design §3.6)
|
|
874
|
+
snapshot, changed_segments = hs.capture_current_state(
|
|
875
|
+
project=project,
|
|
876
|
+
change_dir=change_dir,
|
|
877
|
+
change_name=change_name,
|
|
878
|
+
worktree_root=project,
|
|
879
|
+
)
|
|
880
|
+
git_state = snapshot.get("git") or {}
|
|
881
|
+
|
|
882
|
+
workflow = hg._load_workflow_policy(project=project)
|
|
883
|
+
classification = hg.classify_risk(change_dir, stage, workflow=workflow)
|
|
884
|
+
classification.setdefault("tierOverride", None)
|
|
885
|
+
classification["classifiedAt"] = _now().isoformat().replace("+00:00", "Z")
|
|
886
|
+
policy_path = change_dir / "meta" / "gate-policy.json"
|
|
887
|
+
hg._write_json(policy_path, hg.gate_policy_document(classification))
|
|
888
|
+
|
|
889
|
+
identity = _plan_run_identity(change_dir)
|
|
890
|
+
reused = identity is not None
|
|
891
|
+
if identity is None:
|
|
892
|
+
# v2 identity:必须小写字母开头,裸 UUID 有 10/16 概率数字开头被拒
|
|
893
|
+
run_id = f"plan_{uuid.uuid4()}"
|
|
894
|
+
attempt = 1
|
|
895
|
+
appended = he.append_event(
|
|
896
|
+
change_dir,
|
|
897
|
+
phase="plan",
|
|
898
|
+
type_="phase.start",
|
|
899
|
+
run_id=run_id,
|
|
900
|
+
attempt=attempt,
|
|
901
|
+
executor_tool=executor,
|
|
902
|
+
note=f"/harness-plan 引导:{change_name}",
|
|
903
|
+
)
|
|
904
|
+
if not appended.get("ok", True):
|
|
905
|
+
return appended
|
|
906
|
+
else:
|
|
907
|
+
run_id, attempt = identity
|
|
908
|
+
|
|
909
|
+
return {
|
|
910
|
+
"ok": True,
|
|
911
|
+
"code": "PLAN_BOOTSTRAPPED",
|
|
912
|
+
"changeName": change_name,
|
|
913
|
+
"displayTitle": prepared.get("displayTitle"),
|
|
914
|
+
"changeCreated": created_change,
|
|
915
|
+
"changeDir": str(change_dir),
|
|
916
|
+
"executionRoot": str(execution_root),
|
|
917
|
+
"runId": run_id,
|
|
918
|
+
"attempt": attempt,
|
|
919
|
+
"reused": reused,
|
|
920
|
+
"tier": classification.get("tier"),
|
|
921
|
+
"tierSource": classification.get("source"),
|
|
922
|
+
"defaultPhases": list(classification.get("defaultPhases") or []),
|
|
923
|
+
"conditionalPhases": list(classification.get("conditionalPhases") or []),
|
|
924
|
+
"requiredValidations": list(classification.get("requiredValidations") or []),
|
|
925
|
+
"gatePolicyPath": str(policy_path),
|
|
926
|
+
"plannedPhases": prepared.get("plannedPhases"),
|
|
927
|
+
"changeBase": git_state.get("base"),
|
|
928
|
+
"head": git_state.get("head"),
|
|
929
|
+
"changedSegments": sorted(changed_segments),
|
|
930
|
+
"legacyBootstrap": prepared.get("legacyBootstrap"),
|
|
931
|
+
"doctorOk": bool(health.get("ok", True)),
|
|
932
|
+
"doctor": health,
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
|
|
782
936
|
def _invalidate_for_fixback(
|
|
783
937
|
state_root: Path,
|
|
784
938
|
*,
|
|
@@ -808,6 +962,90 @@ def _invalidate_for_fixback(
|
|
|
808
962
|
return record
|
|
809
963
|
|
|
810
964
|
|
|
965
|
+
def _committed_publication_journal(contract_root: Path) -> Path | None:
|
|
966
|
+
"""已 committed 的 v2 发布 journal —— plan 确实完成的机器证据。"""
|
|
967
|
+
journal_dir = contract_root / "meta" / "publication-journals"
|
|
968
|
+
if not journal_dir.is_dir():
|
|
969
|
+
return None
|
|
970
|
+
for path in sorted(journal_dir.glob("*.json")):
|
|
971
|
+
try:
|
|
972
|
+
payload = _read_json(path)
|
|
973
|
+
except (OSError, ValueError, json.JSONDecodeError):
|
|
974
|
+
continue
|
|
975
|
+
if isinstance(payload, dict) and payload.get("state") == "committed":
|
|
976
|
+
return path
|
|
977
|
+
return None
|
|
978
|
+
|
|
979
|
+
|
|
980
|
+
def _published_plan_artifacts(project: Path, contract_root: Path, change: str) -> list[dict[str, Any]]:
|
|
981
|
+
"""交接携带的 v2 发布产物(存在者入账,缺失者跳过)。"""
|
|
982
|
+
entries: list[dict[str, Any]] = []
|
|
983
|
+
for rel in (
|
|
984
|
+
f"plans/{change}-design.md",
|
|
985
|
+
f"plans/{change}-plan.md",
|
|
986
|
+
f"plans/{change}-implementation-detail.md",
|
|
987
|
+
f"plans/{change}-test-scenarios.md",
|
|
988
|
+
"meta/scenario-manifest.json",
|
|
989
|
+
):
|
|
990
|
+
candidate = contract_root / rel
|
|
991
|
+
if candidate.is_file():
|
|
992
|
+
entries.append(
|
|
993
|
+
{
|
|
994
|
+
"path": candidate.resolve().relative_to(project).as_posix(),
|
|
995
|
+
"sha256": _sha256(candidate),
|
|
996
|
+
}
|
|
997
|
+
)
|
|
998
|
+
return entries
|
|
999
|
+
|
|
1000
|
+
|
|
1001
|
+
def _bootstrap_v2_plan_transition(
|
|
1002
|
+
project: Path,
|
|
1003
|
+
change: str,
|
|
1004
|
+
*,
|
|
1005
|
+
contract_root: Path,
|
|
1006
|
+
state_root: Path,
|
|
1007
|
+
to_phase: str,
|
|
1008
|
+
executor: str,
|
|
1009
|
+
) -> dict[str, Any] | None:
|
|
1010
|
+
"""v2 发布已 committed 却没有交接凭证时,补录 plan → to_phase 凭证。
|
|
1011
|
+
|
|
1012
|
+
v2 plan finalize 只写 plan-events.ndjson 与发布 journal,不碰 context 事务
|
|
1013
|
+
存储,于是 run 阶段 prepare 必报 HANDOFF_REQUIRED、begin 必报
|
|
1014
|
+
LEGACY_BOOTSTRAP_REQUIRED——两个错误都不给恢复路径,调用方只能读脚本源码
|
|
1015
|
+
自己拼出 classify + configure-plan + close,参数全靠现编,可审计性更差。
|
|
1016
|
+
|
|
1017
|
+
committed 的发布 journal 是比人工 close 更强的完成证据:据此补录,并在凭证上
|
|
1018
|
+
留 bootstrapSource/bootstrapEvidence,事后能与人工 close 区分。没有该证据时
|
|
1019
|
+
返回 None,交接继续 fail-closed。
|
|
1020
|
+
"""
|
|
1021
|
+
paths = _paths(state_root)
|
|
1022
|
+
if _read_ndjson(paths["transitions"]):
|
|
1023
|
+
return None
|
|
1024
|
+
if to_phase not in _allowed_next_phases(contract_root, "plan"):
|
|
1025
|
+
return None
|
|
1026
|
+
journal = _committed_publication_journal(contract_root)
|
|
1027
|
+
if journal is None:
|
|
1028
|
+
return None
|
|
1029
|
+
receipt: dict[str, Any] = {
|
|
1030
|
+
"schemaVersion": 1,
|
|
1031
|
+
"changeName": change,
|
|
1032
|
+
"fromPhase": "plan",
|
|
1033
|
+
"toPhase": to_phase,
|
|
1034
|
+
"status": "OK",
|
|
1035
|
+
"executor": executor,
|
|
1036
|
+
"productCommit": _head(project),
|
|
1037
|
+
"artifacts": _published_plan_artifacts(project, contract_root, change),
|
|
1038
|
+
"attempt": 1,
|
|
1039
|
+
"closedAt": _now().isoformat(),
|
|
1040
|
+
"previousReceiptHash": None,
|
|
1041
|
+
"bootstrapSource": "plan_publication_journal",
|
|
1042
|
+
"bootstrapEvidence": journal.resolve().relative_to(project).as_posix(),
|
|
1043
|
+
}
|
|
1044
|
+
receipt["receiptHash"] = _payload_hash(receipt)
|
|
1045
|
+
_append_ndjson(paths["transitions"], receipt)
|
|
1046
|
+
return receipt
|
|
1047
|
+
|
|
1048
|
+
|
|
811
1049
|
def close_transition(
|
|
812
1050
|
project: Path,
|
|
813
1051
|
change: str,
|
|
@@ -1007,11 +1245,26 @@ def _begin_transition_unlocked(
|
|
|
1007
1245
|
"message": "本次修复准备凭证与当前上下文不一致,未启动新阶段。",
|
|
1008
1246
|
}
|
|
1009
1247
|
transitions = _read_ndjson(paths["transitions"])
|
|
1248
|
+
if not transitions and phase != "plan":
|
|
1249
|
+
# prepare 之外的入口(或跳过了 prepare)同样要能从发布证据恢复
|
|
1250
|
+
_bootstrap_v2_plan_transition(
|
|
1251
|
+
project,
|
|
1252
|
+
change,
|
|
1253
|
+
contract_root=contract_root,
|
|
1254
|
+
state_root=state_root,
|
|
1255
|
+
to_phase=phase,
|
|
1256
|
+
executor=executor,
|
|
1257
|
+
)
|
|
1258
|
+
transitions = _read_ndjson(paths["transitions"])
|
|
1010
1259
|
if not transitions:
|
|
1011
1260
|
return {
|
|
1012
1261
|
"ok": False,
|
|
1013
1262
|
"code": "LEGACY_BOOTSTRAP_REQUIRED",
|
|
1014
1263
|
"legacyBootstrap": True,
|
|
1264
|
+
"message": (
|
|
1265
|
+
"没有交接凭证。v2 计划应有 committed 的 meta/publication-journals/*.json "
|
|
1266
|
+
"作为补录证据;legacy 计划需先完成 plan 阶段的 close"
|
|
1267
|
+
),
|
|
1015
1268
|
}
|
|
1016
1269
|
receipt = transitions[-1]
|
|
1017
1270
|
if receipt.get("toPhase") != phase:
|
|
@@ -1256,6 +1509,14 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
1256
1509
|
prepare.add_argument("--title")
|
|
1257
1510
|
prepare.add_argument("--ttl-seconds", type=int, default=3600)
|
|
1258
1511
|
prepare.add_argument("--trigger", choices=["review-fixback"])
|
|
1512
|
+
bootstrap = sub.add_parser("bootstrap-plan")
|
|
1513
|
+
bootstrap.add_argument("--json", action="store_true")
|
|
1514
|
+
bootstrap.add_argument("--project", required=True, type=Path)
|
|
1515
|
+
bootstrap.add_argument("--change", required=True)
|
|
1516
|
+
bootstrap.add_argument("--executor", required=True)
|
|
1517
|
+
bootstrap.add_argument("--title")
|
|
1518
|
+
bootstrap.add_argument("--stage", default="plan", choices=["plan", "post-run"])
|
|
1519
|
+
bootstrap.add_argument("--ttl-seconds", type=int, default=3600)
|
|
1259
1520
|
close = sub.add_parser("close")
|
|
1260
1521
|
close.add_argument("--json", action="store_true")
|
|
1261
1522
|
close.add_argument("--project", required=True, type=Path)
|
|
@@ -1297,6 +1558,15 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
1297
1558
|
ttl_seconds=args.ttl_seconds,
|
|
1298
1559
|
trigger=args.trigger,
|
|
1299
1560
|
)
|
|
1561
|
+
elif args.command == "bootstrap-plan":
|
|
1562
|
+
result = bootstrap_plan(
|
|
1563
|
+
args.project,
|
|
1564
|
+
change=args.change,
|
|
1565
|
+
executor=args.executor,
|
|
1566
|
+
display_title=args.title,
|
|
1567
|
+
stage=args.stage,
|
|
1568
|
+
ttl_seconds=args.ttl_seconds,
|
|
1569
|
+
)
|
|
1300
1570
|
elif args.command == "close":
|
|
1301
1571
|
result = close_transition(
|
|
1302
1572
|
args.project,
|
|
@@ -2024,6 +2024,70 @@ def _deferred_hint(deferred_ids: list[str], phase: str | None) -> str:
|
|
|
2024
2024
|
)
|
|
2025
2025
|
|
|
2026
2026
|
|
|
2027
|
+
_PROJECT_ROOT_HELP = (
|
|
2028
|
+
"project root PATH (defaults to resolving from CWD). Takes a path, not a project name."
|
|
2029
|
+
)
|
|
2030
|
+
|
|
2031
|
+
|
|
2032
|
+
def _resolve_project(args: argparse.Namespace) -> Path:
|
|
2033
|
+
"""--project 优先,未给时按 CWD 解析主项目根。
|
|
2034
|
+
|
|
2035
|
+
其他 harness 脚本都把 --project 当项目根且列为必填,只有 gate 例外——调用方
|
|
2036
|
+
按惯例传过来会被 argparse 拒掉。注意 begin/close 的 --project 是另一层语义
|
|
2037
|
+
(本阶段执行根/worktree),本函数只服务 classify/checkpoint。
|
|
2038
|
+
"""
|
|
2039
|
+
raw = getattr(args, "project", None)
|
|
2040
|
+
if raw:
|
|
2041
|
+
return Path(raw).expanduser().resolve()
|
|
2042
|
+
return hc.resolve_main_project_root()
|
|
2043
|
+
|
|
2044
|
+
|
|
2045
|
+
# 门禁消费的场景字段。v2 plan finalize 派生的 scenario_manifest 一个都没有:
|
|
2046
|
+
# 它只带 scenario_id/coverage_dimension/execution_level/evidence_requirements/
|
|
2047
|
+
# risk_level/task_refs/requirement_refs。
|
|
2048
|
+
_V2_MANIFEST_REQUIRED_FIELDS = (
|
|
2049
|
+
"id",
|
|
2050
|
+
"priority",
|
|
2051
|
+
"requiredEvidenceKind",
|
|
2052
|
+
"ownerPhase",
|
|
2053
|
+
"executableTestId",
|
|
2054
|
+
"testFile",
|
|
2055
|
+
"testTitle",
|
|
2056
|
+
)
|
|
2057
|
+
|
|
2058
|
+
|
|
2059
|
+
def _v2_artifact_manifest_gap(manifest: Any) -> dict[str, Any] | None:
|
|
2060
|
+
"""v2 plan artifact 包装的 scenario-manifest → 明确的不可消费错误。
|
|
2061
|
+
|
|
2062
|
+
不能"解包 + 字段改名"了事:包装体里既没有 priority 也没有
|
|
2063
|
+
requiredEvidenceKind,`required_ids` 会算成空集,C9 随即返回
|
|
2064
|
+
NO_LEDGER_REQUIRED_SCENARIOS——那是把证据门禁对所有 v2 计划静默关掉,
|
|
2065
|
+
比直接失败危险得多。所以这里点名缺口,让调用方回规划阶段补齐后重新发布。
|
|
2066
|
+
|
|
2067
|
+
v2 场景契约补齐这些字段后,解包逻辑加在这里。
|
|
2068
|
+
"""
|
|
2069
|
+
if not isinstance(manifest, dict) or manifest.get("artifact_type") != "scenario_manifest":
|
|
2070
|
+
return None
|
|
2071
|
+
content = manifest.get("content")
|
|
2072
|
+
present: set[str] = set()
|
|
2073
|
+
if isinstance(content, dict) and isinstance(content.get("scenarios"), list):
|
|
2074
|
+
for scenario in content["scenarios"]:
|
|
2075
|
+
if isinstance(scenario, dict):
|
|
2076
|
+
present.update(scenario.keys())
|
|
2077
|
+
return {
|
|
2078
|
+
"ok": False,
|
|
2079
|
+
"code": "SCENARIO_MANIFEST_V2_UNSUPPORTED",
|
|
2080
|
+
"message": (
|
|
2081
|
+
"meta/scenario-manifest.json 是 v2 plan artifact 包装体,缺少门禁消费的场景字段;"
|
|
2082
|
+
"请在规划阶段补齐后重新发布,不要手改派生产物"
|
|
2083
|
+
),
|
|
2084
|
+
"missingFields": [
|
|
2085
|
+
field for field in _V2_MANIFEST_REQUIRED_FIELDS if field not in present
|
|
2086
|
+
],
|
|
2087
|
+
"artifactType": "scenario_manifest",
|
|
2088
|
+
}
|
|
2089
|
+
|
|
2090
|
+
|
|
2027
2091
|
def _validate_scenario_coverage(
|
|
2028
2092
|
change_dir: Path, phase: str | None = None
|
|
2029
2093
|
) -> dict[str, Any]:
|
|
@@ -2050,6 +2114,9 @@ def _validate_scenario_coverage(
|
|
|
2050
2114
|
"code": "SCENARIO_MANIFEST_INVALID",
|
|
2051
2115
|
"message": f"scenario-manifest.json unreadable: {exc}",
|
|
2052
2116
|
}
|
|
2117
|
+
v2_gap = _v2_artifact_manifest_gap(manifest)
|
|
2118
|
+
if v2_gap is not None:
|
|
2119
|
+
return v2_gap
|
|
2053
2120
|
scenarios = manifest.get("scenarios") if isinstance(manifest, dict) else None
|
|
2054
2121
|
if not isinstance(scenarios, list):
|
|
2055
2122
|
return {
|
|
@@ -3218,7 +3285,7 @@ def cmd_close(args: argparse.Namespace) -> int:
|
|
|
3218
3285
|
|
|
3219
3286
|
def cmd_classify(args: argparse.Namespace) -> int:
|
|
3220
3287
|
as_json = bool(args.json)
|
|
3221
|
-
project =
|
|
3288
|
+
project = _resolve_project(args)
|
|
3222
3289
|
try:
|
|
3223
3290
|
workflow = _load_workflow_policy(project=project)
|
|
3224
3291
|
except (OSError, ValueError, hwp.PolicyValidationError) as exc:
|
|
@@ -3271,7 +3338,7 @@ def cmd_classify(args: argparse.Namespace) -> int:
|
|
|
3271
3338
|
|
|
3272
3339
|
def cmd_checkpoint(args: argparse.Namespace) -> int:
|
|
3273
3340
|
as_json = bool(args.json)
|
|
3274
|
-
project =
|
|
3341
|
+
project = _resolve_project(args)
|
|
3275
3342
|
resolved = hc.resolve_change(project, args.change)
|
|
3276
3343
|
if not resolved.get("ok"):
|
|
3277
3344
|
return emit_error(
|
|
@@ -3425,6 +3492,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
3425
3492
|
p_close.set_defaults(func=cmd_close)
|
|
3426
3493
|
|
|
3427
3494
|
p_classify = sub.add_parser("classify", parents=[shared])
|
|
3495
|
+
p_classify.add_argument("--project", default=None, type=Path, help=_PROJECT_ROOT_HELP)
|
|
3428
3496
|
p_classify.add_argument("--change", default=None)
|
|
3429
3497
|
p_classify.add_argument("--stage", required=True, choices=["plan", "post-run"])
|
|
3430
3498
|
p_classify.add_argument(
|
|
@@ -3436,6 +3504,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
3436
3504
|
p_classify.set_defaults(func=cmd_classify)
|
|
3437
3505
|
|
|
3438
3506
|
p_checkpoint = sub.add_parser("checkpoint", parents=[shared])
|
|
3507
|
+
p_checkpoint.add_argument("--project", default=None, type=Path, help=_PROJECT_ROOT_HELP)
|
|
3439
3508
|
p_checkpoint.add_argument("checkpoint_action", choices=["status", "approve"])
|
|
3440
3509
|
p_checkpoint.add_argument("--id", required=True)
|
|
3441
3510
|
p_checkpoint.add_argument("--change", default=None)
|
|
@@ -304,12 +304,23 @@ def _compact_record_payload(payload: dict[str, Any]) -> dict[str, Any]:
|
|
|
304
304
|
|
|
305
305
|
|
|
306
306
|
def _compact_can_reuse_payload(payload: dict[str, Any]) -> dict[str, Any]:
|
|
307
|
-
"""C5: can-reuse compact — ok/reuse/code
|
|
308
|
-
|
|
307
|
+
"""C5: can-reuse compact — ok/reuse/code;拒绝复用时附带可执行的原因。
|
|
308
|
+
|
|
309
|
+
只回 ok/reuse/code 时,调用方拿到裸 `{"reuse": false}` 无从判断该怎么办,
|
|
310
|
+
只能再跑一次 `--verbose`——原因(profile 缺 verificationInputs、证据不完整等)
|
|
311
|
+
是单行短文本,值得留在默认输出里。允许复用时保持原样精简。
|
|
312
|
+
"""
|
|
313
|
+
compact = {
|
|
309
314
|
"ok": payload.get("ok", True),
|
|
310
315
|
"reuse": payload.get("reuse"),
|
|
311
316
|
"code": payload.get("code"),
|
|
312
317
|
}
|
|
318
|
+
if payload.get("reuse") is not True:
|
|
319
|
+
for key in ("reason", "executionNeed", "detail"):
|
|
320
|
+
value = payload.get(key)
|
|
321
|
+
if value not in (None, "", [], {}):
|
|
322
|
+
compact[key] = value
|
|
323
|
+
return compact
|
|
313
324
|
|
|
314
325
|
|
|
315
326
|
def emit_compact_or_verbose(
|
|
@@ -2581,9 +2592,19 @@ def cmd_record(args: argparse.Namespace) -> int:
|
|
|
2581
2592
|
error_code="PROFILE_INPUT_FILES_CONFLICT",
|
|
2582
2593
|
)
|
|
2583
2594
|
files = profile_files
|
|
2584
|
-
|
|
2595
|
+
# NOT_APPLICABLE 的 validation 没有输入文件可言。门禁 close 又要求
|
|
2596
|
+
# requiredValidations 每项都有 entry——两条规则叠加会逼调用方拿无关文件凑数,
|
|
2597
|
+
# ledger 从此声称该 validation 的输入是那些文件,是假证据。
|
|
2598
|
+
not_applicable = str(getattr(args, "applicability", "") or "") == "NOT_APPLICABLE"
|
|
2599
|
+
if not files and not not_applicable:
|
|
2600
|
+
return emit_error(
|
|
2601
|
+
"record requires --files or a non-empty --profile-input file set; "
|
|
2602
|
+
"unless --applicability NOT_APPLICABLE (then --applicability-reason is required)",
|
|
2603
|
+
as_json=as_json,
|
|
2604
|
+
)
|
|
2605
|
+
if not_applicable and not _nonempty_str(getattr(args, "applicability_reason", None)):
|
|
2585
2606
|
return emit_error(
|
|
2586
|
-
"
|
|
2607
|
+
"NOT_APPLICABLE requires --applicability-reason",
|
|
2587
2608
|
as_json=as_json,
|
|
2588
2609
|
)
|
|
2589
2610
|
|
|
@@ -1101,6 +1101,43 @@ def _derive_verification_graph(profile: dict[str, Any]) -> None:
|
|
|
1101
1101
|
}
|
|
1102
1102
|
|
|
1103
1103
|
|
|
1104
|
+
def manual_profile_template(
|
|
1105
|
+
excluded: tuple[str, ...] | list[str], components: list[str]
|
|
1106
|
+
) -> dict[str, Any]:
|
|
1107
|
+
"""歧义时交给调用方的可填骨架。
|
|
1108
|
+
|
|
1109
|
+
骨架本身就是 `empty_profile_skeleton`——defaultsFingerprint、excludedRoots
|
|
1110
|
+
默认集、commands/verificationInputs/verificationGraph 的键都已就位;这里只是
|
|
1111
|
+
补上 commands 的条目形状与 requiredCoverage 取值,省掉调用方去源码里翻。
|
|
1112
|
+
占位值用 `<...>` 包裹,便于替换后 grep 自检。
|
|
1113
|
+
"""
|
|
1114
|
+
template = empty_profile_skeleton(excluded)
|
|
1115
|
+
template["projectType"] = (
|
|
1116
|
+
f"<从 detectedComponents 选定:{'|'.join(components)}>" if components else "<项目类型>"
|
|
1117
|
+
)
|
|
1118
|
+
template["commands"] = {
|
|
1119
|
+
"unitTest": {
|
|
1120
|
+
"command": "<在项目根可直接执行的增量测试命令>",
|
|
1121
|
+
"scope": "incremental",
|
|
1122
|
+
"inputs": ["<相对项目根的 glob,如 <component>/test/**>"],
|
|
1123
|
+
"coverage": "unitTest",
|
|
1124
|
+
"requiredCoverage": "incremental",
|
|
1125
|
+
"dependsOn": [],
|
|
1126
|
+
"source": "user",
|
|
1127
|
+
},
|
|
1128
|
+
"unitTestFull": {
|
|
1129
|
+
"command": "<在项目根可直接执行的全量测试命令>",
|
|
1130
|
+
"scope": "full",
|
|
1131
|
+
"inputs": ["<相对项目根的 glob,如 <component>/**>"],
|
|
1132
|
+
"coverage": "unitTestFull",
|
|
1133
|
+
"requiredCoverage": "module",
|
|
1134
|
+
"dependsOn": [],
|
|
1135
|
+
"source": "user",
|
|
1136
|
+
},
|
|
1137
|
+
}
|
|
1138
|
+
return template
|
|
1139
|
+
|
|
1140
|
+
|
|
1104
1141
|
def detect(project: Path) -> dict[str, Any]:
|
|
1105
1142
|
project = project.resolve()
|
|
1106
1143
|
existing = load_profile(project)
|
|
@@ -1120,6 +1157,14 @@ def detect(project: Path) -> dict[str, Any]:
|
|
|
1120
1157
|
"applied": False,
|
|
1121
1158
|
"detectedComponents": components,
|
|
1122
1159
|
"message": "multiple or nested project components require an explicit profile",
|
|
1160
|
+
# 拒绝生成可以,但不能连结构都不给:调用方否则只能反读本文件源码去凑
|
|
1161
|
+
# v3 形状。骨架不落盘——一份占位 profile 看起来"已配好"比没有更糟。
|
|
1162
|
+
"profileTemplate": manual_profile_template(excluded, components),
|
|
1163
|
+
"hint": (
|
|
1164
|
+
"手工声明一份 profile:以 profileTemplate 为骨架,按 detectedComponents "
|
|
1165
|
+
"选定产品包,commands 里的 inputs 用相对项目根的 glob(如 "
|
|
1166
|
+
f"'<component>/test/**'),填好后写入 {project / PROFILE_REL}"
|
|
1167
|
+
),
|
|
1123
1168
|
}
|
|
1124
1169
|
if project_type == "unknown" and existing:
|
|
1125
1170
|
return {
|
|
@@ -332,12 +332,15 @@ def _matches_pattern(rel: str, pattern: str) -> bool:
|
|
|
332
332
|
|
|
333
333
|
|
|
334
334
|
def _standard_test_path(rel: str) -> bool:
|
|
335
|
+
"""路径中出现 test/tests 目录段即视为标准测试路径。
|
|
336
|
+
|
|
337
|
+
此前只认 parts[0],于是 monorepo(packages/cli/tests/…)与"仓库根 + 同名
|
|
338
|
+
子包"(kld-sdd/test/…)这类常见布局全被拒,调用方被迫先补 build-profile
|
|
339
|
+
才能记录一次 stale-test-repair。段级匹配不放宽语义:testing/ 与
|
|
340
|
+
tests-helper/ 仍然不算,目录本身(末段)也不算文件。
|
|
341
|
+
"""
|
|
335
342
|
parts = rel.split("/")
|
|
336
|
-
|
|
337
|
-
return True
|
|
338
|
-
if any(parts[index:index + 2] == ["src", "test"] for index in range(len(parts) - 1)):
|
|
339
|
-
return True
|
|
340
|
-
return False
|
|
343
|
+
return any(part in ("test", "tests") for part in parts[:-1])
|
|
341
344
|
|
|
342
345
|
|
|
343
346
|
def _allowed_test_path(project: Path, rel: str) -> bool:
|
|
@@ -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=2c27498e1b85cfca; overlay=none; agent=cursor; do not edit -->
|
|
7
7
|
# harness-archive — 变更归档
|
|
8
8
|
|
|
9
9
|
## Purpose
|
|
@@ -39,9 +39,9 @@ description: 归档所有变更产出(计划/测试报告/审查/SQL/API文档
|
|
|
39
39
|
## 统一读取协议
|
|
40
40
|
|
|
41
41
|
1. **`.harness/changes/<change-name>/` 是唯一真相源** — 所有输入从该目录读取,产物写入对应子目录
|
|
42
|
-
2. **change-name 优先从 frontmatter 读取** — `spec/*-design.md`、`plans/*-plan.md` 的 YAML `change-name`
|
|
42
|
+
2. **change-name 优先从 frontmatter 读取** — `plans/*-design.md`、`spec/*-design.md`、`plans/*-plan.md` 的 YAML `change-name`
|
|
43
43
|
3. **frontmatter 缺失时兼容旧格式** — 从路径推断,标记 `🟡 legacy-plan`,不失败
|
|
44
|
-
4. **
|
|
44
|
+
4. **design** — 设计真相源按序取第一个存在的:`plans/<change>-design.md`(v2 发布产物,哈希绑定)→ `spec/<change>-design.md`(legacy 手写)。两份**同时存在**时以 `plans/` 为准,并记 `🟡 WARN 设计文档双份`——v2 发布的那份才受完整性门禁保护,读手写的那份等于绕过校验
|
|
45
45
|
5. **plan** — 任务真相源:`plans/<change>-plan.md`
|
|
46
46
|
6. **implementation-detail** — 自适应执行参考;legacy 缺失 🟡WARN,不阻断
|
|
47
47
|
7. **test-scenarios** — 测试真相源:`plans/<change>-test-scenarios.md`
|
|
@@ -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=2c27498e1b85cfca; overlay=none; agent=cursor; 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=2c27498e1b85cfca; overlay=none; agent=cursor; 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=2c27498e1b85cfca; overlay=none; agent=cursor; do not edit -->
|
|
6
6
|
# harness-knowledge-query
|
|
7
7
|
|
|
8
8
|
项目知识以 Hunter Platform 的服务端索引为唯一真源。客户端只提交查询并消费结果:
|