@hap-labs/human-agent-paradigm 0.1.0
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/CHANGELOG.md +21 -0
- package/LICENSE +21 -0
- package/README.md +60 -0
- package/README.zh-CN.md +52 -0
- package/assets/EN/CONFORMANCE_CHECKLIST.md +211 -0
- package/assets/EN/CONTRACT_TEMPLATE.md +241 -0
- package/assets/EN/DECISION_REQUEST_TEMPLATE.md +157 -0
- package/assets/EN/DELIVERY_REPORT_TEMPLATE.md +188 -0
- package/assets/EN/DERIVED_SPECIFICATION.md +607 -0
- package/assets/EN/HUMAN_AGENT_PARADIGM.md +390 -0
- package/assets/ZH_CN/CONFORMANCE_CHECKLIST.md +192 -0
- package/assets/ZH_CN/CONTRACT_TEMPLATE.md +216 -0
- package/assets/ZH_CN/DECISION_REQUEST_TEMPLATE.md +140 -0
- package/assets/ZH_CN/DELIVERY_REPORT_TEMPLATE.md +199 -0
- package/assets/ZH_CN/DERIVED_SPECIFICATION.md +497 -0
- package/assets/ZH_CN/HUMAN_AGENT_PARADIGM.md +285 -0
- package/package.json +38 -0
- package/scripts/python/repo_governance_check/__init__.py +3 -0
- package/scripts/python/repo_governance_check/__main__.py +6 -0
- package/scripts/python/repo_governance_check/checks.py +359 -0
- package/scripts/python/repo_governance_check/cli.py +115 -0
- package/scripts/python/repo_governance_check/config.py +33 -0
- package/scripts/python/repo_governance_check/governance_check_config.json +166 -0
- package/scripts/python/repo_governance_check/report.py +79 -0
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
"""G01-G08 governance checks.
|
|
2
|
+
|
|
3
|
+
Every rule receives a CheckContext and returns a list of Finding objects.
|
|
4
|
+
Rules never modify files and only read files declared in the config.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
import re
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
ITEM_RE = re.compile(r"^\|\s*([A-Z]\d+)\s*(★)?\s*\|")
|
|
13
|
+
SEPARATOR_CELL_RE = re.compile(r"^:?-{1,}:?$")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass(frozen=True)
|
|
17
|
+
class Finding:
|
|
18
|
+
rule_id: str
|
|
19
|
+
severity: str
|
|
20
|
+
file: str
|
|
21
|
+
line: int
|
|
22
|
+
message: str
|
|
23
|
+
evidence: str
|
|
24
|
+
|
|
25
|
+
def to_dict(self):
|
|
26
|
+
return {
|
|
27
|
+
"rule_id": self.rule_id,
|
|
28
|
+
"severity": self.severity,
|
|
29
|
+
"file": self.file,
|
|
30
|
+
"line": self.line,
|
|
31
|
+
"message": self.message,
|
|
32
|
+
"evidence": self.evidence,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _upper_name(filename):
|
|
37
|
+
"""Language directories store documents under uppercase underscore names;
|
|
38
|
+
consumer copies keep the canonical lowercase names."""
|
|
39
|
+
stem, dot, ext = filename.rpartition(".")
|
|
40
|
+
return stem.replace("-", "_").upper() + dot + ext
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class CheckContext:
|
|
44
|
+
def __init__(self, root, config, docs_dir=None):
|
|
45
|
+
self.root = Path(root)
|
|
46
|
+
self.config = config
|
|
47
|
+
self.docs_dir = Path(docs_dir) if docs_dir else self.root
|
|
48
|
+
|
|
49
|
+
def doc_file(self, filename):
|
|
50
|
+
"""First existing document path under docs_dir (canonical or
|
|
51
|
+
uppercase-underscore form); falls back to the canonical path."""
|
|
52
|
+
direct = self.docs_dir / filename
|
|
53
|
+
if direct.is_file():
|
|
54
|
+
return direct
|
|
55
|
+
upper = self.docs_dir / _upper_name(filename)
|
|
56
|
+
return upper if upper.is_file() else direct
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _read_lines(ctx, filename):
|
|
60
|
+
try:
|
|
61
|
+
return ctx.doc_file(filename).read_text(encoding="utf-8").splitlines()
|
|
62
|
+
except OSError:
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _headings(lines):
|
|
67
|
+
for number, raw in enumerate(lines, 1):
|
|
68
|
+
stripped = raw.strip()
|
|
69
|
+
if stripped.startswith("#"):
|
|
70
|
+
yield number, stripped.lstrip("#").strip()
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _finding(ctx, rule_id, filename, line, message, evidence):
|
|
74
|
+
return Finding(rule_id, "error", filename, line, message, evidence)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _table_blocks(lines):
|
|
78
|
+
blocks = []
|
|
79
|
+
current = []
|
|
80
|
+
for number, raw in enumerate(lines, 1):
|
|
81
|
+
if raw.strip().startswith("|"):
|
|
82
|
+
current.append((number, raw.strip()))
|
|
83
|
+
elif current:
|
|
84
|
+
blocks.append(current)
|
|
85
|
+
current = []
|
|
86
|
+
if current:
|
|
87
|
+
blocks.append(current)
|
|
88
|
+
return blocks
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _cell_count(row):
|
|
92
|
+
return max(row.count("|") - 1, 0)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _is_separator(row):
|
|
96
|
+
cells = [cell.strip() for cell in row.strip("|").split("|")]
|
|
97
|
+
if not cells:
|
|
98
|
+
return False
|
|
99
|
+
return all(SEPARATOR_CELL_RE.fullmatch(cell) for cell in cells)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def check_required_files(ctx):
|
|
103
|
+
findings = []
|
|
104
|
+
for filename in ctx.config["required_files"]:
|
|
105
|
+
checked = ctx.doc_file(filename)
|
|
106
|
+
if not checked.is_file():
|
|
107
|
+
findings.append(
|
|
108
|
+
_finding(
|
|
109
|
+
ctx,
|
|
110
|
+
"G01",
|
|
111
|
+
filename,
|
|
112
|
+
0,
|
|
113
|
+
f"required file is missing: {filename}",
|
|
114
|
+
f"checked path: {checked}",
|
|
115
|
+
)
|
|
116
|
+
)
|
|
117
|
+
return findings
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def check_upstream_references(ctx):
|
|
121
|
+
findings = []
|
|
122
|
+
for filename, expected in ctx.config["upstream_references"].items():
|
|
123
|
+
lines = _read_lines(ctx, filename)
|
|
124
|
+
if lines is None:
|
|
125
|
+
continue
|
|
126
|
+
content = "\n".join(lines)
|
|
127
|
+
for ref in expected:
|
|
128
|
+
if ref not in content:
|
|
129
|
+
findings.append(
|
|
130
|
+
_finding(
|
|
131
|
+
ctx,
|
|
132
|
+
"G02",
|
|
133
|
+
filename,
|
|
134
|
+
0,
|
|
135
|
+
f"missing upstream reference: {ref!r}",
|
|
136
|
+
"full-file substring check",
|
|
137
|
+
)
|
|
138
|
+
)
|
|
139
|
+
return findings
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def check_required_headings(ctx):
|
|
143
|
+
findings = []
|
|
144
|
+
for filename, expected in ctx.config["required_headings"].items():
|
|
145
|
+
lines = _read_lines(ctx, filename)
|
|
146
|
+
if lines is None:
|
|
147
|
+
continue
|
|
148
|
+
present = {heading for _, heading in _headings(lines)}
|
|
149
|
+
for heading in expected:
|
|
150
|
+
if heading not in present:
|
|
151
|
+
findings.append(
|
|
152
|
+
_finding(
|
|
153
|
+
ctx,
|
|
154
|
+
"G03",
|
|
155
|
+
filename,
|
|
156
|
+
0,
|
|
157
|
+
f"missing required heading: {heading}",
|
|
158
|
+
"heading text parsed from markdown heading lines",
|
|
159
|
+
)
|
|
160
|
+
)
|
|
161
|
+
return findings
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def check_markdown_tables(ctx):
|
|
165
|
+
findings = []
|
|
166
|
+
for filename in ctx.config["required_files"]:
|
|
167
|
+
lines = _read_lines(ctx, filename)
|
|
168
|
+
if lines is None:
|
|
169
|
+
continue
|
|
170
|
+
for block in _table_blocks(lines):
|
|
171
|
+
first_line, _ = block[0]
|
|
172
|
+
if len(block) < 2 or not _is_separator(block[1][1]):
|
|
173
|
+
findings.append(
|
|
174
|
+
_finding(
|
|
175
|
+
ctx,
|
|
176
|
+
"G04",
|
|
177
|
+
filename,
|
|
178
|
+
first_line,
|
|
179
|
+
"table has no valid separator row as its second row",
|
|
180
|
+
f"table starts at line {first_line}",
|
|
181
|
+
)
|
|
182
|
+
)
|
|
183
|
+
continue
|
|
184
|
+
expected_columns = _cell_count(block[1][1])
|
|
185
|
+
for line_number, row in block:
|
|
186
|
+
if _cell_count(row) != expected_columns:
|
|
187
|
+
findings.append(
|
|
188
|
+
_finding(
|
|
189
|
+
ctx,
|
|
190
|
+
"G04",
|
|
191
|
+
filename,
|
|
192
|
+
line_number,
|
|
193
|
+
"table row column count differs from separator row",
|
|
194
|
+
f"expected {expected_columns} columns, row has {_cell_count(row)}",
|
|
195
|
+
)
|
|
196
|
+
)
|
|
197
|
+
return findings
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def _checklist_missing_sections(ctx, headings):
|
|
201
|
+
findings = []
|
|
202
|
+
checklist_config = ctx.config["checklist"]
|
|
203
|
+
for section in checklist_config["sections"]:
|
|
204
|
+
if section not in headings:
|
|
205
|
+
findings.append(
|
|
206
|
+
_finding(
|
|
207
|
+
ctx,
|
|
208
|
+
"G05",
|
|
209
|
+
"conformance_checklist.md",
|
|
210
|
+
0,
|
|
211
|
+
f"missing checklist section heading: {section}",
|
|
212
|
+
"heading inventory",
|
|
213
|
+
)
|
|
214
|
+
)
|
|
215
|
+
final_section = checklist_config["final_section"]
|
|
216
|
+
if final_section not in headings:
|
|
217
|
+
findings.append(
|
|
218
|
+
_finding(
|
|
219
|
+
ctx,
|
|
220
|
+
"G05",
|
|
221
|
+
"conformance_checklist.md",
|
|
222
|
+
0,
|
|
223
|
+
f"missing final section heading: {final_section}",
|
|
224
|
+
"heading inventory",
|
|
225
|
+
)
|
|
226
|
+
)
|
|
227
|
+
return findings
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def _checklist_row_metrics(lines):
|
|
231
|
+
item_count = 0
|
|
232
|
+
star_count = 0
|
|
233
|
+
bad_star_rows = []
|
|
234
|
+
item_ids = []
|
|
235
|
+
for line_number, raw in enumerate(lines, 1):
|
|
236
|
+
match = ITEM_RE.match(raw.strip())
|
|
237
|
+
if not match:
|
|
238
|
+
continue
|
|
239
|
+
item_count += 1
|
|
240
|
+
item_ids.append(match.group(1))
|
|
241
|
+
if match.group(2):
|
|
242
|
+
star_count += 1
|
|
243
|
+
cells = [cell.strip() for cell in raw.strip().strip("|").split("|")]
|
|
244
|
+
if cells and cells[-1] == "不适用":
|
|
245
|
+
bad_star_rows.append(line_number)
|
|
246
|
+
return item_count, star_count, bad_star_rows, item_ids
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def check_checklist_invariants(ctx):
|
|
250
|
+
lines = _read_lines(ctx, "conformance_checklist.md")
|
|
251
|
+
if lines is None:
|
|
252
|
+
return []
|
|
253
|
+
headings = {heading for _, heading in _headings(lines)}
|
|
254
|
+
findings = _checklist_missing_sections(ctx, headings)
|
|
255
|
+
item_count, star_count, bad_star_rows, item_ids = _checklist_row_metrics(lines)
|
|
256
|
+
checklist_config = ctx.config["checklist"]
|
|
257
|
+
prefix_map = checklist_config.get("item_prefixes", {})
|
|
258
|
+
for section in checklist_config["sections"]:
|
|
259
|
+
prefix = prefix_map.get(section, section.split(".")[0])
|
|
260
|
+
if not any(item_id.startswith(prefix) for item_id in item_ids):
|
|
261
|
+
findings.append(
|
|
262
|
+
_finding(
|
|
263
|
+
ctx,
|
|
264
|
+
"G05",
|
|
265
|
+
"conformance_checklist.md",
|
|
266
|
+
0,
|
|
267
|
+
f"checklist section has no item rows: {section}",
|
|
268
|
+
f"no item ID starts with prefix {prefix!r}",
|
|
269
|
+
)
|
|
270
|
+
)
|
|
271
|
+
expected_items = checklist_config["expected_items"]
|
|
272
|
+
if item_count != expected_items:
|
|
273
|
+
findings.append(
|
|
274
|
+
_finding(
|
|
275
|
+
ctx,
|
|
276
|
+
"G05",
|
|
277
|
+
"conformance_checklist.md",
|
|
278
|
+
0,
|
|
279
|
+
f"checklist item count is {item_count}, expected {expected_items}",
|
|
280
|
+
"item rows matching the A-Z numbering pattern",
|
|
281
|
+
)
|
|
282
|
+
)
|
|
283
|
+
expected_stars = checklist_config["expected_star_items"]
|
|
284
|
+
if star_count != expected_stars:
|
|
285
|
+
findings.append(
|
|
286
|
+
_finding(
|
|
287
|
+
ctx,
|
|
288
|
+
"G05",
|
|
289
|
+
"conformance_checklist.md",
|
|
290
|
+
0,
|
|
291
|
+
f"star item count is {star_count}, expected {expected_stars}",
|
|
292
|
+
"item rows containing the star marker",
|
|
293
|
+
)
|
|
294
|
+
)
|
|
295
|
+
for line_number in bad_star_rows:
|
|
296
|
+
findings.append(
|
|
297
|
+
_finding(
|
|
298
|
+
ctx,
|
|
299
|
+
"G05",
|
|
300
|
+
"conformance_checklist.md",
|
|
301
|
+
line_number,
|
|
302
|
+
"star item result column must not contain 不适用",
|
|
303
|
+
"star item rows may not be exempted in the checklist source",
|
|
304
|
+
)
|
|
305
|
+
)
|
|
306
|
+
return findings
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def _check_sections(ctx, rule_id, filename, config_key):
|
|
310
|
+
lines = _read_lines(ctx, filename)
|
|
311
|
+
if lines is None:
|
|
312
|
+
return []
|
|
313
|
+
present = {heading for _, heading in _headings(lines)}
|
|
314
|
+
findings = []
|
|
315
|
+
for section in ctx.config[config_key]["required_sections"]:
|
|
316
|
+
if section not in present:
|
|
317
|
+
findings.append(
|
|
318
|
+
_finding(
|
|
319
|
+
ctx,
|
|
320
|
+
rule_id,
|
|
321
|
+
filename,
|
|
322
|
+
0,
|
|
323
|
+
f"missing required section: {section}",
|
|
324
|
+
"heading inventory",
|
|
325
|
+
)
|
|
326
|
+
)
|
|
327
|
+
return findings
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def check_contract_template_sections(ctx):
|
|
331
|
+
return _check_sections(ctx, "G06", "contract_template.md", "contract_template")
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def check_decision_template_sections(ctx):
|
|
335
|
+
return _check_sections(ctx, "G07", "decision_request_template.md", "decision_request_template")
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def check_delivery_template_sections(ctx):
|
|
339
|
+
return _check_sections(ctx, "G08", "delivery_report_template.md", "delivery_report_template")
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
ALL_RULES = (
|
|
343
|
+
check_required_files,
|
|
344
|
+
check_upstream_references,
|
|
345
|
+
check_required_headings,
|
|
346
|
+
check_markdown_tables,
|
|
347
|
+
check_checklist_invariants,
|
|
348
|
+
check_contract_template_sections,
|
|
349
|
+
check_decision_template_sections,
|
|
350
|
+
check_delivery_template_sections,
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def run_all_checks(ctx):
|
|
355
|
+
findings = []
|
|
356
|
+
for rule in ALL_RULES:
|
|
357
|
+
findings.extend(rule(ctx))
|
|
358
|
+
findings.sort(key=lambda item: (item.file, item.line, item.rule_id))
|
|
359
|
+
return findings
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""Command line entry point for repo_governance_check."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import time
|
|
7
|
+
|
|
8
|
+
from .checks import CheckContext, _upper_name, run_all_checks
|
|
9
|
+
from .config import load_config
|
|
10
|
+
from .report import render_console, render_json, render_markdown, result_payload
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def find_config(root):
|
|
14
|
+
"""Locate the check config: explicit root copy first, then the bundled
|
|
15
|
+
default that ships next to this package."""
|
|
16
|
+
candidates = [
|
|
17
|
+
root / "governance_check_config.json",
|
|
18
|
+
Path(__file__).resolve().parent / "governance_check_config.json",
|
|
19
|
+
]
|
|
20
|
+
for candidate in candidates:
|
|
21
|
+
if candidate.is_file():
|
|
22
|
+
return candidate
|
|
23
|
+
return None
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def detect_docs_dir(root, config):
|
|
27
|
+
"""Pick the directory holding the governance document set.
|
|
28
|
+
|
|
29
|
+
Candidates mirror the repository layouts: the root itself, the package's
|
|
30
|
+
per-language assets directories (ZH_CN holds the frozen Chinese
|
|
31
|
+
originals, EN the English translations), the project docs directory, and
|
|
32
|
+
a consumer workspace's .hap/docs/ directory. Falls back to the root so an
|
|
33
|
+
explicit --config against arbitrary directories keeps the historical
|
|
34
|
+
semantics (missing files surface as G01 findings).
|
|
35
|
+
"""
|
|
36
|
+
candidates = [
|
|
37
|
+
root,
|
|
38
|
+
root / "assets" / "ZH_CN",
|
|
39
|
+
root / "assets" / "EN",
|
|
40
|
+
root / "assets",
|
|
41
|
+
root / "vendor" / "governance-snapshot" / "ZH_CN",
|
|
42
|
+
root / "vendor" / "governance-snapshot" / "EN",
|
|
43
|
+
root / "vendor" / "governance-snapshot",
|
|
44
|
+
root / "docs",
|
|
45
|
+
root / ".hap" / "docs",
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
def _doc_exists(candidate, name):
|
|
49
|
+
return (candidate / name).is_file() or (candidate / _upper_name(name)).is_file()
|
|
50
|
+
|
|
51
|
+
for candidate in candidates:
|
|
52
|
+
if all(_doc_exists(candidate, name) for name in config["required_files"]):
|
|
53
|
+
return candidate
|
|
54
|
+
return root
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def parse_args(argv=None):
|
|
58
|
+
parser = argparse.ArgumentParser(
|
|
59
|
+
prog="repo_governance_check",
|
|
60
|
+
description="Check governance documents for structural consistency.",
|
|
61
|
+
)
|
|
62
|
+
parser.add_argument("--root", default=".", help="repository root to check")
|
|
63
|
+
parser.add_argument(
|
|
64
|
+
"--config",
|
|
65
|
+
default=None,
|
|
66
|
+
help="path to governance_check_config.json (default: ROOT or the bundled package copy)",
|
|
67
|
+
)
|
|
68
|
+
parser.add_argument(
|
|
69
|
+
"--out-dir",
|
|
70
|
+
default="reports",
|
|
71
|
+
help="directory for generated reports (default: ROOT/reports)",
|
|
72
|
+
)
|
|
73
|
+
parser.add_argument(
|
|
74
|
+
"--format",
|
|
75
|
+
choices=("json", "markdown", "both"),
|
|
76
|
+
default="both",
|
|
77
|
+
help="report format to write",
|
|
78
|
+
)
|
|
79
|
+
return parser.parse_args(argv)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def main(argv=None):
|
|
83
|
+
args = parse_args(argv)
|
|
84
|
+
root = Path(args.root).resolve()
|
|
85
|
+
if args.config:
|
|
86
|
+
config_path = Path(args.config).resolve()
|
|
87
|
+
else:
|
|
88
|
+
config_path = find_config(root)
|
|
89
|
+
if config_path is None:
|
|
90
|
+
raise SystemExit(
|
|
91
|
+
"no governance_check_config.json found under --root "
|
|
92
|
+
"or next to the package; pass --config explicitly"
|
|
93
|
+
)
|
|
94
|
+
out_dir = Path(args.out_dir)
|
|
95
|
+
if not out_dir.is_absolute():
|
|
96
|
+
out_dir = root / out_dir
|
|
97
|
+
config = load_config(config_path)
|
|
98
|
+
docs_dir = detect_docs_dir(root, config)
|
|
99
|
+
context = CheckContext(root, config, docs_dir)
|
|
100
|
+
started = time.perf_counter()
|
|
101
|
+
findings = run_all_checks(context)
|
|
102
|
+
elapsed = time.perf_counter() - started
|
|
103
|
+
run_id = datetime.now(timezone.utc).strftime("RGC-%Y%m%d-%H%M%S")
|
|
104
|
+
payload = result_payload(run_id, root, config_path, docs_dir, elapsed, findings)
|
|
105
|
+
out_dir.mkdir(parents=True, exist_ok=True)
|
|
106
|
+
if args.format in ("json", "both"):
|
|
107
|
+
(out_dir / "repo_governance_check_report.json").write_text(
|
|
108
|
+
render_json(payload), encoding="utf-8"
|
|
109
|
+
)
|
|
110
|
+
if args.format in ("markdown", "both"):
|
|
111
|
+
(out_dir / "repo_governance_check_report.md").write_text(
|
|
112
|
+
render_markdown(payload), encoding="utf-8"
|
|
113
|
+
)
|
|
114
|
+
print(render_console(payload))
|
|
115
|
+
return 1 if payload["summary"]["total_findings"] else 0
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Load and validate governance check configuration."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
REQUIRED_KEYS = (
|
|
8
|
+
"required_files",
|
|
9
|
+
"upstream_references",
|
|
10
|
+
"required_headings",
|
|
11
|
+
"checklist",
|
|
12
|
+
"contract_template",
|
|
13
|
+
"decision_request_template",
|
|
14
|
+
"delivery_report_template",
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def load_config(path):
|
|
19
|
+
"""Read a JSON config file and return the decoded object.
|
|
20
|
+
|
|
21
|
+
The function only performs structural validation. Rule-specific values
|
|
22
|
+
are consumed by the checks module.
|
|
23
|
+
"""
|
|
24
|
+
config_path = Path(path)
|
|
25
|
+
config = json.loads(config_path.read_text(encoding="utf-8"))
|
|
26
|
+
missing = [key for key in REQUIRED_KEYS if key not in config]
|
|
27
|
+
if missing:
|
|
28
|
+
raise ValueError(f"config missing keys: {', '.join(missing)}")
|
|
29
|
+
if not isinstance(config["required_files"], list):
|
|
30
|
+
raise ValueError("config 'required_files' must be a list")
|
|
31
|
+
if not isinstance(config["required_headings"], dict):
|
|
32
|
+
raise ValueError("config 'required_headings' must be a dict")
|
|
33
|
+
return config
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"required_files": [
|
|
4
|
+
"human_agent_paradigm.md",
|
|
5
|
+
"derived_specification.md",
|
|
6
|
+
"conformance_checklist.md",
|
|
7
|
+
"contract_template.md",
|
|
8
|
+
"decision_request_template.md",
|
|
9
|
+
"delivery_report_template.md"
|
|
10
|
+
],
|
|
11
|
+
"upstream_references": {
|
|
12
|
+
"derived_specification.md": [
|
|
13
|
+
"版本:v1.0",
|
|
14
|
+
"上级文件:`HUMAN_AGENT_PARADIGM.md` v1.0"
|
|
15
|
+
],
|
|
16
|
+
"conformance_checklist.md": [
|
|
17
|
+
"版本:v1.0",
|
|
18
|
+
"配套文件:`DERIVED_SPECIFICATION.md` v1.0"
|
|
19
|
+
],
|
|
20
|
+
"contract_template.md": [
|
|
21
|
+
"派生规范 `DERIVED_SPECIFICATION.md` v1.0"
|
|
22
|
+
],
|
|
23
|
+
"decision_request_template.md": [
|
|
24
|
+
"派生规范 `DERIVED_SPECIFICATION.md` v1.0"
|
|
25
|
+
],
|
|
26
|
+
"delivery_report_template.md": [
|
|
27
|
+
"派生规范 `DERIVED_SPECIFICATION.md` v1.0"
|
|
28
|
+
]
|
|
29
|
+
},
|
|
30
|
+
"required_headings": {
|
|
31
|
+
"human_agent_paradigm.md": [
|
|
32
|
+
"0. 文件地位与效力",
|
|
33
|
+
"1. 愿景与成功定义",
|
|
34
|
+
"2. 根本信条(公理)",
|
|
35
|
+
"3. 两条硬性要求",
|
|
36
|
+
"4. 角色与行为边界",
|
|
37
|
+
"5. 协作流程(方向级)",
|
|
38
|
+
"6. 卓越性方向(优秀的五个维度)",
|
|
39
|
+
"7. 经济性方向(最低总成本)",
|
|
40
|
+
"8. 资产与演进(复利飞轮)",
|
|
41
|
+
"9. 范式的自我治理",
|
|
42
|
+
"10. 术语定义",
|
|
43
|
+
"11. 范式级验收判据"
|
|
44
|
+
],
|
|
45
|
+
"derived_specification.md": [
|
|
46
|
+
"0. 地位、追溯与解释",
|
|
47
|
+
"1. 通用交付物与记录要求",
|
|
48
|
+
"2. 契约规范",
|
|
49
|
+
"3. 协作流程规范(P0–P7)",
|
|
50
|
+
"4. 决策与沟通规范",
|
|
51
|
+
"5. 可靠性与证据规范",
|
|
52
|
+
"6. 优秀与多视角审视规范",
|
|
53
|
+
"7. 经济性与自治预算规范",
|
|
54
|
+
"8. 资产与演进规范",
|
|
55
|
+
"9. 符合性治理",
|
|
56
|
+
"附录 A:追溯总表",
|
|
57
|
+
"附录 B:最低记录清单",
|
|
58
|
+
"修订记录"
|
|
59
|
+
],
|
|
60
|
+
"conformance_checklist.md": [
|
|
61
|
+
"使用说明",
|
|
62
|
+
"A. 规范地位与适用性",
|
|
63
|
+
"B. 可靠(硬性要求一)",
|
|
64
|
+
"C. 优秀(硬性要求二)",
|
|
65
|
+
"D. 边界与决策",
|
|
66
|
+
"E. 流程与契约",
|
|
67
|
+
"F. 经济性与自治",
|
|
68
|
+
"G. 资产与演进",
|
|
69
|
+
"H. 安全与合规底线",
|
|
70
|
+
"I. 治理与证据",
|
|
71
|
+
"J. 范式级验收判据(宪法 11)",
|
|
72
|
+
"最终判定",
|
|
73
|
+
"修订记录"
|
|
74
|
+
],
|
|
75
|
+
"contract_template.md": [
|
|
76
|
+
"使用规则",
|
|
77
|
+
"契约元信息"
|
|
78
|
+
],
|
|
79
|
+
"decision_request_template.md": [
|
|
80
|
+
"使用规则"
|
|
81
|
+
],
|
|
82
|
+
"delivery_report_template.md": [
|
|
83
|
+
"使用规则"
|
|
84
|
+
]
|
|
85
|
+
},
|
|
86
|
+
"checklist": {
|
|
87
|
+
"expected_items": 89,
|
|
88
|
+
"expected_star_items": 41,
|
|
89
|
+
"item_prefixes": {
|
|
90
|
+
"A. 规范地位与适用性": "A",
|
|
91
|
+
"B. 可靠(硬性要求一)": "R",
|
|
92
|
+
"C. 优秀(硬性要求二)": "E",
|
|
93
|
+
"D. 边界与决策": "D",
|
|
94
|
+
"E. 流程与契约": "P",
|
|
95
|
+
"F. 经济性与自治": "C",
|
|
96
|
+
"G. 资产与演进": "K",
|
|
97
|
+
"H. 安全与合规底线": "S",
|
|
98
|
+
"I. 治理与证据": "G",
|
|
99
|
+
"J. 范式级验收判据(宪法 11)": "J"
|
|
100
|
+
},
|
|
101
|
+
"sections": [
|
|
102
|
+
"A. 规范地位与适用性",
|
|
103
|
+
"B. 可靠(硬性要求一)",
|
|
104
|
+
"C. 优秀(硬性要求二)",
|
|
105
|
+
"D. 边界与决策",
|
|
106
|
+
"E. 流程与契约",
|
|
107
|
+
"F. 经济性与自治",
|
|
108
|
+
"G. 资产与演进",
|
|
109
|
+
"H. 安全与合规底线",
|
|
110
|
+
"I. 治理与证据",
|
|
111
|
+
"J. 范式级验收判据(宪法 11)"
|
|
112
|
+
],
|
|
113
|
+
"final_section": "最终判定"
|
|
114
|
+
},
|
|
115
|
+
"contract_template": {
|
|
116
|
+
"required_sections": [
|
|
117
|
+
"C1 背景与意图",
|
|
118
|
+
"C2 范围与非目标",
|
|
119
|
+
"C3 约束与底线",
|
|
120
|
+
"C4 可靠标准",
|
|
121
|
+
"C5 优秀标准(五个维度,逐条可判定)",
|
|
122
|
+
"C6 多视角审视要求",
|
|
123
|
+
"C7 自治预算",
|
|
124
|
+
"C8 交付物与证据清单",
|
|
125
|
+
"C9 验收方式",
|
|
126
|
+
"C10 变更、终止与失败处置"
|
|
127
|
+
]
|
|
128
|
+
},
|
|
129
|
+
"decision_request_template": {
|
|
130
|
+
"required_sections": [
|
|
131
|
+
"Part A 决策请求",
|
|
132
|
+
"A0 元信息",
|
|
133
|
+
"A1 必要性测试(必填,不通过则不得发送)",
|
|
134
|
+
"A2 背景摘要",
|
|
135
|
+
"A3 实质选项",
|
|
136
|
+
"A4 推荐与理由",
|
|
137
|
+
"A5 默认选择与逾期处置",
|
|
138
|
+
"A6 决策影响",
|
|
139
|
+
"A7 响应窗口",
|
|
140
|
+
"A8 人的决定与留痕",
|
|
141
|
+
"Part B 必要信息补全请求(仅限 P0/P1)",
|
|
142
|
+
"B0 前置校验(不通过则不得发送)",
|
|
143
|
+
"B1 请求内容",
|
|
144
|
+
"B2 人的答复与留痕",
|
|
145
|
+
"发送前自检"
|
|
146
|
+
]
|
|
147
|
+
},
|
|
148
|
+
"delivery_report_template": {
|
|
149
|
+
"required_sections": [
|
|
150
|
+
"1. 元信息",
|
|
151
|
+
"2. 交付单元清单",
|
|
152
|
+
"3. 契约对照总表",
|
|
153
|
+
"4. 可靠证据",
|
|
154
|
+
"5. 优秀证据(五个维度逐项)",
|
|
155
|
+
"6. 多视角审视与缺陷",
|
|
156
|
+
"7. 符合性自检结果",
|
|
157
|
+
"8. 资产沉淀",
|
|
158
|
+
"9. 成本台账摘要",
|
|
159
|
+
"10. 安全与合规",
|
|
160
|
+
"11. 已知边界与未完成说明",
|
|
161
|
+
"12. 验收建议(仅自查结论)",
|
|
162
|
+
"13. 签署与验收",
|
|
163
|
+
"附:追溯矩阵"
|
|
164
|
+
]
|
|
165
|
+
}
|
|
166
|
+
}
|