@hunter-harness/workflow-harness 0.2.76 → 0.2.78
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 +5 -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 +1 -1
- 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 +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/scripts/harness_archive.py +55 -0
- 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 +76 -2
- package/harness/bundles/general/codebuddy/.harness-build.json +1 -1
- package/harness/bundles/general/codebuddy/harness-archive/SKILL.md +5 -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 +1 -1
- 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 +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/scripts/harness_archive.py +55 -0
- 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 +76 -2
- package/harness/bundles/general/codex/.harness-build.json +1 -1
- package/harness/bundles/general/codex/harness-archive/SKILL.md +5 -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 +1 -1
- 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 +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/scripts/harness_archive.py +55 -0
- 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 +76 -2
- package/harness/bundles/general/cursor/.harness-build.json +1 -1
- package/harness/bundles/general/cursor/harness-archive/SKILL.md +5 -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 +1 -1
- 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 +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/scripts/harness_archive.py +55 -0
- 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 +76 -2
- 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 +5 -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 +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 +1 -1
- 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/scripts/harness_archive.py +55 -0
- 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 +76 -2
- 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 +5 -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 +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 +1 -1
- 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/scripts/harness_archive.py +55 -0
- 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 +76 -2
- 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 +5 -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 +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 +1 -1
- 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/scripts/harness_archive.py +55 -0
- 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 +76 -2
- 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 +5 -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 +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 +1 -1
- 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/scripts/harness_archive.py +55 -0
- 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 +76 -2
- package/harness/manifests/general/claude-code.json +26 -22
- package/harness/manifests/general/codebuddy.json +26 -22
- package/harness/manifests/general/codex.json +26 -22
- package/harness/manifests/general/cursor.json +26 -22
- package/harness/manifests/java/claude-code.json +28 -24
- package/harness/manifests/java/codebuddy.json +28 -24
- package/harness/manifests/java/codex.json +28 -24
- package/harness/manifests/java/cursor.json +28 -24
- package/hunter-workflow-family.json +5 -5
- package/package.json +1 -1
|
@@ -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,
|
|
@@ -1578,7 +1578,7 @@ def _run_session_worker_with_log(state_root: Path, session_id: str) -> int:
|
|
|
1578
1578
|
return _run_session_worker(state_root, session_id)
|
|
1579
1579
|
|
|
1580
1580
|
|
|
1581
|
-
def
|
|
1581
|
+
def _run_session_status_raw(state_root: Path, session_id: str) -> dict[str, Any]:
|
|
1582
1582
|
from harness_service import is_pid_alive
|
|
1583
1583
|
|
|
1584
1584
|
receipt = _load_run_receipt(state_root, session_id)
|
|
@@ -1651,6 +1651,65 @@ def run_session_status(state_root: Path, session_id: str) -> dict[str, Any]:
|
|
|
1651
1651
|
return receipt
|
|
1652
1652
|
|
|
1653
1653
|
|
|
1654
|
+
# 终态判定此前只活在脚本内部的 RUN_TERMINAL_STATUSES 里,从不出现在返回体中。
|
|
1655
|
+
# 调用方于是只能按状态名猜——而 "INCOMPLETE" 读起来最像"还没结束",实际是终态。
|
|
1656
|
+
# 2026-08-18 的一次 fixback 执行里,一个启动即失败(LAUNCHER_FAILED)的会话
|
|
1657
|
+
# 被连等 20s、60s,纯属这个缺口造成的浪费。
|
|
1658
|
+
_TERMINAL_HINTS = {
|
|
1659
|
+
"LAUNCHER_FAILED": (
|
|
1660
|
+
"被测进程未能启动(testProcessStarted=false),不是超时;"
|
|
1661
|
+
"先核对 argv 的可执行文件在该工作目录下能否直接运行,再重跑 run-start。"
|
|
1662
|
+
),
|
|
1663
|
+
"HEARTBEAT_LOST": "worker 心跳丢失,会话已判定结束;日志可能不完整。",
|
|
1664
|
+
"WORKER_IDENTITY_MISMATCH": "worker 身份校验不通过,会话已终止且未采信其结果。",
|
|
1665
|
+
"WORKER_EXITED_WITHOUT_FINAL_RECEIPT": "worker 未写最终回执即退出。",
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
|
|
1669
|
+
def _annotate_run_status(receipt: dict[str, Any]) -> dict[str, Any]:
|
|
1670
|
+
"""给回执补上可判定的终态标记与可行动线索。
|
|
1671
|
+
|
|
1672
|
+
FINALIZING 刻意判为非终态:结果虽已确定,但 worker 尚未退出,
|
|
1673
|
+
调用方此时取读数会与清理竞争。
|
|
1674
|
+
"""
|
|
1675
|
+
status = receipt.get("status")
|
|
1676
|
+
terminal = status in RUN_TERMINAL_STATUSES and status != "FINALIZING"
|
|
1677
|
+
annotated = dict(receipt)
|
|
1678
|
+
annotated["terminal"] = bool(terminal)
|
|
1679
|
+
hint = _TERMINAL_HINTS.get(str(receipt.get("reasonCode") or ""))
|
|
1680
|
+
if terminal and hint is not None:
|
|
1681
|
+
annotated["terminalHint"] = hint
|
|
1682
|
+
return annotated
|
|
1683
|
+
|
|
1684
|
+
|
|
1685
|
+
def run_session_status(state_root: Path, session_id: str) -> dict[str, Any]:
|
|
1686
|
+
return _annotate_run_status(_run_session_status_raw(state_root, session_id))
|
|
1687
|
+
|
|
1688
|
+
|
|
1689
|
+
def await_run_session(
|
|
1690
|
+
state_root: Path,
|
|
1691
|
+
session_id: str,
|
|
1692
|
+
*,
|
|
1693
|
+
timeout_seconds: float = 600.0,
|
|
1694
|
+
poll_seconds: float = 2.0,
|
|
1695
|
+
) -> dict[str, Any]:
|
|
1696
|
+
"""阻塞到会话进入终态,或超时后带 waitTimedOut 标记返回。
|
|
1697
|
+
|
|
1698
|
+
没有这个入口时,调用方只能 `sleep <猜一个时长>` 再查一次;上述执行日志里
|
|
1699
|
+
连猜了 5s / 20s / 60s / 100s 四轮。等待逻辑属于会话语义,应由这里承担。
|
|
1700
|
+
"""
|
|
1701
|
+
deadline = time.monotonic() + max(float(timeout_seconds), 0.0)
|
|
1702
|
+
while True:
|
|
1703
|
+
current = run_session_status(state_root, session_id)
|
|
1704
|
+
if current.get("terminal") is True:
|
|
1705
|
+
current["waitTimedOut"] = False
|
|
1706
|
+
return current
|
|
1707
|
+
if time.monotonic() >= deadline:
|
|
1708
|
+
current["waitTimedOut"] = True
|
|
1709
|
+
return current
|
|
1710
|
+
time.sleep(max(float(poll_seconds), 0.01))
|
|
1711
|
+
|
|
1712
|
+
|
|
1654
1713
|
def read_run_session_log(
|
|
1655
1714
|
state_root: Path,
|
|
1656
1715
|
session_id: str,
|
|
@@ -1974,6 +2033,13 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
1974
2033
|
p_status = sub.add_parser("run-status", help="read a managed run receipt")
|
|
1975
2034
|
p_status.add_argument("--state-root", required=True)
|
|
1976
2035
|
p_status.add_argument("--session-id", required=True)
|
|
2036
|
+
p_status.add_argument(
|
|
2037
|
+
"--wait",
|
|
2038
|
+
action="store_true",
|
|
2039
|
+
help="阻塞到会话进入终态再返回,替代调用方自己 sleep 猜时长",
|
|
2040
|
+
)
|
|
2041
|
+
p_status.add_argument("--wait-timeout-seconds", type=float, default=600.0)
|
|
2042
|
+
p_status.add_argument("--poll-seconds", type=float, default=2.0)
|
|
1977
2043
|
p_status.add_argument("--json", action="store_true")
|
|
1978
2044
|
p_log = sub.add_parser("run-log", help="read an incremental log page")
|
|
1979
2045
|
p_log.add_argument("--state-root", required=True)
|
|
@@ -2022,7 +2088,15 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
2022
2088
|
resource_locks=list(args.resource_lock),
|
|
2023
2089
|
)
|
|
2024
2090
|
elif args.command == "run-status":
|
|
2025
|
-
|
|
2091
|
+
if getattr(args, "wait", False):
|
|
2092
|
+
result = await_run_session(
|
|
2093
|
+
Path(args.state_root),
|
|
2094
|
+
args.session_id,
|
|
2095
|
+
timeout_seconds=args.wait_timeout_seconds,
|
|
2096
|
+
poll_seconds=args.poll_seconds,
|
|
2097
|
+
)
|
|
2098
|
+
else:
|
|
2099
|
+
result = run_session_status(Path(args.state_root), args.session_id)
|
|
2026
2100
|
elif args.command == "run-log":
|
|
2027
2101
|
result = read_run_session_log(
|
|
2028
2102
|
Path(args.state_root),
|
|
@@ -21,7 +21,7 @@ disallowed-tools:
|
|
|
21
21
|
- Bash(awk *)
|
|
22
22
|
- Bash(curl *)
|
|
23
23
|
---
|
|
24
|
-
<!-- generated by harness_deploy.py; core=
|
|
24
|
+
<!-- generated by harness_deploy.py; core=4e58f505d91f5969; overlay=java; agent=claude-code; do not edit -->
|
|
25
25
|
# harness-apidoc — 前端接口文档(Java overlay)
|
|
26
26
|
|
|
27
27
|
## Purpose
|
|
@@ -21,7 +21,7 @@ disallowed-tools:
|
|
|
21
21
|
- Bash(awk *)
|
|
22
22
|
- Bash(curl *)
|
|
23
23
|
---
|
|
24
|
-
<!-- generated by harness_deploy.py; core=
|
|
24
|
+
<!-- generated by harness_deploy.py; core=4e58f505d91f5969; overlay=java; agent=claude-code; do not edit -->
|
|
25
25
|
# harness-archive — 变更归档
|
|
26
26
|
|
|
27
27
|
## Purpose
|
|
@@ -221,11 +221,13 @@ git 命令通过 `powershell.exe -Command "..."` 执行;archive-meta.md 和 su
|
|
|
221
221
|
| 上传 | 载什么 | 结果在哪 |
|
|
222
222
|
|------|--------|---------|
|
|
223
223
|
| `steps.archive_push` | 归档 ZIP(核心产物) | `archiveId`/`archiveStatus`/`uploadStatus`/`knowledgeStatus`,同时落盘 `state/local/archive-packages/<cn>.remote.json` |
|
|
224
|
-
| `steps.managed_snapshot_push` | **归档交付物(每个变更目录的 `plans/`、`spec/`、`reports
|
|
224
|
+
| `steps.managed_snapshot_push` | **归档交付物(每个变更目录的 `plans/`、`spec/`、`docs/` 全部,`reports/` 只取 `final/` 定稿)、`.harness/codebase` 架构地图、`.harness/rules`、`.harness/project.yaml`、指令入口** | `ok`/`reasonCode`/`exitCode`/`cliCode`/`detail` |
|
|
225
225
|
|
|
226
226
|
走 `harness-push`(remote-sync,产生分支快照)而非 legacy `push`(proposal 管道)。
|
|
227
227
|
归档目录下的 `runtime/`、`meta/`、`evidence/`、`logs/`、`fixback/`、`.publication-staging/`
|
|
228
|
-
|
|
228
|
+
等过程文件不上传;`reports/review/` 与 `reports/test/` 同样是过程产物,不上传——
|
|
229
|
+
这条边界与归档 ZIP 的包内容边界一致。交付物目录里的 `*.log`、`*.tmp`、凭证与 `.env*`
|
|
230
|
+
也仍被安全规则拦下。
|
|
229
231
|
|
|
230
232
|
```
|
|
231
233
|
远端归档:arc_3ff325bb…(archiveStatus=durable,uploadStatus=ready,knowledgeStatus=ready,8 文件)
|
|
@@ -20,7 +20,7 @@ disallowed-tools:
|
|
|
20
20
|
- Bash(awk *)
|
|
21
21
|
- Bash(curl *)
|
|
22
22
|
---
|
|
23
|
-
<!-- generated by harness_deploy.py; core=
|
|
23
|
+
<!-- generated by harness_deploy.py; core=4e58f505d91f5969; overlay=java; agent=claude-code; do not edit -->
|
|
24
24
|
# harness-codebase-map — 代码库地图
|
|
25
25
|
|
|
26
26
|
## Purpose
|
|
@@ -8,7 +8,7 @@ disallowed-tools:
|
|
|
8
8
|
- Bash(git *)
|
|
9
9
|
- Bash(curl *)
|
|
10
10
|
---
|
|
11
|
-
<!-- generated by harness_deploy.py; core=
|
|
11
|
+
<!-- generated by harness_deploy.py; core=4e58f505d91f5969; overlay=java; agent=claude-code; do not edit -->
|
|
12
12
|
# harness-knowledge-ingest
|
|
13
13
|
|
|
14
14
|
知识 ingest 完全由 Hunter Platform 负责。归档完成时,客户端生成一个确定性 ZIP;
|
|
@@ -8,7 +8,7 @@ disallowed-tools:
|
|
|
8
8
|
- Bash(git *)
|
|
9
9
|
- Bash(curl *)
|
|
10
10
|
---
|
|
11
|
-
<!-- generated by harness_deploy.py; core=
|
|
11
|
+
<!-- generated by harness_deploy.py; core=4e58f505d91f5969; overlay=java; agent=claude-code; do not edit -->
|
|
12
12
|
# harness-knowledge-query
|
|
13
13
|
|
|
14
14
|
项目知识以 Hunter Platform 的服务端索引为唯一真源。客户端只提交查询并消费结果:
|
|
@@ -21,7 +21,7 @@ disallowed-tools:
|
|
|
21
21
|
- Bash(awk *)
|
|
22
22
|
- Bash(curl *)
|
|
23
23
|
---
|
|
24
|
-
<!-- generated by harness_deploy.py; core=
|
|
24
|
+
<!-- generated by harness_deploy.py; core=4e58f505d91f5969; overlay=java; agent=claude-code; do not edit -->
|
|
25
25
|
# harness-package — 增量模块打包(Java overlay)
|
|
26
26
|
|
|
27
27
|
## Purpose
|
|
@@ -21,7 +21,7 @@ disallowed-tools:
|
|
|
21
21
|
- Bash(awk *)
|
|
22
22
|
- Bash(curl *)
|
|
23
23
|
---
|
|
24
|
-
<!-- generated by harness_deploy.py; core=
|
|
24
|
+
<!-- generated by harness_deploy.py; core=4e58f505d91f5969; overlay=java; agent=claude-code; do not edit -->
|
|
25
25
|
# harness-plan — 需求规划
|
|
26
26
|
|
|
27
27
|
## Purpose
|
|
@@ -21,7 +21,7 @@ disallowed-tools:
|
|
|
21
21
|
- Bash(awk *)
|
|
22
22
|
- Bash(curl *)
|
|
23
23
|
---
|
|
24
|
-
<!-- generated by harness_deploy.py; core=
|
|
24
|
+
<!-- generated by harness_deploy.py; core=4e58f505d91f5969; overlay=java; agent=claude-code; do not edit -->
|
|
25
25
|
# harness-pull — 从 Hunter Platform 下拉与恢复
|
|
26
26
|
|
|
27
27
|
## Purpose
|
|
@@ -21,7 +21,7 @@ disallowed-tools:
|
|
|
21
21
|
- Bash(awk *)
|
|
22
22
|
- Bash(curl *)
|
|
23
23
|
---
|
|
24
|
-
<!-- generated by harness_deploy.py; core=
|
|
24
|
+
<!-- generated by harness_deploy.py; core=4e58f505d91f5969; overlay=java; agent=claude-code; do not edit -->
|
|
25
25
|
# harness-push — 上传到 Hunter Platform
|
|
26
26
|
|
|
27
27
|
## Purpose
|
|
@@ -22,7 +22,7 @@ disallowed-tools:
|
|
|
22
22
|
- Bash(curl *)
|
|
23
23
|
- Bash(codegraph *)
|
|
24
24
|
---
|
|
25
|
-
<!-- generated by harness_deploy.py; core=
|
|
25
|
+
<!-- generated by harness_deploy.py; core=4e58f505d91f5969; overlay=java; agent=claude-code; do not edit -->
|
|
26
26
|
# harness-review — 代码审查
|
|
27
27
|
|
|
28
28
|
## Purpose
|
|
@@ -22,7 +22,7 @@ disallowed-tools:
|
|
|
22
22
|
- Bash(curl *)
|
|
23
23
|
- Bash(codegraph *)
|
|
24
24
|
---
|
|
25
|
-
<!-- generated by harness_deploy.py; core=
|
|
25
|
+
<!-- generated by harness_deploy.py; core=4e58f505d91f5969; overlay=java; agent=claude-code; do not edit -->
|
|
26
26
|
# harness-run — 需求编码
|
|
27
27
|
|
|
28
28
|
## Purpose
|
|
@@ -21,7 +21,7 @@ disallowed-tools:
|
|
|
21
21
|
- Bash(awk *)
|
|
22
22
|
- Bash(curl *)
|
|
23
23
|
---
|
|
24
|
-
<!-- generated by harness_deploy.py; core=
|
|
24
|
+
<!-- generated by harness_deploy.py; core=4e58f505d91f5969; overlay=java; agent=claude-code; do not edit -->
|
|
25
25
|
# harness-submit — 最终提交(含 worktree 合并)
|
|
26
26
|
|
|
27
27
|
## Purpose
|
|
@@ -19,7 +19,7 @@ disallowed-tools:
|
|
|
19
19
|
- Bash(awk *)
|
|
20
20
|
- Bash(curl *)
|
|
21
21
|
---
|
|
22
|
-
<!-- generated by harness_deploy.py; core=
|
|
22
|
+
<!-- generated by harness_deploy.py; core=4e58f505d91f5969; overlay=java; agent=claude-code; do not edit -->
|
|
23
23
|
# harness-sync
|
|
24
24
|
|
|
25
25
|
## Purpose
|
|
@@ -23,7 +23,7 @@ disallowed-tools:
|
|
|
23
23
|
- Bash(node *)
|
|
24
24
|
- Bash(codegraph *)
|
|
25
25
|
---
|
|
26
|
-
<!-- generated by harness_deploy.py; core=
|
|
26
|
+
<!-- generated by harness_deploy.py; core=4e58f505d91f5969; overlay=java; agent=claude-code; do not edit -->
|
|
27
27
|
# harness-test — 测试执行
|
|
28
28
|
|
|
29
29
|
## Purpose
|
|
@@ -196,6 +196,26 @@ Windows 默认常为 Windows PowerShell **5.1**。下列特性在 5.1 不可用
|
|
|
196
196
|
- 非 ASCII 路径(含中文)仓库:git/构建/文件操作一律 PowerShell;Bash 被 hook 拒绝(如 `Denied: non-ASCII path`)后**立即**改用 PowerShell 重发等价命令,禁止原样重试 Bash。
|
|
197
197
|
- Maven `-D` 参数在 PowerShell 中加引号:`"-Dmaven.test.skip=true"`。
|
|
198
198
|
|
|
199
|
+
### 捕获子进程中文输出:`$env:PYTHONUTF8=1` 不够
|
|
200
|
+
|
|
201
|
+
`PYTHONUTF8=1` 只让 **Python 按 UTF-8 输出**,不改变 **PowerShell 按什么编码读**。
|
|
202
|
+
控制台代码页是 GBK(936) 时,PowerShell 会用 GBK 解码 UTF-8 字节,得到形如
|
|
203
|
+
`绫?javadoc 鏈�闅� 6 绾х簿绠€` 的乱码——内容已在捕获阶段损坏,落进报告、回执与
|
|
204
|
+
JSON 字段后无法还原。
|
|
205
|
+
|
|
206
|
+
调用任何会输出中文的子进程前,**必须同时设置输出编码**:
|
|
207
|
+
|
|
208
|
+
```powershell
|
|
209
|
+
[Console]::OutputEncoding = [Text.Encoding]::UTF8
|
|
210
|
+
$env:PYTHONUTF8 = 1
|
|
211
|
+
python harness/scripts/harness_fixback.py <子命令> --json
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
- 两行缺一不可:只设 `PYTHONUTF8` 会乱码,只设 `OutputEncoding` 则 Python 可能仍按本地编码输出。
|
|
215
|
+
- 写脚本时把这两行放在脚本开头,不要依赖调用方记得加。
|
|
216
|
+
- 少用 `2>&1` 合并流:PowerShell 5.1 会把 stderr 包装成 `NativeCommandError`,
|
|
217
|
+
与编码问题叠加后更难判断真实失败原因;需要 stderr 时分开捕获。
|
|
218
|
+
|
|
199
219
|
## 13. PS5/PS7 原生参数合同
|
|
200
220
|
|
|
201
221
|
复杂 JSON、Docker Go template、here-string 内容及多层引号不得作为 native process 的内联参数跨 `Bash → powershell -Command → native executable` 传递。统一写 UTF-8 JSON 参数文件:
|