@hunter-harness/workflow-harness 0.2.77 → 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 +1 -1
- 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 +1 -1
- 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 +1 -1
- 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 +1 -1
- 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 +1 -1
- 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 +1 -1
- 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 +1 -1
- 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 +1 -1
- 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),
|
|
@@ -3,7 +3,7 @@ name: harness-apidoc
|
|
|
3
3
|
description: 从 Java 后端代码生成结构化前端 API 文档(Controller/VO/ErrorCode)。仅当用户显式调用
|
|
4
4
|
/harness-apidoc 时使用,不得自动触发。
|
|
5
5
|
---
|
|
6
|
-
<!-- generated by harness_deploy.py; core=
|
|
6
|
+
<!-- generated by harness_deploy.py; core=4e58f505d91f5969; overlay=java; agent=codebuddy; do not edit -->
|
|
7
7
|
# harness-apidoc — 前端接口文档(Java overlay)
|
|
8
8
|
|
|
9
9
|
## Purpose
|
|
@@ -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=4e58f505d91f5969; overlay=java; agent=codebuddy; do not edit -->
|
|
7
7
|
# harness-archive — 变更归档
|
|
8
8
|
|
|
9
9
|
## Purpose
|
|
@@ -3,7 +3,7 @@ name: harness-codebase-map
|
|
|
3
3
|
description: 分析现有代码库并生成 .harness/codebase/map/
|
|
4
4
|
下的结构化代码库地图。用于陌生项目理解、重大改造前梳理、harness-sync 提示 map 过期后的人工刷新。
|
|
5
5
|
---
|
|
6
|
-
<!-- generated by harness_deploy.py; core=
|
|
6
|
+
<!-- generated by harness_deploy.py; core=4e58f505d91f5969; overlay=java; agent=codebuddy; do not edit -->
|
|
7
7
|
# harness-codebase-map — 代码库地图
|
|
8
8
|
|
|
9
9
|
## Purpose
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: harness-knowledge-ingest
|
|
3
3
|
description: 确认归档 ZIP 已上传并由 Hunter Platform 在服务端解包、校验和入库。客户端不再构建或维护本地知识索引。
|
|
4
4
|
---
|
|
5
|
-
<!-- generated by harness_deploy.py; core=
|
|
5
|
+
<!-- generated by harness_deploy.py; core=4e58f505d91f5969; overlay=java; agent=codebuddy; do not edit -->
|
|
6
6
|
# harness-knowledge-ingest
|
|
7
7
|
|
|
8
8
|
知识 ingest 完全由 Hunter Platform 负责。归档完成时,客户端生成一个确定性 ZIP;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: harness-knowledge-query
|
|
3
3
|
description: 在规划、实现或排查前,通过 hunter-harness CLI 查询远端项目知识库。远端不可用时直接报告不可用,不建立本地索引或离线回退。
|
|
4
4
|
---
|
|
5
|
-
<!-- generated by harness_deploy.py; core=
|
|
5
|
+
<!-- generated by harness_deploy.py; core=4e58f505d91f5969; overlay=java; agent=codebuddy; do not edit -->
|
|
6
6
|
# harness-knowledge-query
|
|
7
7
|
|
|
8
8
|
项目知识以 Hunter Platform 的服务端索引为唯一真源。客户端只提交查询并消费结果:
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: harness-package
|
|
3
3
|
description: 增量模块打包:拉取最新→编译验证→mvn package→汇总 jar/war。仅当用户显式调用 /harness-package 时使用,不得自动触发。
|
|
4
4
|
---
|
|
5
|
-
<!-- generated by harness_deploy.py; core=
|
|
5
|
+
<!-- generated by harness_deploy.py; core=4e58f505d91f5969; overlay=java; agent=codebuddy; do not edit -->
|
|
6
6
|
# harness-package — 增量模块打包(Java overlay)
|
|
7
7
|
|
|
8
8
|
## Purpose
|
|
@@ -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=4e58f505d91f5969; overlay=java; agent=codebuddy; 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=4e58f505d91f5969; overlay=java; agent=codebuddy; do not edit -->
|
|
7
7
|
# harness-pull — 从 Hunter Platform 下拉与恢复
|
|
8
8
|
|
|
9
9
|
## Purpose
|
|
@@ -3,7 +3,7 @@ name: harness-push
|
|
|
3
3
|
description: 上传本地配置/规则/架构/指令(及显式归档)到 Hunter Platform。仅当用户显式调用 /harness-push
|
|
4
4
|
或明确说'上传到平台'时使用;不得因存在本地修改就自动触发。
|
|
5
5
|
---
|
|
6
|
-
<!-- generated by harness_deploy.py; core=
|
|
6
|
+
<!-- generated by harness_deploy.py; core=4e58f505d91f5969; overlay=java; agent=codebuddy; do not edit -->
|
|
7
7
|
# harness-push — 上传到 Hunter Platform
|
|
8
8
|
|
|
9
9
|
## Purpose
|
|
@@ -4,7 +4,7 @@ description: 6维度代码审查(架构/安全/规范/兼容/测试/性能)
|
|
|
4
4
|
.harness/context-index.json)和测试场景表,在隔离上下文运行。仅当用户显式调用 /harness-review 时使用;不得在
|
|
5
5
|
test 结束后自动接续执行。
|
|
6
6
|
---
|
|
7
|
-
<!-- generated by harness_deploy.py; core=
|
|
7
|
+
<!-- generated by harness_deploy.py; core=4e58f505d91f5969; overlay=java; agent=codebuddy; do not edit -->
|
|
8
8
|
# harness-review — 代码审查
|
|
9
9
|
|
|
10
10
|
## Purpose
|
|
@@ -3,7 +3,7 @@ name: harness-run
|
|
|
3
3
|
description: 按变更簇执行 TDD 编码循环(RED→GREEN→REFACTOR→编译验证),逐变更簇实现计划中的任务。仅当用户显式调用
|
|
4
4
|
/harness-run 时使用;不得因用户提到编码/实现就自动触发,也不得被其他阶段 skill 自动接续。
|
|
5
5
|
---
|
|
6
|
-
<!-- generated by harness_deploy.py; core=
|
|
6
|
+
<!-- generated by harness_deploy.py; core=4e58f505d91f5969; overlay=java; agent=codebuddy; do not edit -->
|
|
7
7
|
# harness-run — 需求编码
|
|
8
8
|
|
|
9
9
|
## Purpose
|
|
@@ -3,7 +3,7 @@ name: harness-submit
|
|
|
3
3
|
description: 最终提交封装:验证→中文 commit→提交/推送;worktree 模式含 --no-ff 合并回主分支。仅当用户显式调用
|
|
4
4
|
/harness-submit(或 /harness-merge 重入合并段)时使用;用户口头说'提交/commit/push'时必须先确认,不得自动触发。
|
|
5
5
|
---
|
|
6
|
-
<!-- generated by harness_deploy.py; core=
|
|
6
|
+
<!-- generated by harness_deploy.py; core=4e58f505d91f5969; overlay=java; agent=codebuddy; do not edit -->
|
|
7
7
|
# harness-submit — 最终提交(含 worktree 合并)
|
|
8
8
|
|
|
9
9
|
## Purpose
|
|
@@ -4,7 +4,7 @@ description: Use when the user asks to synchronize, refresh, or validate Harness
|
|
|
4
4
|
metadata, adapters, remote knowledge ownership, instruction entrypoints,
|
|
5
5
|
config origins, or CodeGraph status.
|
|
6
6
|
---
|
|
7
|
-
<!-- generated by harness_deploy.py; core=
|
|
7
|
+
<!-- generated by harness_deploy.py; core=4e58f505d91f5969; overlay=java; agent=codebuddy; do not edit -->
|
|
8
8
|
# harness-sync
|
|
9
9
|
|
|
10
10
|
## Purpose
|
|
@@ -3,7 +3,7 @@ name: harness-test
|
|
|
3
3
|
description: 测试执行:读取场景表,执行单元测试+API接口测试+数据兼容验证,输出测试报告。仅当用户显式调用 /harness-test
|
|
4
4
|
时使用;不得在 run 结束后自动接续执行。
|
|
5
5
|
---
|
|
6
|
-
<!-- generated by harness_deploy.py; core=
|
|
6
|
+
<!-- generated by harness_deploy.py; core=4e58f505d91f5969; overlay=java; agent=codebuddy; do not edit -->
|
|
7
7
|
# harness-test — 测试执行
|
|
8
8
|
|
|
9
9
|
## 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 参数文件:
|
|
@@ -91,6 +91,7 @@ import harness_events as he # noqa: E402
|
|
|
91
91
|
import harness_events_sync as hes # noqa: E402
|
|
92
92
|
import harness_efficiency as heff # noqa: E402
|
|
93
93
|
import harness_gate as hgate # noqa: E402
|
|
94
|
+
import harness_knowledge_candidates as hkc # noqa: E402
|
|
94
95
|
import harness_ledger as hl # noqa: E402
|
|
95
96
|
import harness_paths as hp # noqa: E402
|
|
96
97
|
import harness_phase as hphase # noqa: E402
|
|
@@ -4173,6 +4174,33 @@ def write_archive_meta(work_dir: Path, summary: dict[str, Any]) -> Path:
|
|
|
4173
4174
|
return out
|
|
4174
4175
|
|
|
4175
4176
|
|
|
4177
|
+
def write_knowledge_candidates(work_dir: Path, summary: dict[str, Any]) -> Path:
|
|
4178
|
+
"""Generate candidates/knowledge.json from summary-data (single ownership).
|
|
4179
|
+
|
|
4180
|
+
Mirrors write_archive_meta: derived from the same summary, written before the
|
|
4181
|
+
after-manifest so its bytes are covered. The archive directory name is the
|
|
4182
|
+
archive id, matching write_archive_meta's `archive-id` field.
|
|
4183
|
+
"""
|
|
4184
|
+
archive_id = work_dir.name
|
|
4185
|
+
candidates = hkc.build_knowledge_candidates(
|
|
4186
|
+
summary,
|
|
4187
|
+
change_key=str(summary.get("changeName") or archive_id),
|
|
4188
|
+
archive_id=archive_id,
|
|
4189
|
+
# The archive schema version identifies the producing format; there is
|
|
4190
|
+
# no separate harness version constant to borrow here.
|
|
4191
|
+
producer_version=SCHEMA_VERSION,
|
|
4192
|
+
created_at=now_iso(),
|
|
4193
|
+
)
|
|
4194
|
+
out = work_dir / "candidates" / "knowledge.json"
|
|
4195
|
+
out.parent.mkdir(parents=True, exist_ok=True)
|
|
4196
|
+
out.write_text(
|
|
4197
|
+
hkc.render_knowledge_candidates_json(candidates),
|
|
4198
|
+
encoding="utf-8",
|
|
4199
|
+
newline="\n",
|
|
4200
|
+
)
|
|
4201
|
+
return out
|
|
4202
|
+
|
|
4203
|
+
|
|
4176
4204
|
|
|
4177
4205
|
|
|
4178
4206
|
def _changed_files_from_git(
|
|
@@ -8269,6 +8297,21 @@ def cmd_finalize(
|
|
|
8269
8297
|
warnings.append(f"archive-meta write failed: {exc}")
|
|
8270
8298
|
payload["steps"]["archive_meta"] = {"ok": False, "error": str(exc)}
|
|
8271
8299
|
|
|
8300
|
+
# --- 8b. knowledge candidates (also before the after-manifest) ---
|
|
8301
|
+
# Soft-fail like archive-meta: an archive must never be rolled back because
|
|
8302
|
+
# knowledge extraction found nothing. An empty array is a valid outcome.
|
|
8303
|
+
try:
|
|
8304
|
+
summary = read_json(summary_path)
|
|
8305
|
+
candidates_path = write_knowledge_candidates(work_dir, summary)
|
|
8306
|
+
payload["steps"]["knowledge_candidates"] = {
|
|
8307
|
+
"ok": True,
|
|
8308
|
+
"path": str(candidates_path),
|
|
8309
|
+
"count": len(read_json(candidates_path)),
|
|
8310
|
+
}
|
|
8311
|
+
except Exception as exc: # noqa: BLE001 — candidates soft-fail
|
|
8312
|
+
warnings.append(f"knowledge candidates write failed: {exc}")
|
|
8313
|
+
payload["steps"]["knowledge_candidates"] = {"ok": False, "error": str(exc)}
|
|
8314
|
+
|
|
8272
8315
|
# --- 9/10. final summary stats, then LAST manifest (IA-7) ---
|
|
8273
8316
|
# Post-manifest rewrites of covered bytes are forbidden. We update the
|
|
8274
8317
|
# summary first, regenerate after-manifest last, then verify on-disk hashes.
|
|
@@ -8885,6 +8928,12 @@ def _archive_core_file_specs(
|
|
|
8885
8928
|
file_specs.append(
|
|
8886
8929
|
(source, f"{folder}/{relative}", role, "text/markdown")
|
|
8887
8930
|
)
|
|
8931
|
+
add_if_file(
|
|
8932
|
+
archive / "candidates" / "knowledge.json",
|
|
8933
|
+
"candidates/knowledge.json",
|
|
8934
|
+
"knowledge_candidates",
|
|
8935
|
+
"application/json",
|
|
8936
|
+
)
|
|
8888
8937
|
add_if_file(
|
|
8889
8938
|
archive / "meta" / "archive-meta.md",
|
|
8890
8939
|
"archive-meta.md",
|
|
@@ -8913,6 +8962,8 @@ def _archive_source_identity(project_root: Path) -> dict[str, str | None]:
|
|
|
8913
8962
|
["git", *args],
|
|
8914
8963
|
cwd=str(project_root),
|
|
8915
8964
|
text=True,
|
|
8965
|
+
encoding="utf-8",
|
|
8966
|
+
errors="replace",
|
|
8916
8967
|
stdout=subprocess.PIPE,
|
|
8917
8968
|
stderr=subprocess.DEVNULL,
|
|
8918
8969
|
timeout=10,
|
|
@@ -9449,6 +9500,10 @@ def auto_push_archive_core(
|
|
|
9449
9500
|
command,
|
|
9450
9501
|
cwd=str(project_root),
|
|
9451
9502
|
text=True,
|
|
9503
|
+
# 不指定 encoding 时中文 Windows 按 cp936 解码 UTF-8,
|
|
9504
|
+
# CLI 的中文回执会被损坏,随后的 json.loads 也可能失败。
|
|
9505
|
+
encoding="utf-8",
|
|
9506
|
+
errors="replace",
|
|
9452
9507
|
stdout=subprocess.PIPE,
|
|
9453
9508
|
stderr=subprocess.PIPE,
|
|
9454
9509
|
timeout=180,
|