@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),
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"schema_version": 2,
|
|
3
3
|
"profile": "general",
|
|
4
4
|
"adapter": "claude-code",
|
|
5
|
-
"bundle_version": "0.2.
|
|
5
|
+
"bundle_version": "0.2.67",
|
|
6
6
|
"requires": {
|
|
7
|
-
"minimumCliVersion": "0.2.
|
|
7
|
+
"minimumCliVersion": "0.2.86",
|
|
8
8
|
"capabilities": [
|
|
9
9
|
"sync@2",
|
|
10
10
|
"rules-sync@1",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"files": [
|
|
26
26
|
{
|
|
27
27
|
"path": ".harness-build.json",
|
|
28
|
-
"sha256": "
|
|
28
|
+
"sha256": "cc158bb89a59e384d0b343e7f3c52a874a5ea7879a7aea66709e7b9b98c4dcaa"
|
|
29
29
|
},
|
|
30
30
|
{
|
|
31
31
|
"path": "agents/harness-evaluator.md",
|
|
@@ -97,7 +97,7 @@
|
|
|
97
97
|
},
|
|
98
98
|
{
|
|
99
99
|
"path": "harness-archive/SKILL.md",
|
|
100
|
-
"sha256": "
|
|
100
|
+
"sha256": "f81ef9835b374ba758f0d6fad02fb7112411856c7f5406027876e33fb61b8648"
|
|
101
101
|
},
|
|
102
102
|
{
|
|
103
103
|
"path": "harness-archive/templates/summary-data-template.json",
|
|
@@ -113,7 +113,7 @@
|
|
|
113
113
|
},
|
|
114
114
|
{
|
|
115
115
|
"path": "harness-codebase-map/SKILL.md",
|
|
116
|
-
"sha256": "
|
|
116
|
+
"sha256": "e52941ab45203d0ca3a784429b428523124829adc83f971ef6098c0064b72b90"
|
|
117
117
|
},
|
|
118
118
|
{
|
|
119
119
|
"path": "harness-codebase-map/templates/ARCHITECTURE.md",
|
|
@@ -153,11 +153,11 @@
|
|
|
153
153
|
},
|
|
154
154
|
{
|
|
155
155
|
"path": "harness-knowledge-ingest/SKILL.md",
|
|
156
|
-
"sha256": "
|
|
156
|
+
"sha256": "8fdf89699db58fc4aec5585da55f6a1f9952cdec4f5b594365504c35e0de8094"
|
|
157
157
|
},
|
|
158
158
|
{
|
|
159
159
|
"path": "harness-knowledge-query/SKILL.md",
|
|
160
|
-
"sha256": "
|
|
160
|
+
"sha256": "415ae4026c2e43df75a86d7f1685e31c1474c53a0f75844399d7435a77be6ee0"
|
|
161
161
|
},
|
|
162
162
|
{
|
|
163
163
|
"path": "harness-plan/checklist.md",
|
|
@@ -173,15 +173,15 @@
|
|
|
173
173
|
},
|
|
174
174
|
{
|
|
175
175
|
"path": "harness-plan/SKILL.md",
|
|
176
|
-
"sha256": "
|
|
176
|
+
"sha256": "6a19ce510aa26f773b0562171fd099b8a22cdbe0fb34d395e34ed0c414745dfd"
|
|
177
177
|
},
|
|
178
178
|
{
|
|
179
179
|
"path": "harness-pull/SKILL.md",
|
|
180
|
-
"sha256": "
|
|
180
|
+
"sha256": "9b5d350dbb6f3f2a77b44bfe2038251c868fac6ddc048a1b7883f4b93515727f"
|
|
181
181
|
},
|
|
182
182
|
{
|
|
183
183
|
"path": "harness-push/SKILL.md",
|
|
184
|
-
"sha256": "
|
|
184
|
+
"sha256": "582b2ee80d72649ec51efbe4f60d52fea4c59a5addf9db316a116e5562b7d2de"
|
|
185
185
|
},
|
|
186
186
|
{
|
|
187
187
|
"path": "harness-review/checklist.md",
|
|
@@ -197,7 +197,7 @@
|
|
|
197
197
|
},
|
|
198
198
|
{
|
|
199
199
|
"path": "harness-review/SKILL.md",
|
|
200
|
-
"sha256": "
|
|
200
|
+
"sha256": "699d69c44a8bbfe13badf6d4ec7ad9cdb6513f818fb7ef56becf80cc8e1ad6fb"
|
|
201
201
|
},
|
|
202
202
|
{
|
|
203
203
|
"path": "harness-run/checklist.md",
|
|
@@ -213,7 +213,7 @@
|
|
|
213
213
|
},
|
|
214
214
|
{
|
|
215
215
|
"path": "harness-run/SKILL.md",
|
|
216
|
-
"sha256": "
|
|
216
|
+
"sha256": "a6e72fa4bffb277237904c482a915c66f53287e78dcd83975c100ba7763d5a48"
|
|
217
217
|
},
|
|
218
218
|
{
|
|
219
219
|
"path": "harness-submit/checklist.md",
|
|
@@ -225,7 +225,7 @@
|
|
|
225
225
|
},
|
|
226
226
|
{
|
|
227
227
|
"path": "harness-submit/SKILL.md",
|
|
228
|
-
"sha256": "
|
|
228
|
+
"sha256": "6956d9e69fa4c7ce78761a90980e66f3ec2a8df727443e3ab6ccb7327eae164f"
|
|
229
229
|
},
|
|
230
230
|
{
|
|
231
231
|
"path": "harness-sync/reference.md",
|
|
@@ -233,7 +233,7 @@
|
|
|
233
233
|
},
|
|
234
234
|
{
|
|
235
235
|
"path": "harness-sync/SKILL.md",
|
|
236
|
-
"sha256": "
|
|
236
|
+
"sha256": "cdfc86c932e9a2e41879cc136b5adf00f0447f55fbe43e218111e9a95102d737"
|
|
237
237
|
},
|
|
238
238
|
{
|
|
239
239
|
"path": "harness-test/checklist.md",
|
|
@@ -257,7 +257,7 @@
|
|
|
257
257
|
},
|
|
258
258
|
{
|
|
259
259
|
"path": "harness-test/SKILL.md",
|
|
260
|
-
"sha256": "
|
|
260
|
+
"sha256": "c51e8ee818ffb7078341d634ad357eb205bd16cb3f788902703fd55bbf478c56"
|
|
261
261
|
},
|
|
262
262
|
{
|
|
263
263
|
"path": "protocols/archive-report-protocol.md",
|
|
@@ -293,7 +293,7 @@
|
|
|
293
293
|
},
|
|
294
294
|
{
|
|
295
295
|
"path": "protocols/powershell-protocol.md",
|
|
296
|
-
"sha256": "
|
|
296
|
+
"sha256": "6812ea19e75474a2ab668854c4d08f693e0cc2a577c3ec0542f0d3811883f4fa"
|
|
297
297
|
},
|
|
298
298
|
{
|
|
299
299
|
"path": "protocols/registry-governance-protocol.md",
|
|
@@ -329,7 +329,7 @@
|
|
|
329
329
|
},
|
|
330
330
|
{
|
|
331
331
|
"path": "scripts/harness_archive.py",
|
|
332
|
-
"sha256": "
|
|
332
|
+
"sha256": "c90932fb5234281a6d3e75b818580c12820373084c03815dc8d17d1f49955253"
|
|
333
333
|
},
|
|
334
334
|
{
|
|
335
335
|
"path": "scripts/harness_change.py",
|
|
@@ -337,11 +337,11 @@
|
|
|
337
337
|
},
|
|
338
338
|
{
|
|
339
339
|
"path": "scripts/harness_check_gate.py",
|
|
340
|
-
"sha256": "
|
|
340
|
+
"sha256": "a4965d22d27bc4105db24a55e1fb6d1f94f7c1e970c75b809e470130df53814c"
|
|
341
341
|
},
|
|
342
342
|
{
|
|
343
343
|
"path": "scripts/harness_context.py",
|
|
344
|
-
"sha256": "
|
|
344
|
+
"sha256": "e61bbeb3073d77a34ac9aefb13421c187a8d16c8f95597d1ca50d91b0c6af1d2"
|
|
345
345
|
},
|
|
346
346
|
{
|
|
347
347
|
"path": "scripts/harness_deploy.py",
|
|
@@ -369,7 +369,7 @@
|
|
|
369
369
|
},
|
|
370
370
|
{
|
|
371
371
|
"path": "scripts/harness_fixback.py",
|
|
372
|
-
"sha256": "
|
|
372
|
+
"sha256": "287bac38427c7fe60de5384d2b867df9d4cf7be2fd03fbffa307f3ac63b55bfd"
|
|
373
373
|
},
|
|
374
374
|
{
|
|
375
375
|
"path": "scripts/harness_gate.py",
|
|
@@ -383,9 +383,13 @@
|
|
|
383
383
|
"path": "scripts/harness_integration.py",
|
|
384
384
|
"sha256": "cded86ca9fb6b87301945b4d81919e5d0301730c0bb75c925e0d4c8246851d30"
|
|
385
385
|
},
|
|
386
|
+
{
|
|
387
|
+
"path": "scripts/harness_knowledge_candidates.py",
|
|
388
|
+
"sha256": "efbc52faa1f01053e033c3a609ab99a0a3df153567a7fb89558f6b3a87e041f7"
|
|
389
|
+
},
|
|
386
390
|
{
|
|
387
391
|
"path": "scripts/harness_ledger.py",
|
|
388
|
-
"sha256": "
|
|
392
|
+
"sha256": "476832333a63089fe312d9fb288b9e8a51d081a44b30548e1dac9a2b8298e0fb"
|
|
389
393
|
},
|
|
390
394
|
{
|
|
391
395
|
"path": "scripts/harness_migration_head.py",
|
|
@@ -437,7 +441,7 @@
|
|
|
437
441
|
},
|
|
438
442
|
{
|
|
439
443
|
"path": "scripts/harness_runtime.py",
|
|
440
|
-
"sha256": "
|
|
444
|
+
"sha256": "b02c4953d9237bdebf308fb61f6cda55b7f377a01102fe635196d443421f7a2f"
|
|
441
445
|
},
|
|
442
446
|
{
|
|
443
447
|
"path": "scripts/harness_service.py",
|