@hunter-harness/workflow-harness 0.2.77 → 0.2.79
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 +17 -2
- 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 +1 -1
- package/harness/bundles/general/claude-code/harness-pull/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-push/SKILL.md +16 -6
- package/harness/bundles/general/claude-code/harness-review/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-run/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-submit/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-test/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/protocols/powershell-protocol.md +20 -0
- package/harness/bundles/general/claude-code/protocols/sensitive-info-protocol.md +5 -0
- package/harness/bundles/general/claude-code/scripts/harness_archive.py +459 -46
- package/harness/bundles/general/claude-code/scripts/harness_check_gate.py +2 -0
- package/harness/bundles/general/claude-code/scripts/harness_context.py +4 -0
- package/harness/bundles/general/claude-code/scripts/harness_fixback.py +102 -3
- package/harness/bundles/general/claude-code/scripts/harness_knowledge_candidates.py +262 -0
- package/harness/bundles/general/claude-code/scripts/harness_ledger.py +4 -0
- package/harness/bundles/general/claude-code/scripts/harness_runtime.py +82 -4
- package/harness/bundles/general/codebuddy/.harness-build.json +1 -1
- package/harness/bundles/general/codebuddy/harness-archive/SKILL.md +17 -2
- 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 +1 -1
- package/harness/bundles/general/codebuddy/harness-pull/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-push/SKILL.md +15 -5
- package/harness/bundles/general/codebuddy/harness-review/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-run/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-submit/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-test/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/protocols/powershell-protocol.md +20 -0
- package/harness/bundles/general/codebuddy/protocols/sensitive-info-protocol.md +5 -0
- package/harness/bundles/general/codebuddy/scripts/harness_archive.py +459 -46
- package/harness/bundles/general/codebuddy/scripts/harness_check_gate.py +2 -0
- package/harness/bundles/general/codebuddy/scripts/harness_context.py +4 -0
- package/harness/bundles/general/codebuddy/scripts/harness_fixback.py +102 -3
- package/harness/bundles/general/codebuddy/scripts/harness_knowledge_candidates.py +262 -0
- package/harness/bundles/general/codebuddy/scripts/harness_ledger.py +4 -0
- package/harness/bundles/general/codebuddy/scripts/harness_runtime.py +82 -4
- package/harness/bundles/general/codex/.harness-build.json +1 -1
- package/harness/bundles/general/codex/harness-archive/SKILL.md +17 -2
- 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 +1 -1
- package/harness/bundles/general/codex/harness-pull/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-push/SKILL.md +15 -5
- package/harness/bundles/general/codex/harness-review/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-run/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/harness-test/SKILL.md +1 -1
- package/harness/bundles/general/codex/protocols/powershell-protocol.md +20 -0
- package/harness/bundles/general/codex/protocols/sensitive-info-protocol.md +5 -0
- package/harness/bundles/general/codex/scripts/harness_archive.py +459 -46
- package/harness/bundles/general/codex/scripts/harness_check_gate.py +2 -0
- package/harness/bundles/general/codex/scripts/harness_context.py +4 -0
- package/harness/bundles/general/codex/scripts/harness_fixback.py +102 -3
- package/harness/bundles/general/codex/scripts/harness_knowledge_candidates.py +262 -0
- package/harness/bundles/general/codex/scripts/harness_ledger.py +4 -0
- package/harness/bundles/general/codex/scripts/harness_runtime.py +82 -4
- package/harness/bundles/general/cursor/.harness-build.json +1 -1
- package/harness/bundles/general/cursor/harness-archive/SKILL.md +17 -2
- 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 +1 -1
- package/harness/bundles/general/cursor/harness-pull/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-push/SKILL.md +15 -5
- package/harness/bundles/general/cursor/harness-review/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-run/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/harness-test/SKILL.md +1 -1
- package/harness/bundles/general/cursor/protocols/powershell-protocol.md +20 -0
- package/harness/bundles/general/cursor/protocols/sensitive-info-protocol.md +5 -0
- package/harness/bundles/general/cursor/scripts/harness_archive.py +459 -46
- package/harness/bundles/general/cursor/scripts/harness_check_gate.py +2 -0
- package/harness/bundles/general/cursor/scripts/harness_context.py +4 -0
- package/harness/bundles/general/cursor/scripts/harness_fixback.py +102 -3
- package/harness/bundles/general/cursor/scripts/harness_knowledge_candidates.py +262 -0
- package/harness/bundles/general/cursor/scripts/harness_ledger.py +4 -0
- package/harness/bundles/general/cursor/scripts/harness_runtime.py +82 -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 +17 -2
- 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 +1 -1
- package/harness/bundles/java/claude-code/harness-pull/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-push/SKILL.md +16 -6
- package/harness/bundles/java/claude-code/harness-review/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-run/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-submit/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-test/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/protocols/powershell-protocol.md +20 -0
- package/harness/bundles/java/claude-code/protocols/sensitive-info-protocol.md +5 -0
- package/harness/bundles/java/claude-code/scripts/harness_archive.py +459 -46
- package/harness/bundles/java/claude-code/scripts/harness_check_gate.py +2 -0
- package/harness/bundles/java/claude-code/scripts/harness_context.py +4 -0
- package/harness/bundles/java/claude-code/scripts/harness_fixback.py +102 -3
- package/harness/bundles/java/claude-code/scripts/harness_knowledge_candidates.py +262 -0
- package/harness/bundles/java/claude-code/scripts/harness_ledger.py +4 -0
- package/harness/bundles/java/claude-code/scripts/harness_runtime.py +82 -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 +17 -2
- 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 +1 -1
- package/harness/bundles/java/codebuddy/harness-pull/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-push/SKILL.md +15 -5
- package/harness/bundles/java/codebuddy/harness-review/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-run/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-submit/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-test/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/protocols/powershell-protocol.md +20 -0
- package/harness/bundles/java/codebuddy/protocols/sensitive-info-protocol.md +5 -0
- package/harness/bundles/java/codebuddy/scripts/harness_archive.py +459 -46
- package/harness/bundles/java/codebuddy/scripts/harness_check_gate.py +2 -0
- package/harness/bundles/java/codebuddy/scripts/harness_context.py +4 -0
- package/harness/bundles/java/codebuddy/scripts/harness_fixback.py +102 -3
- package/harness/bundles/java/codebuddy/scripts/harness_knowledge_candidates.py +262 -0
- package/harness/bundles/java/codebuddy/scripts/harness_ledger.py +4 -0
- package/harness/bundles/java/codebuddy/scripts/harness_runtime.py +82 -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 +17 -2
- 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 +1 -1
- package/harness/bundles/java/codex/harness-pull/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-push/SKILL.md +15 -5
- package/harness/bundles/java/codex/harness-review/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-run/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/harness-test/SKILL.md +1 -1
- package/harness/bundles/java/codex/protocols/powershell-protocol.md +20 -0
- package/harness/bundles/java/codex/protocols/sensitive-info-protocol.md +5 -0
- package/harness/bundles/java/codex/scripts/harness_archive.py +459 -46
- package/harness/bundles/java/codex/scripts/harness_check_gate.py +2 -0
- package/harness/bundles/java/codex/scripts/harness_context.py +4 -0
- package/harness/bundles/java/codex/scripts/harness_fixback.py +102 -3
- package/harness/bundles/java/codex/scripts/harness_knowledge_candidates.py +262 -0
- package/harness/bundles/java/codex/scripts/harness_ledger.py +4 -0
- package/harness/bundles/java/codex/scripts/harness_runtime.py +82 -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 +17 -2
- 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 +1 -1
- package/harness/bundles/java/cursor/harness-pull/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-push/SKILL.md +15 -5
- package/harness/bundles/java/cursor/harness-review/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-run/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/harness-test/SKILL.md +1 -1
- package/harness/bundles/java/cursor/protocols/powershell-protocol.md +20 -0
- package/harness/bundles/java/cursor/protocols/sensitive-info-protocol.md +5 -0
- package/harness/bundles/java/cursor/scripts/harness_archive.py +459 -46
- package/harness/bundles/java/cursor/scripts/harness_check_gate.py +2 -0
- package/harness/bundles/java/cursor/scripts/harness_context.py +4 -0
- package/harness/bundles/java/cursor/scripts/harness_fixback.py +102 -3
- package/harness/bundles/java/cursor/scripts/harness_knowledge_candidates.py +262 -0
- package/harness/bundles/java/cursor/scripts/harness_ledger.py +4 -0
- package/harness/bundles/java/cursor/scripts/harness_runtime.py +82 -4
- package/harness/manifests/general/claude-code.json +27 -23
- package/harness/manifests/general/codebuddy.json +27 -23
- package/harness/manifests/general/codex.json +27 -23
- package/harness/manifests/general/cursor.json +27 -23
- package/harness/manifests/java/claude-code.json +29 -25
- package/harness/manifests/java/codebuddy.json +29 -25
- package/harness/manifests/java/codex.json +29 -25
- package/harness/manifests/java/cursor.json +29 -25
- package/hunter-workflow-family.json +5 -5
- package/package.json +1 -1
|
@@ -184,6 +184,8 @@ def _head(project: Path) -> str | None:
|
|
|
184
184
|
process = subprocess.run(
|
|
185
185
|
["git", "-C", str(project), "rev-parse", "--verify", "HEAD"],
|
|
186
186
|
text=True,
|
|
187
|
+
encoding="utf-8",
|
|
188
|
+
errors="replace",
|
|
187
189
|
stdout=subprocess.PIPE,
|
|
188
190
|
stderr=subprocess.PIPE,
|
|
189
191
|
check=False,
|
|
@@ -196,6 +198,8 @@ def _git_common_dir(project: Path) -> Path | None:
|
|
|
196
198
|
process = subprocess.run(
|
|
197
199
|
["git", "-C", str(project), "rev-parse", "--git-common-dir"],
|
|
198
200
|
text=True,
|
|
201
|
+
encoding="utf-8",
|
|
202
|
+
errors="replace",
|
|
199
203
|
stdout=subprocess.PIPE,
|
|
200
204
|
stderr=subprocess.PIPE,
|
|
201
205
|
check=False,
|
|
@@ -587,6 +587,13 @@ def launch_review_fixback(
|
|
|
587
587
|
"transition": begun,
|
|
588
588
|
"gate": gate,
|
|
589
589
|
"batch": batch,
|
|
590
|
+
# 证据要求随开批次一起交底:晚一步暴露,调用方就已经改完代码,
|
|
591
|
+
# 只能回退伪造 RED。
|
|
592
|
+
"evidenceContract": evidence_contract(
|
|
593
|
+
change_dir=str(change_dir),
|
|
594
|
+
batch_id=str(batch.get("batchId") or ""),
|
|
595
|
+
product_identity=str(batch.get("productIdentity") or ""),
|
|
596
|
+
),
|
|
590
597
|
}
|
|
591
598
|
|
|
592
599
|
|
|
@@ -802,12 +809,102 @@ def _resolve_evidence_path(change_dir: Path, raw_path: str) -> Path:
|
|
|
802
809
|
)
|
|
803
810
|
path = next((candidate for candidate in candidates if candidate.is_file()), None)
|
|
804
811
|
if path is None:
|
|
805
|
-
raise ValueError(
|
|
812
|
+
raise ValueError(evidence_error_message("FIXBACK_EVIDENCE_MISSING", raw_path))
|
|
806
813
|
if not _is_within(path, [contract_root, state_root, project_root]):
|
|
807
|
-
raise ValueError(
|
|
814
|
+
raise ValueError(
|
|
815
|
+
evidence_error_message("FIXBACK_EVIDENCE_OUTSIDE_PROJECT", raw_path)
|
|
816
|
+
)
|
|
808
817
|
return path
|
|
809
818
|
|
|
810
819
|
|
|
820
|
+
def evidence_contract(
|
|
821
|
+
*,
|
|
822
|
+
change_dir: str | None = None,
|
|
823
|
+
batch_id: str | None = None,
|
|
824
|
+
product_identity: str | None = None,
|
|
825
|
+
) -> dict[str, Any]:
|
|
826
|
+
"""开批次时就交出证据契约,而不是等 resolve-issue 报错才逼调用方逆向。
|
|
827
|
+
|
|
828
|
+
契约本身没变过:每个 code 项需要一条修复前的 RED(会话 FAIL)与一条修复后的
|
|
829
|
+
GREEN(会话 OK),两者都必须是 run-start 产出的托管会话证据,且先注册后引用。
|
|
830
|
+
问题在于它此前只写在实现里——调用方通常已经改完代码才发现 RED 要"修复前",
|
|
831
|
+
于是把改动回退、跑一次假 RED、再改回来,凭空多出一轮返工与一段脏工作树。
|
|
832
|
+
"""
|
|
833
|
+
change = change_dir or "<change-dir>"
|
|
834
|
+
batch = batch_id or "<batch-id>"
|
|
835
|
+
product = product_identity or "<product-identity>"
|
|
836
|
+
return {
|
|
837
|
+
"why": "RED 证明问题真实存在,GREEN 证明修复真的生效;缺任一条都无法关批次。",
|
|
838
|
+
"order": [
|
|
839
|
+
"1. 修复前:先用 run-start 跑一次能复现该问题的命令,得到 status=FAIL 的会话(RED)",
|
|
840
|
+
"2. 实施修复",
|
|
841
|
+
"3. 修复后:用同一条命令再跑一次 run-start,得到 status=OK 的会话(GREEN)",
|
|
842
|
+
"4. 两条证据各写一个 JSON 文件,分别 register-evidence 注册",
|
|
843
|
+
"5. resolve-issue 引用这两个已注册的证据文件",
|
|
844
|
+
],
|
|
845
|
+
"antiPattern": (
|
|
846
|
+
"不要先改完再把修改回退来凑 RED:那样 RED 证明的只是回退后的状态,"
|
|
847
|
+
"不是原始缺陷,中途还会留下脏工作树。"
|
|
848
|
+
),
|
|
849
|
+
"commands": {
|
|
850
|
+
"collectSession": (
|
|
851
|
+
"python harness_runtime.py run-start --state-root "
|
|
852
|
+
f"{change} --verification <名称> --working-directory . "
|
|
853
|
+
f"--product-identity {product} --json -- <复现/验证命令>"
|
|
854
|
+
),
|
|
855
|
+
"awaitSession": (
|
|
856
|
+
"python harness_runtime.py run-status --state-root "
|
|
857
|
+
f"{change} --session-id <sessionId> --wait --json"
|
|
858
|
+
),
|
|
859
|
+
"register": (
|
|
860
|
+
"python harness_fixback.py register-evidence --change-dir "
|
|
861
|
+
f"{change} --evidence <证据 JSON 路径>"
|
|
862
|
+
),
|
|
863
|
+
"resolve": (
|
|
864
|
+
"python harness_fixback.py resolve-issue --change-dir "
|
|
865
|
+
f"{change} --batch-id {batch} --issue-id <issueId> "
|
|
866
|
+
"--red-evidence <red.json> --green-evidence <green.json>"
|
|
867
|
+
),
|
|
868
|
+
},
|
|
869
|
+
"evidenceFile": {
|
|
870
|
+
"schemaVersion": 2,
|
|
871
|
+
"kind": ["red", "green"],
|
|
872
|
+
"status": {"red": "FAIL", "green": "OK"},
|
|
873
|
+
"provenance": {
|
|
874
|
+
"type": "managed-run-session",
|
|
875
|
+
"note": "指向 run-start 产出的 runtime/run-sessions/<sessionId>/session.json",
|
|
876
|
+
},
|
|
877
|
+
},
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
|
|
881
|
+
_EVIDENCE_ERROR_HINTS = {
|
|
882
|
+
"FIXBACK_EVIDENCE_MISSING": (
|
|
883
|
+
"路径未找到证据文件。它应当是 run-start 产出的托管会话证据 JSON"
|
|
884
|
+
"(schemaVersion=2,kind=red|green,provenance.type=managed-run-session);"
|
|
885
|
+
"先用 run-start 采集会话,再据此写证据文件。"
|
|
886
|
+
),
|
|
887
|
+
"FIXBACK_EVIDENCE_UNREGISTERED": (
|
|
888
|
+
"证据文件存在但未注册,或注册后内容已变。"
|
|
889
|
+
"先执行 register-evidence 注册(内容改动后需重新注册),再引用。"
|
|
890
|
+
),
|
|
891
|
+
"FIXBACK_EVIDENCE_INVALID": (
|
|
892
|
+
"证据文件不是合法的 schemaVersion=2 托管会话证据。"
|
|
893
|
+
"检查 kind / status / provenance 三个字段。"
|
|
894
|
+
),
|
|
895
|
+
"FIXBACK_EVIDENCE_OUTSIDE_PROJECT": (
|
|
896
|
+
"证据路径落在项目之外。证据必须放在变更目录或项目内,便于随归档留痕。"
|
|
897
|
+
),
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
|
|
901
|
+
def evidence_error_message(code: str, raw_path: str) -> str:
|
|
902
|
+
"""把只回显路径的错误码,补成能直接照做的一句话。"""
|
|
903
|
+
hint = _EVIDENCE_ERROR_HINTS.get(code)
|
|
904
|
+
base = f"{code}: {raw_path}"
|
|
905
|
+
return base if hint is None else f"{base} — {hint}"
|
|
906
|
+
|
|
907
|
+
|
|
811
908
|
def register_evidence(change_dir: Path, raw_path: str) -> dict[str, Any]:
|
|
812
909
|
"""Register evidence only after validating its authoritative provenance."""
|
|
813
910
|
path = _resolve_evidence_path(change_dir, raw_path)
|
|
@@ -968,7 +1065,9 @@ def _evidence_record(
|
|
|
968
1065
|
or ledger_record.get("productIdentity") != evidence_product
|
|
969
1066
|
or ledger_record.get("passedGates") != sorted(set(passed_gates))
|
|
970
1067
|
):
|
|
971
|
-
raise ValueError(
|
|
1068
|
+
raise ValueError(
|
|
1069
|
+
evidence_error_message("FIXBACK_EVIDENCE_UNREGISTERED", raw_path)
|
|
1070
|
+
)
|
|
972
1071
|
if product_identity is not None and evidence_product != product_identity:
|
|
973
1072
|
raise ValueError(
|
|
974
1073
|
"FIXBACK_EVIDENCE_IDENTITY_MISMATCH: "
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Knowledge candidate generation from an archived change's summary-data.json.
|
|
3
|
+
|
|
4
|
+
The archive workflow has already filtered once: review findings were produced by
|
|
5
|
+
an independent reviewer and then adjudicated, and knownRisks are evidence-derived
|
|
6
|
+
facts. This module turns those two — and only those two — into KnowledgeCandidate
|
|
7
|
+
records for the archive package's ``candidates/knowledge.json``.
|
|
8
|
+
|
|
9
|
+
Mapping is fixed by docs/superpowers/specs/2026-08-18-three-views-data-flow-design.md
|
|
10
|
+
("知识来源的选定")::
|
|
11
|
+
|
|
12
|
+
disposition = FIXED -> pitfall RED 0.95 / YELLOW 0.85
|
|
13
|
+
disposition = ACCEPTED_RISK | DEFERRED -> risk RED 0.95 / YELLOW 0.85
|
|
14
|
+
knownRisks[] -> risk 0.85
|
|
15
|
+
severity = OK | disposition = NOT_APPLICABLE -> dropped
|
|
16
|
+
|
|
17
|
+
Dispositions outside the adopted set (OPEN / UNKNOWN) are dropped too: an
|
|
18
|
+
unadjudicated finding is not yet knowledge. maintenanceNotes, finalStatusReasons
|
|
19
|
+
and manualActions are deliberately excluded — the spec evaluated each and found
|
|
20
|
+
them too noisy or empty to be worth persisting.
|
|
21
|
+
|
|
22
|
+
No LLM is involved. Every emitted field is copied or derived from a real
|
|
23
|
+
summary-data field, so the output is reproducible and free of invention.
|
|
24
|
+
"""
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import hashlib
|
|
28
|
+
import json
|
|
29
|
+
from typing import Any
|
|
30
|
+
|
|
31
|
+
SCHEMA_VERSION = 1
|
|
32
|
+
PRODUCER = "harness-archive"
|
|
33
|
+
|
|
34
|
+
# Only these severities carry knowledge; OK is explicitly dropped by the spec.
|
|
35
|
+
_SEVERITIES = {"RED", "YELLOW"}
|
|
36
|
+
# Adjudicated dispositions the spec adopts, mapped to the knowledge entry type.
|
|
37
|
+
_DISPOSITION_ENTRY_TYPES = {
|
|
38
|
+
"FIXED": "pitfall",
|
|
39
|
+
"ACCEPTED_RISK": "risk",
|
|
40
|
+
"DEFERRED": "risk",
|
|
41
|
+
}
|
|
42
|
+
_SEVERITY_CONFIDENCE = {"RED": 0.95, "YELLOW": 0.85}
|
|
43
|
+
_KNOWN_RISK_CONFIDENCE = 0.85
|
|
44
|
+
|
|
45
|
+
_MAX_KEYWORDS = 32
|
|
46
|
+
_MAX_KEYWORD_CHARS = 80
|
|
47
|
+
_MAX_BODY_CHARS = 20_000
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _text(value: Any) -> str:
|
|
51
|
+
return value.strip() if isinstance(value, str) else ""
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _digest(*parts: str) -> str:
|
|
55
|
+
return hashlib.sha256("\0".join(parts).encode("utf-8")).hexdigest()
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _candidate_id(change_key: str, kind: str, identity: str) -> str:
|
|
59
|
+
return f"kc_{_digest(change_key, kind, identity)[:32]}"
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _content_hash(entry_type: str, summary: str, body: str, keywords: list[str]) -> str:
|
|
63
|
+
canonical = json.dumps(
|
|
64
|
+
{"entry_type": entry_type, "summary": summary, "body": body, "keywords": keywords},
|
|
65
|
+
ensure_ascii=False,
|
|
66
|
+
sort_keys=True,
|
|
67
|
+
separators=(",", ":"),
|
|
68
|
+
)
|
|
69
|
+
return "sha256:" + hashlib.sha256(canonical.encode("utf-8")).hexdigest()
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _keywords(*values: str) -> list[str]:
|
|
73
|
+
"""Deduplicate, preserve order, and honour the contract's bounds."""
|
|
74
|
+
seen: list[str] = []
|
|
75
|
+
for value in values:
|
|
76
|
+
keyword = _text(value)[:_MAX_KEYWORD_CHARS]
|
|
77
|
+
if keyword and keyword not in seen:
|
|
78
|
+
seen.append(keyword)
|
|
79
|
+
return seen[:_MAX_KEYWORDS]
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _path_segments(path: str) -> list[str]:
|
|
83
|
+
return [segment for segment in path.replace("\\", "/").split("/") if segment]
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _location(path: str, line: Any) -> str:
|
|
87
|
+
"""``path:line`` when the line number is real, otherwise just the path."""
|
|
88
|
+
if not path:
|
|
89
|
+
return ""
|
|
90
|
+
if isinstance(line, bool) or not isinstance(line, int) or line < 1:
|
|
91
|
+
return path
|
|
92
|
+
return f"{path}:{line}"
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _finding_candidate(
|
|
96
|
+
finding: dict[str, Any],
|
|
97
|
+
*,
|
|
98
|
+
change_key: str,
|
|
99
|
+
archive_id: str,
|
|
100
|
+
producer_version: str,
|
|
101
|
+
created_at: str,
|
|
102
|
+
) -> dict[str, Any] | None:
|
|
103
|
+
severity = _text(finding.get("severity"))
|
|
104
|
+
disposition = _text(finding.get("disposition"))
|
|
105
|
+
entry_type = _DISPOSITION_ENTRY_TYPES.get(disposition)
|
|
106
|
+
title = _text(finding.get("title"))
|
|
107
|
+
if severity not in _SEVERITIES or entry_type is None or not title:
|
|
108
|
+
return None
|
|
109
|
+
|
|
110
|
+
path = _text(finding.get("path"))
|
|
111
|
+
line = finding.get("line")
|
|
112
|
+
location = _location(path, line)
|
|
113
|
+
segments = _path_segments(path)
|
|
114
|
+
finding_id = _text(finding.get("id"))
|
|
115
|
+
|
|
116
|
+
body_lines = [title]
|
|
117
|
+
if location:
|
|
118
|
+
body_lines.append(f"位置:{location}")
|
|
119
|
+
body_lines.append(f"严重度:{severity}")
|
|
120
|
+
body_lines.append(f"裁决:{disposition}")
|
|
121
|
+
body = "\n".join(body_lines)[:_MAX_BODY_CHARS]
|
|
122
|
+
|
|
123
|
+
keywords = _keywords(
|
|
124
|
+
segments[-1] if segments else "",
|
|
125
|
+
segments[-2] if len(segments) >= 2 else "",
|
|
126
|
+
severity,
|
|
127
|
+
disposition,
|
|
128
|
+
)
|
|
129
|
+
source_ref = f"archive:{archive_id}#{finding_id}" if finding_id else f"archive:{archive_id}"
|
|
130
|
+
if path and location != path:
|
|
131
|
+
source_refs = [f"{path}#L{line}"]
|
|
132
|
+
elif path:
|
|
133
|
+
source_refs = [path]
|
|
134
|
+
else:
|
|
135
|
+
source_refs = [f"archive:{archive_id}"]
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
"schema_version": SCHEMA_VERSION,
|
|
139
|
+
"candidate_id": _candidate_id(
|
|
140
|
+
change_key, "review", finding_id or f"{title}\0{path}\0{line}"
|
|
141
|
+
),
|
|
142
|
+
"source_change_key": change_key,
|
|
143
|
+
"source_refs": source_refs,
|
|
144
|
+
"summary": title,
|
|
145
|
+
"reusability_scope": segments[0] if segments else "project",
|
|
146
|
+
"content_hash": _content_hash(entry_type, title, body, keywords),
|
|
147
|
+
"confidence": _SEVERITY_CONFIDENCE[severity],
|
|
148
|
+
"status": "pending",
|
|
149
|
+
"entry_type": entry_type,
|
|
150
|
+
"body": body,
|
|
151
|
+
"keywords": keywords,
|
|
152
|
+
"provenance": {
|
|
153
|
+
"source_kind": "review",
|
|
154
|
+
"source_ref": source_ref,
|
|
155
|
+
"producer": PRODUCER,
|
|
156
|
+
"producer_version": producer_version,
|
|
157
|
+
"created_at": created_at,
|
|
158
|
+
},
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _risk_candidate(
|
|
163
|
+
risk: dict[str, Any],
|
|
164
|
+
*,
|
|
165
|
+
change_key: str,
|
|
166
|
+
archive_id: str,
|
|
167
|
+
producer_version: str,
|
|
168
|
+
created_at: str,
|
|
169
|
+
) -> dict[str, Any] | None:
|
|
170
|
+
message = _text(risk.get("message"))
|
|
171
|
+
if not message:
|
|
172
|
+
return None
|
|
173
|
+
phase = _text(risk.get("phase"))
|
|
174
|
+
severity = _text(risk.get("severity"))
|
|
175
|
+
|
|
176
|
+
body_lines = [message]
|
|
177
|
+
if phase:
|
|
178
|
+
body_lines.append(f"阶段:{phase}")
|
|
179
|
+
if severity:
|
|
180
|
+
body_lines.append(f"严重度:{severity}")
|
|
181
|
+
body = "\n".join(body_lines)[:_MAX_BODY_CHARS]
|
|
182
|
+
keywords = _keywords(phase, severity)
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
"schema_version": SCHEMA_VERSION,
|
|
186
|
+
"candidate_id": _candidate_id(change_key, "known_risk", f"{phase}\0{message}"),
|
|
187
|
+
"source_change_key": change_key,
|
|
188
|
+
"source_refs": [f"archive:{archive_id}"],
|
|
189
|
+
"summary": message,
|
|
190
|
+
"reusability_scope": phase or "project",
|
|
191
|
+
"content_hash": _content_hash("risk", message, body, keywords),
|
|
192
|
+
"confidence": _KNOWN_RISK_CONFIDENCE,
|
|
193
|
+
"status": "pending",
|
|
194
|
+
"entry_type": "risk",
|
|
195
|
+
"body": body,
|
|
196
|
+
"keywords": keywords,
|
|
197
|
+
"provenance": {
|
|
198
|
+
"source_kind": "archive",
|
|
199
|
+
"source_ref": f"archive:{archive_id}",
|
|
200
|
+
"producer": PRODUCER,
|
|
201
|
+
"producer_version": producer_version,
|
|
202
|
+
"created_at": created_at,
|
|
203
|
+
},
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def build_knowledge_candidates(
|
|
208
|
+
summary: dict[str, Any],
|
|
209
|
+
*,
|
|
210
|
+
change_key: str,
|
|
211
|
+
archive_id: str,
|
|
212
|
+
producer_version: str,
|
|
213
|
+
created_at: str,
|
|
214
|
+
) -> list[dict[str, Any]]:
|
|
215
|
+
"""Project reviewFindings + knownRisks into KnowledgeCandidate records.
|
|
216
|
+
|
|
217
|
+
Returns [] for missing or malformed input: an archive with nothing worth
|
|
218
|
+
persisting must still produce a valid (empty) candidates file.
|
|
219
|
+
"""
|
|
220
|
+
if not isinstance(summary, dict):
|
|
221
|
+
return []
|
|
222
|
+
candidates: list[dict[str, Any]] = []
|
|
223
|
+
seen_ids: set[str] = set()
|
|
224
|
+
|
|
225
|
+
def collect(candidate: dict[str, Any] | None) -> None:
|
|
226
|
+
if candidate is None or candidate["candidate_id"] in seen_ids:
|
|
227
|
+
return
|
|
228
|
+
seen_ids.add(candidate["candidate_id"])
|
|
229
|
+
candidates.append(candidate)
|
|
230
|
+
|
|
231
|
+
findings = summary.get("reviewFindings")
|
|
232
|
+
if isinstance(findings, list):
|
|
233
|
+
for finding in findings:
|
|
234
|
+
if isinstance(finding, dict):
|
|
235
|
+
collect(_finding_candidate(
|
|
236
|
+
finding,
|
|
237
|
+
change_key=change_key,
|
|
238
|
+
archive_id=archive_id,
|
|
239
|
+
producer_version=producer_version,
|
|
240
|
+
created_at=created_at,
|
|
241
|
+
))
|
|
242
|
+
|
|
243
|
+
risks = summary.get("knownRisks")
|
|
244
|
+
if isinstance(risks, list):
|
|
245
|
+
for risk in risks:
|
|
246
|
+
if isinstance(risk, dict):
|
|
247
|
+
collect(_risk_candidate(
|
|
248
|
+
risk,
|
|
249
|
+
change_key=change_key,
|
|
250
|
+
archive_id=archive_id,
|
|
251
|
+
producer_version=producer_version,
|
|
252
|
+
created_at=created_at,
|
|
253
|
+
))
|
|
254
|
+
|
|
255
|
+
return candidates
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def render_knowledge_candidates_json(candidates: list[dict[str, Any]]) -> str:
|
|
259
|
+
"""Deterministic bytes for the archive package entry."""
|
|
260
|
+
return json.dumps(
|
|
261
|
+
candidates, ensure_ascii=False, sort_keys=True, separators=(",", ":")
|
|
262
|
+
) + "\n"
|
|
@@ -444,6 +444,8 @@ def infer_execution_project_root(change_dir: Path) -> Path | None:
|
|
|
444
444
|
process = subprocess.run(
|
|
445
445
|
["git", "-C", str(root), "rev-parse", "--path-format=absolute", "--git-common-dir"],
|
|
446
446
|
text=True,
|
|
447
|
+
encoding="utf-8",
|
|
448
|
+
errors="replace",
|
|
447
449
|
stdout=subprocess.PIPE,
|
|
448
450
|
stderr=subprocess.PIPE,
|
|
449
451
|
check=False,
|
|
@@ -461,6 +463,8 @@ def infer_execution_project_root(change_dir: Path) -> Path | None:
|
|
|
461
463
|
process = subprocess.run(
|
|
462
464
|
["git", "-C", str(candidate), "rev-parse", "--show-toplevel"],
|
|
463
465
|
text=True,
|
|
466
|
+
encoding="utf-8",
|
|
467
|
+
errors="replace",
|
|
464
468
|
stdout=subprocess.PIPE,
|
|
465
469
|
stderr=subprocess.PIPE,
|
|
466
470
|
check=False,
|
|
@@ -217,10 +217,14 @@ def _secure_private_path(path: Path, *, directory: bool) -> str:
|
|
|
217
217
|
|
|
218
218
|
def _sensitive_candidates(root: Path) -> list[dict[str, Any]]:
|
|
219
219
|
"""Find high-confidence plaintext assignments without treating prose as a secret."""
|
|
220
|
-
|
|
220
|
+
# Compare by relative path, not by resolve() per file: this loop covers the
|
|
221
|
+
# whole change tree, and on Windows each resolve() is a realpath syscall.
|
|
222
|
+
receipt_rel = SECRET_SCAN_RECEIPT_REL.as_posix()
|
|
221
223
|
candidates: list[dict[str, Any]] = []
|
|
222
224
|
for path in sorted(root.rglob("*")):
|
|
223
|
-
if not path.is_file()
|
|
225
|
+
if not path.is_file():
|
|
226
|
+
continue
|
|
227
|
+
if path.relative_to(root).as_posix() == receipt_rel:
|
|
224
228
|
continue
|
|
225
229
|
try:
|
|
226
230
|
raw = path.read_bytes()
|
|
@@ -1578,7 +1582,7 @@ def _run_session_worker_with_log(state_root: Path, session_id: str) -> int:
|
|
|
1578
1582
|
return _run_session_worker(state_root, session_id)
|
|
1579
1583
|
|
|
1580
1584
|
|
|
1581
|
-
def
|
|
1585
|
+
def _run_session_status_raw(state_root: Path, session_id: str) -> dict[str, Any]:
|
|
1582
1586
|
from harness_service import is_pid_alive
|
|
1583
1587
|
|
|
1584
1588
|
receipt = _load_run_receipt(state_root, session_id)
|
|
@@ -1651,6 +1655,65 @@ def run_session_status(state_root: Path, session_id: str) -> dict[str, Any]:
|
|
|
1651
1655
|
return receipt
|
|
1652
1656
|
|
|
1653
1657
|
|
|
1658
|
+
# 终态判定此前只活在脚本内部的 RUN_TERMINAL_STATUSES 里,从不出现在返回体中。
|
|
1659
|
+
# 调用方于是只能按状态名猜——而 "INCOMPLETE" 读起来最像"还没结束",实际是终态。
|
|
1660
|
+
# 2026-08-18 的一次 fixback 执行里,一个启动即失败(LAUNCHER_FAILED)的会话
|
|
1661
|
+
# 被连等 20s、60s,纯属这个缺口造成的浪费。
|
|
1662
|
+
_TERMINAL_HINTS = {
|
|
1663
|
+
"LAUNCHER_FAILED": (
|
|
1664
|
+
"被测进程未能启动(testProcessStarted=false),不是超时;"
|
|
1665
|
+
"先核对 argv 的可执行文件在该工作目录下能否直接运行,再重跑 run-start。"
|
|
1666
|
+
),
|
|
1667
|
+
"HEARTBEAT_LOST": "worker 心跳丢失,会话已判定结束;日志可能不完整。",
|
|
1668
|
+
"WORKER_IDENTITY_MISMATCH": "worker 身份校验不通过,会话已终止且未采信其结果。",
|
|
1669
|
+
"WORKER_EXITED_WITHOUT_FINAL_RECEIPT": "worker 未写最终回执即退出。",
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1672
|
+
|
|
1673
|
+
def _annotate_run_status(receipt: dict[str, Any]) -> dict[str, Any]:
|
|
1674
|
+
"""给回执补上可判定的终态标记与可行动线索。
|
|
1675
|
+
|
|
1676
|
+
FINALIZING 刻意判为非终态:结果虽已确定,但 worker 尚未退出,
|
|
1677
|
+
调用方此时取读数会与清理竞争。
|
|
1678
|
+
"""
|
|
1679
|
+
status = receipt.get("status")
|
|
1680
|
+
terminal = status in RUN_TERMINAL_STATUSES and status != "FINALIZING"
|
|
1681
|
+
annotated = dict(receipt)
|
|
1682
|
+
annotated["terminal"] = bool(terminal)
|
|
1683
|
+
hint = _TERMINAL_HINTS.get(str(receipt.get("reasonCode") or ""))
|
|
1684
|
+
if terminal and hint is not None:
|
|
1685
|
+
annotated["terminalHint"] = hint
|
|
1686
|
+
return annotated
|
|
1687
|
+
|
|
1688
|
+
|
|
1689
|
+
def run_session_status(state_root: Path, session_id: str) -> dict[str, Any]:
|
|
1690
|
+
return _annotate_run_status(_run_session_status_raw(state_root, session_id))
|
|
1691
|
+
|
|
1692
|
+
|
|
1693
|
+
def await_run_session(
|
|
1694
|
+
state_root: Path,
|
|
1695
|
+
session_id: str,
|
|
1696
|
+
*,
|
|
1697
|
+
timeout_seconds: float = 600.0,
|
|
1698
|
+
poll_seconds: float = 2.0,
|
|
1699
|
+
) -> dict[str, Any]:
|
|
1700
|
+
"""阻塞到会话进入终态,或超时后带 waitTimedOut 标记返回。
|
|
1701
|
+
|
|
1702
|
+
没有这个入口时,调用方只能 `sleep <猜一个时长>` 再查一次;上述执行日志里
|
|
1703
|
+
连猜了 5s / 20s / 60s / 100s 四轮。等待逻辑属于会话语义,应由这里承担。
|
|
1704
|
+
"""
|
|
1705
|
+
deadline = time.monotonic() + max(float(timeout_seconds), 0.0)
|
|
1706
|
+
while True:
|
|
1707
|
+
current = run_session_status(state_root, session_id)
|
|
1708
|
+
if current.get("terminal") is True:
|
|
1709
|
+
current["waitTimedOut"] = False
|
|
1710
|
+
return current
|
|
1711
|
+
if time.monotonic() >= deadline:
|
|
1712
|
+
current["waitTimedOut"] = True
|
|
1713
|
+
return current
|
|
1714
|
+
time.sleep(max(float(poll_seconds), 0.01))
|
|
1715
|
+
|
|
1716
|
+
|
|
1654
1717
|
def read_run_session_log(
|
|
1655
1718
|
state_root: Path,
|
|
1656
1719
|
session_id: str,
|
|
@@ -1974,6 +2037,13 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
1974
2037
|
p_status = sub.add_parser("run-status", help="read a managed run receipt")
|
|
1975
2038
|
p_status.add_argument("--state-root", required=True)
|
|
1976
2039
|
p_status.add_argument("--session-id", required=True)
|
|
2040
|
+
p_status.add_argument(
|
|
2041
|
+
"--wait",
|
|
2042
|
+
action="store_true",
|
|
2043
|
+
help="阻塞到会话进入终态再返回,替代调用方自己 sleep 猜时长",
|
|
2044
|
+
)
|
|
2045
|
+
p_status.add_argument("--wait-timeout-seconds", type=float, default=600.0)
|
|
2046
|
+
p_status.add_argument("--poll-seconds", type=float, default=2.0)
|
|
1977
2047
|
p_status.add_argument("--json", action="store_true")
|
|
1978
2048
|
p_log = sub.add_parser("run-log", help="read an incremental log page")
|
|
1979
2049
|
p_log.add_argument("--state-root", required=True)
|
|
@@ -2022,7 +2092,15 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
2022
2092
|
resource_locks=list(args.resource_lock),
|
|
2023
2093
|
)
|
|
2024
2094
|
elif args.command == "run-status":
|
|
2025
|
-
|
|
2095
|
+
if getattr(args, "wait", False):
|
|
2096
|
+
result = await_run_session(
|
|
2097
|
+
Path(args.state_root),
|
|
2098
|
+
args.session_id,
|
|
2099
|
+
timeout_seconds=args.wait_timeout_seconds,
|
|
2100
|
+
poll_seconds=args.poll_seconds,
|
|
2101
|
+
)
|
|
2102
|
+
else:
|
|
2103
|
+
result = run_session_status(Path(args.state_root), args.session_id)
|
|
2026
2104
|
elif args.command == "run-log":
|
|
2027
2105
|
result = read_run_session_log(
|
|
2028
2106
|
Path(args.state_root),
|
|
@@ -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=c8c212940eb5848d; overlay=none; agent=codex; do not edit -->
|
|
7
7
|
# harness-archive — 变更归档
|
|
8
8
|
|
|
9
9
|
## Purpose
|
|
@@ -130,9 +130,24 @@ description: 归档所有变更产出(计划/测试报告/审查/SQL/API文档
|
|
|
130
130
|
|
|
131
131
|
| 阻断 | 出路 |
|
|
132
132
|
|------|------|
|
|
133
|
-
| `SENSITIVE_EVIDENCE_UNQUARANTINED` | `python <skills-root>/scripts/harness_runtime.py quarantine-evidence --project . --change-dir ".harness/changes/<cn>" --file "<相对 change-dir 的路径>" --reason "<为什么是敏感证据>" --json`(`--file` 可重复)。私有根默认已与项目同盘、且在项目根之外——**不要**手工指定项目内的路径,归档的密钥扫描会以 `SECRET_SCAN_PRIVATE_PATH_IN_COPY_ROOT` 拒绝 |
|
|
133
|
+
| `SENSITIVE_EVIDENCE_UNQUARANTINED`(默认只告警,`HUNTER_HARNESS_SENSITIVE_SCAN=block` 时才阻断) | `python <skills-root>/scripts/harness_runtime.py quarantine-evidence --project . --change-dir ".harness/changes/<cn>" --file "<相对 change-dir 的路径>" --reason "<为什么是敏感证据>" --json`(`--file` 可重复)。私有根默认已与项目同盘、且在项目根之外——**不要**手工指定项目内的路径,归档的密钥扫描会以 `SECRET_SCAN_PRIVATE_PATH_IN_COPY_ROOT` 拒绝 |
|
|
134
134
|
| `DIFF_ZERO_WITH_NONEMPTY_COMMIT`(提交范围非空但 filesChanged=0) | 契约缺 `ownership.productPaths`,全部改动被判为 `foreignPaths`。用 `python <skills-root>/scripts/harness_change.py declare-ownership --change <cn> --product-path "<目录前缀或精确文件>" --json` 按计划的实际改动范围声明(可重复;只收精确路径,不支持通配)。**不要**手改 `change-context.json` |
|
|
135
135
|
|
|
136
|
+
### 二·B、归档补传(上传失败或历史归档缺条目)
|
|
137
|
+
|
|
138
|
+
归档上传成功后 ZIP 与回执会被清理,旧版本产生的归档从未入 outbox——所以
|
|
139
|
+
`harness-push --scope archive` 找不到可复用的包属于正常状态,不是故障。补传从已封存的
|
|
140
|
+
归档目录重建确定性包:
|
|
141
|
+
|
|
142
|
+
```powershell
|
|
143
|
+
python <skills-root>/scripts/harness_archive.py republish --change <change-key> --dry-run --json # 只看包内容
|
|
144
|
+
python <skills-root>/scripts/harness_archive.py republish --change <change-key> --json # 重建并上传
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
归档目录是封存的(after-manifest 覆盖其每个字节),因此 `republish` **不写回**归档目录:
|
|
148
|
+
缺失的 `candidates/knowledge.json` 只在内存中按已归档 summary 生成并放进包里。平台的知识
|
|
149
|
+
条目只来自这个包;`--scope all` 走的是 branch_file 通道,不进知识管道。
|
|
150
|
+
|
|
136
151
|
### 三、文件移动只用内置工具或 PowerShell
|
|
137
152
|
|
|
138
153
|
移动用 Read+Write+验证 或 PowerShell;**禁止 Bash mv/cp/rm**。移动失败时不删除原目录,报错退出让用户手动处理。
|
|
@@ -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=c8c212940eb5848d; overlay=none; agent=codex; do not edit -->
|
|
7
7
|
# harness-codebase-map — 代码库地图
|
|
8
8
|
|
|
9
9
|
## Purpose
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: harness-knowledge-ingest
|
|
3
3
|
description: 确认归档 ZIP 已上传并由 Hunter Platform 在服务端解包、校验和入库。客户端不再构建或维护本地知识索引。
|
|
4
4
|
---
|
|
5
|
-
<!-- generated by harness_deploy.py; core=
|
|
5
|
+
<!-- generated by harness_deploy.py; core=c8c212940eb5848d; overlay=none; agent=codex; do not edit -->
|
|
6
6
|
# harness-knowledge-ingest
|
|
7
7
|
|
|
8
8
|
知识 ingest 完全由 Hunter Platform 负责。归档完成时,客户端生成一个确定性 ZIP;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: harness-knowledge-query
|
|
3
3
|
description: 在规划、实现或排查前,通过 hunter-harness CLI 查询远端项目知识库。远端不可用时直接报告不可用,不建立本地索引或离线回退。
|
|
4
4
|
---
|
|
5
|
-
<!-- generated by harness_deploy.py; core=
|
|
5
|
+
<!-- generated by harness_deploy.py; core=c8c212940eb5848d; overlay=none; agent=codex; do not edit -->
|
|
6
6
|
# harness-knowledge-query
|
|
7
7
|
|
|
8
8
|
项目知识以 Hunter Platform 的服务端索引为唯一真源。客户端只提交查询并消费结果:
|
|
@@ -3,7 +3,7 @@ name: harness-plan
|
|
|
3
3
|
description: 将需求转化为设计文档+实施计划+完整测试场景表,必须在编码前完成。仅当用户显式调用 /harness-plan 或明确要求进入
|
|
4
4
|
Harness 规划阶段时使用;不得因用户描述需求就自动触发。
|
|
5
5
|
---
|
|
6
|
-
<!-- generated by harness_deploy.py; core=
|
|
6
|
+
<!-- generated by harness_deploy.py; core=c8c212940eb5848d; overlay=none; agent=codex; do not edit -->
|
|
7
7
|
# harness-plan — 需求规划
|
|
8
8
|
|
|
9
9
|
## Purpose
|
|
@@ -3,7 +3,7 @@ name: harness-pull
|
|
|
3
3
|
description: 从 Hunter Platform 下拉配置/规则/架构/指令(及显式来源分支的分支文件恢复)。仅当用户显式调用
|
|
4
4
|
/harness-pull 或明确说'从平台拉取/恢复'时使用;不得自动触发。
|
|
5
5
|
---
|
|
6
|
-
<!-- generated by harness_deploy.py; core=
|
|
6
|
+
<!-- generated by harness_deploy.py; core=c8c212940eb5848d; overlay=none; agent=codex; do not edit -->
|
|
7
7
|
# harness-pull — 从 Hunter Platform 下拉与恢复
|
|
8
8
|
|
|
9
9
|
## Purpose
|