@topmindspace/tms-skills 2.0.1 → 2.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 +145 -134
- package/README.md +1 -1
- package/bin/tms-skills.js +160 -160
- package/package.json +48 -48
- package/top-ppt-html/README.md +254 -254
- package/top-ppt-html/SKILL.md +111 -111
- package/top-ppt-html/assets/examples/2026-09-09-architecture-graphite-dark.html +3926 -3926
- package/top-ppt-html/assets/examples/2026-09-09-architecture-graphite-dark.model.json +167 -167
- package/top-ppt-html/assets/examples/2026-09-09-architecture-spectrum.html +3926 -3926
- package/top-ppt-html/assets/examples/2026-09-09-architecture-spectrum.model.json +167 -167
- package/top-ppt-html/assets/examples/2026-09-09-presentation-apple-mono.html +4325 -4325
- package/top-ppt-html/assets/examples/2026-09-09-presentation-apple-mono.model.json +320 -320
- package/top-ppt-html/assets/examples/2026-09-09-presentation-brand-red.html +4325 -4325
- package/top-ppt-html/assets/examples/2026-09-09-presentation-brand-red.model.json +320 -320
- package/top-ppt-html/assets/examples/2026-09-09-presentation-business-blue.html +4325 -4325
- package/top-ppt-html/assets/examples/2026-09-09-presentation-business-blue.model.json +320 -320
- package/top-ppt-html/assets/examples/2026-09-09-research-deep-teal.html +5527 -5527
- package/top-ppt-html/assets/examples/2026-09-09-research-deep-teal.model.json +913 -913
- package/top-ppt-html/assets/examples/2026-09-09-research-indigo-violet.html +5527 -5527
- package/top-ppt-html/assets/examples/2026-09-09-research-indigo-violet.model.json +913 -913
- package/top-ppt-html/assets/examples/2026-09-09-research-mckinsey.html +5527 -5527
- package/top-ppt-html/assets/examples/2026-09-09-research-mckinsey.model.json +913 -913
- package/top-ppt-html/assets/examples/2026-09-09-research-warm-sand.html +5527 -5527
- package/top-ppt-html/assets/examples/2026-09-09-research-warm-sand.model.json +913 -913
- package/top-ppt-html/assets/pptx-export.js +1944 -1944
- package/top-ppt-html/assets/style-gallery.html +589 -589
- package/top-ppt-html/assets/templates/architecture.html +3728 -3728
- package/top-ppt-html/assets/templates/engine.css +840 -840
- package/top-ppt-html/assets/templates/presentation.html +3738 -3738
- package/top-ppt-html/assets/templates/research.html +4017 -4017
- package/top-ppt-html/assets/templates/ui.js +520 -520
- package/top-ppt-html/evals/prompts.csv +15 -15
- package/top-ppt-html/evals/rubric.schema.json +25 -25
- package/top-ppt-html/evals/run_evals.py +220 -220
- package/top-ppt-html/evals/trace.example.json +16 -16
- package/top-ppt-html/package-lock.json +207 -0
- package/top-ppt-html/package.json +35 -35
- package/top-ppt-html/references/charts-basic.md +624 -624
- package/top-ppt-html/references/charts-discipline.md +108 -108
- package/top-ppt-html/references/charts-extended.md +482 -482
- package/top-ppt-html/references/charts.md +28 -28
- package/top-ppt-html/references/components-atoms.md +624 -624
- package/top-ppt-html/references/components.md +30 -30
- package/top-ppt-html/references/content-rules.md +490 -490
- package/top-ppt-html/references/design-system-engine.md +235 -235
- package/top-ppt-html/references/design-system.md +478 -478
- package/top-ppt-html/references/failure-modes.md +214 -214
- package/top-ppt-html/references/high-fidelity.md +127 -127
- package/top-ppt-html/references/icons.md +397 -397
- package/top-ppt-html/references/industry-benchmark.md +105 -105
- package/top-ppt-html/references/infographics-stats.md +308 -308
- package/top-ppt-html/references/infographics-structure.md +226 -226
- package/top-ppt-html/references/infographics.md +43 -43
- package/top-ppt-html/references/layout-grammar.md +315 -315
- package/top-ppt-html/references/layouts-architecture.md +108 -108
- package/top-ppt-html/references/layouts-combo.md +600 -600
- package/top-ppt-html/references/layouts-research.md +160 -160
- package/top-ppt-html/references/modes.md +254 -254
- package/top-ppt-html/references/outline-design.md +275 -275
- package/top-ppt-html/references/playbook.md +266 -266
- package/top-ppt-html/references/pptx-export.md +209 -209
- package/top-ppt-html/references/reform-plan.md +252 -252
- package/top-ppt-html/references/styles.md +370 -370
- package/top-ppt-html/references/tech-design.md +138 -138
- package/top-ppt-html/scripts/audit_css.py +109 -109
- package/top-ppt-html/scripts/audit_docs.py +176 -176
- package/top-ppt-html/scripts/audit_skill.py +220 -220
- package/top-ppt-html/scripts/audit_styles.py +351 -351
- package/top-ppt-html/scripts/build_examples.py +2276 -2276
- package/top-ppt-html/scripts/build_pptx.js +2380 -2380
- package/top-ppt-html/scripts/capture_theme_overview.js +78 -78
- package/top-ppt-html/scripts/checks_html.py +127 -127
- package/top-ppt-html/scripts/cross_verify.py +294 -294
- package/top-ppt-html/scripts/env_probe.py +158 -158
- package/top-ppt-html/scripts/extract_model.py +210 -210
- package/top-ppt-html/scripts/extract_snippet.py +374 -374
- package/top-ppt-html/scripts/gen_channel_a.js +214 -214
- package/top-ppt-html/scripts/layout-constants.json +3377 -3377
- package/top-ppt-html/scripts/layout_slots.json +830 -830
- package/top-ppt-html/scripts/lib_layout_regions.js +412 -412
- package/top-ppt-html/scripts/measure_height.py +178 -178
- package/top-ppt-html/scripts/model-schema.json +547 -547
- package/top-ppt-html/scripts/negative_tests.py +307 -307
- package/top-ppt-html/scripts/package_skill.py +291 -291
- package/top-ppt-html/scripts/prepare_images.py +341 -341
- package/top-ppt-html/scripts/probe_image_export.py +188 -188
- package/top-ppt-html/scripts/quality_gate.py +301 -301
- package/top-ppt-html/scripts/regression.py +308 -308
- package/top-ppt-html/scripts/render_compare.py +275 -275
- package/top-ppt-html/scripts/render_from_model.py +698 -698
- package/top-ppt-html/scripts/scaffold_report.py +1054 -1054
- package/top-ppt-html/scripts/section-file-map.json +104 -104
- package/top-ppt-html/scripts/sync_runtime.py +662 -662
- package/top-ppt-html/scripts/validate_pptx.py +1510 -1510
- package/top-ppt-html/scripts/validate_report.py +1456 -1456
|
@@ -1,176 +1,176 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
# -*- coding: utf-8 -*-
|
|
3
|
-
"""TopPPT HTML · 文档一致性审计(规范与引用完整性 · 零依赖)
|
|
4
|
-
|
|
5
|
-
用法:
|
|
6
|
-
python scripts/audit_docs.py # 全量审计
|
|
7
|
-
python scripts/audit_docs.py --quiet # 仅打印结论
|
|
8
|
-
|
|
9
|
-
审计项(任一不过 → 退出码 1):
|
|
10
|
-
① § 引用可解析性 —— references 采用**跨文件唯一稳定编号**(§1–§77),
|
|
11
|
-
所有 `§数字` 引用必须能在三份组件/图表规范中解析到章节
|
|
12
|
-
(白名单:`design-system.md` 自有子节 §1a–§1f / §9b–§9d)
|
|
13
|
-
② 文件前缀规范性 —— 引用组件/图表规范时须带正确文件名,且不出现
|
|
14
|
-
重复前缀(`` `charts.md` charts.md §N ``)或裸前缀(`charts.md §N` 未加反引号)
|
|
15
|
-
③ §46 选型表覆盖度 —— 36 个登记图表类型与 29 个页型都必须在选型表出现
|
|
16
|
-
④ 图表代码节齐备 —— 每个登记图表类型都要在 charts.md / infographics.md 有 `data-chart` 代码节
|
|
17
|
-
⑤ 篇数与必需文件 —— references 篇数、SKILL.md frontmatter、进包必需文件齐全
|
|
18
|
-
⑥ 任务路由可解析性 —— extract_snippet.py 的 TASK_ROUTES 每条 (文件, 节) 必须可
|
|
19
|
-
实际抽取(防「文字声明同源」漂移:路由声明的节在文件里找不到即失败;
|
|
20
|
-
空节路由 = 该文件只能整读,≥20KB 的 L2 文件不允许)
|
|
21
|
-
|
|
22
|
-
何时跑:改 `references/*`、拆分/新增规范文件、调整选型表后(与 regression.py 互补——
|
|
23
|
-
后者管交付物正确性,本工具管规范自洽性)。
|
|
24
|
-
"""
|
|
25
|
-
from __future__ import annotations
|
|
26
|
-
|
|
27
|
-
import json
|
|
28
|
-
import re
|
|
29
|
-
import sys
|
|
30
|
-
from pathlib import Path
|
|
31
|
-
|
|
32
|
-
try:
|
|
33
|
-
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
|
|
34
|
-
except Exception:
|
|
35
|
-
pass
|
|
36
|
-
|
|
37
|
-
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
38
|
-
from package_skill import DESC_LIMIT, NAME, REQUIRED # noqa: E402 (复用进包清单,避免两处维护)
|
|
39
|
-
import extract_snippet as ES # noqa: E402 (⑥ 任务路由审计复用同一解析器,避免第三源)
|
|
40
|
-
|
|
41
|
-
ROOT = Path(__file__).resolve().parent.parent
|
|
42
|
-
REF = ROOT / 'references'
|
|
43
|
-
QUIET = '--quiet' in sys.argv[1:]
|
|
44
|
-
|
|
45
|
-
# 组件/图表族物理文件(components.md / charts.md 为逻辑索引;§编号跨文件稳定)
|
|
46
|
-
COMPONENT_FILES = (
|
|
47
|
-
'components.md', 'components-atoms.md', 'layouts-research.md',
|
|
48
|
-
'layouts-architecture.md', 'layouts-combo.md',
|
|
49
|
-
)
|
|
50
|
-
CHART_FILES = (
|
|
51
|
-
'charts.md', 'charts-basic.md', 'charts-extended.md', 'charts-discipline.md',
|
|
52
|
-
)
|
|
53
|
-
OWNER_SRC = tuple(
|
|
54
|
-
[(fn, r'^## (\d+[a-z]?)\. ') for fn in COMPONENT_FILES + CHART_FILES]
|
|
55
|
-
+ [('infographics.md', r'^### (7[1-9]|8\d)\.? '),
|
|
56
|
-
('infographics-stats.md', r'^### (?:§)?(\d+[a-z]?)\.?\s'),
|
|
57
|
-
('infographics-structure.md', r'^### (?:§)?(\d+[a-z]?)\.?\s')]
|
|
58
|
-
)
|
|
59
|
-
# design-system.md 自有子节编号(不算悬空)
|
|
60
|
-
DS_OWN = {'1a', '1b', '1c', '1d', '1e', '1f', '9b', '9c', '9d'}
|
|
61
|
-
DUP_PRE = re.compile(r'`(components|charts|infographics)\.md`\s*[((]?\s*`?\1\.md')
|
|
62
|
-
BARE_PRE = re.compile(r'(?<![`\w])(components|charts|infographics)\.md\s*§')
|
|
63
|
-
REF_ID = re.compile(r'§\s*(\d+[a-zA-Z]?)') # 节号后缀大小写均收录(§38b / §46C 一并受审计)
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
def doc_files():
|
|
67
|
-
files = ['SKILL.md', 'README.md']
|
|
68
|
-
files += ['references/%s' % p.name for p in sorted(REF.glob('*.md'))]
|
|
69
|
-
return files
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
def main() -> int:
|
|
73
|
-
errs: list[str] = []
|
|
74
|
-
lc = json.loads((ROOT / 'scripts' / 'layout-constants.json').read_text(encoding='utf-8'))
|
|
75
|
-
ms = json.loads((ROOT / 'scripts' / 'model-schema.json').read_text(encoding='utf-8'))
|
|
76
|
-
|
|
77
|
-
owner: dict[str, str] = {}
|
|
78
|
-
for f, pat in OWNER_SRC:
|
|
79
|
-
p = REF / f
|
|
80
|
-
if not p.exists():
|
|
81
|
-
continue
|
|
82
|
-
for m in re.finditer(pat, p.read_text(encoding='utf-8'), re.M):
|
|
83
|
-
owner[m.group(1)] = f
|
|
84
|
-
|
|
85
|
-
# ① § 引用可解析性
|
|
86
|
-
dangling = 0
|
|
87
|
-
for rel in doc_files():
|
|
88
|
-
for i, ln in enumerate((ROOT / rel).read_text(encoding='utf-8').splitlines()):
|
|
89
|
-
for m in REF_ID.finditer(ln):
|
|
90
|
-
i_d = m.group(1)
|
|
91
|
-
if i_d in owner or i_d in DS_OWN:
|
|
92
|
-
continue
|
|
93
|
-
dangling += 1
|
|
94
|
-
errs.append(f'悬空引用 {rel} L{i+1}: §{i_d}')
|
|
95
|
-
print(f'① § 引用可解析性:悬空 {dangling} 处(编号空间 {len(owner)} 项)')
|
|
96
|
-
|
|
97
|
-
# ② 文件前缀规范性
|
|
98
|
-
prefix_bad = 0
|
|
99
|
-
for rel in doc_files():
|
|
100
|
-
for i, ln in enumerate((ROOT / rel).read_text(encoding='utf-8').splitlines()):
|
|
101
|
-
if DUP_PRE.search(ln):
|
|
102
|
-
prefix_bad += 1
|
|
103
|
-
errs.append(f'重复文件前缀 {rel} L{i+1}')
|
|
104
|
-
if BARE_PRE.search(ln):
|
|
105
|
-
prefix_bad += 1
|
|
106
|
-
errs.append(f'裸文件前缀 {rel} L{i+1}')
|
|
107
|
-
print(f'② 文件前缀规范性:异常 {prefix_bad} 处')
|
|
108
|
-
|
|
109
|
-
# ③ §46 选型表覆盖度(物理文件 layouts-combo.md;逻辑名 components.md 亦可)
|
|
110
|
-
combo = REF / 'layouts-combo.md'
|
|
111
|
-
if not combo.exists():
|
|
112
|
-
combo = REF / 'components.md'
|
|
113
|
-
comp = combo.read_text(encoding='utf-8')
|
|
114
|
-
sec46 = re.search(r'## 46\. .*?\n(.*?)\n## 46b\.', comp, re.S)
|
|
115
|
-
table = sec46.group(1) if sec46 else ''
|
|
116
|
-
chart_types = lc['charts']['types']
|
|
117
|
-
page_types = [k for k in ms['pageTypes'] if not k.startswith('$')]
|
|
118
|
-
miss_chart = [t for t in chart_types if t not in table]
|
|
119
|
-
miss_page = [t for t in page_types if ('`%s`' % t) not in table]
|
|
120
|
-
if miss_chart:
|
|
121
|
-
errs.append(f'§46 未覆盖图表类型:{miss_chart}')
|
|
122
|
-
if miss_page:
|
|
123
|
-
errs.append(f'§46 未覆盖页型:{miss_page}')
|
|
124
|
-
print(f'③ §46 选型表覆盖:图表 {len(chart_types) - len(miss_chart)}/{len(chart_types)} · '
|
|
125
|
-
f'页型 {len(page_types) - len(miss_page)}/{len(page_types)}')
|
|
126
|
-
|
|
127
|
-
# ④ 图表代码节齐备(拆分后的 charts-* + infographics)
|
|
128
|
-
charts_txt = ''
|
|
129
|
-
for fn in CHART_FILES + ('infographics.md', 'infographics-stats.md', 'infographics-structure.md'):
|
|
130
|
-
p = REF / fn
|
|
131
|
-
if p.exists():
|
|
132
|
-
charts_txt += p.read_text(encoding='utf-8')
|
|
133
|
-
no_code = [t for t in chart_types
|
|
134
|
-
if not re.search(r'data-chart="%s"' % re.escape(t), charts_txt)]
|
|
135
|
-
if no_code:
|
|
136
|
-
errs.append(f'缺代码节的图表类型:{no_code}')
|
|
137
|
-
print(f'④ 图表代码节:{len(chart_types) - len(no_code)}/{len(chart_types)} 齐备')
|
|
138
|
-
|
|
139
|
-
# ⑤ 篇数与必需文件
|
|
140
|
-
n_ref = len(list(REF.glob('*.md')))
|
|
141
|
-
sk = (ROOT / 'SKILL.md').read_text(encoding='utf-8')
|
|
142
|
-
d = re.search(r'^description:\s*"(.*)"\s*$', sk, re.M | re.S)
|
|
143
|
-
dlen = len(d.group(1)) if d else -1
|
|
144
|
-
nm = re.search(r'^name:\s*(\S+)\s*$', sk, re.M)
|
|
145
|
-
missing = [r for r in REQUIRED if not (ROOT / r).exists()]
|
|
146
|
-
if dlen < 0 or dlen > DESC_LIMIT:
|
|
147
|
-
errs.append(f'SKILL.md description 长度异常:{dlen}(上限 {DESC_LIMIT})')
|
|
148
|
-
if not nm or nm.group(1) != NAME:
|
|
149
|
-
errs.append(f'SKILL.md name 与技能名 {NAME} 不一致')
|
|
150
|
-
if missing:
|
|
151
|
-
errs.append(f'缺必需文件:{missing}')
|
|
152
|
-
print(f'⑤ 规范篇数 {n_ref} · description {dlen} 字符 · 必需文件缺 {len(missing)} 项')
|
|
153
|
-
|
|
154
|
-
# ⑥ 任务路由可解析性(--task 与 validate_report FIX_GUIDE 的修复链路依赖它;
|
|
155
|
-
# 实现与 regression ⑦-b 共用 ES.verify_routes,防两处维护)
|
|
156
|
-
route_bad = ES.verify_routes()
|
|
157
|
-
unresolved = [x for x in route_bad if not x.endswith('空路由')]
|
|
158
|
-
empty_routes = [x for x in route_bad if x.endswith('空路由')]
|
|
159
|
-
if unresolved:
|
|
160
|
-
errs.append(f'任务路由不可解析(--task/FIX_GUIDE 修复链路断裂):{unresolved}')
|
|
161
|
-
if empty_routes:
|
|
162
|
-
errs.append(f'任务路由空节(整读大文件,应补节号或标题关键词):{empty_routes}')
|
|
163
|
-
print(f'⑥ 任务路由可解析性:{len(ES.TASK_ROUTES)} 条路由 · 不可解析 {len(unresolved)} · 空节 {len(empty_routes)}')
|
|
164
|
-
|
|
165
|
-
print()
|
|
166
|
-
if errs:
|
|
167
|
-
print('审计未通过:')
|
|
168
|
-
for e in errs:
|
|
169
|
-
print(f' ✗ {e}')
|
|
170
|
-
return 1
|
|
171
|
-
print('文档一致性审计通过:引用可解析 · 前缀规范 · 选型表全覆盖 · 代码节齐备 · 元数据合规')
|
|
172
|
-
return 0
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
if __name__ == '__main__':
|
|
176
|
-
sys.exit(main())
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""TopPPT HTML · 文档一致性审计(规范与引用完整性 · 零依赖)
|
|
4
|
+
|
|
5
|
+
用法:
|
|
6
|
+
python scripts/audit_docs.py # 全量审计
|
|
7
|
+
python scripts/audit_docs.py --quiet # 仅打印结论
|
|
8
|
+
|
|
9
|
+
审计项(任一不过 → 退出码 1):
|
|
10
|
+
① § 引用可解析性 —— references 采用**跨文件唯一稳定编号**(§1–§77),
|
|
11
|
+
所有 `§数字` 引用必须能在三份组件/图表规范中解析到章节
|
|
12
|
+
(白名单:`design-system.md` 自有子节 §1a–§1f / §9b–§9d)
|
|
13
|
+
② 文件前缀规范性 —— 引用组件/图表规范时须带正确文件名,且不出现
|
|
14
|
+
重复前缀(`` `charts.md` charts.md §N ``)或裸前缀(`charts.md §N` 未加反引号)
|
|
15
|
+
③ §46 选型表覆盖度 —— 36 个登记图表类型与 29 个页型都必须在选型表出现
|
|
16
|
+
④ 图表代码节齐备 —— 每个登记图表类型都要在 charts.md / infographics.md 有 `data-chart` 代码节
|
|
17
|
+
⑤ 篇数与必需文件 —— references 篇数、SKILL.md frontmatter、进包必需文件齐全
|
|
18
|
+
⑥ 任务路由可解析性 —— extract_snippet.py 的 TASK_ROUTES 每条 (文件, 节) 必须可
|
|
19
|
+
实际抽取(防「文字声明同源」漂移:路由声明的节在文件里找不到即失败;
|
|
20
|
+
空节路由 = 该文件只能整读,≥20KB 的 L2 文件不允许)
|
|
21
|
+
|
|
22
|
+
何时跑:改 `references/*`、拆分/新增规范文件、调整选型表后(与 regression.py 互补——
|
|
23
|
+
后者管交付物正确性,本工具管规范自洽性)。
|
|
24
|
+
"""
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import json
|
|
28
|
+
import re
|
|
29
|
+
import sys
|
|
30
|
+
from pathlib import Path
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
|
|
34
|
+
except Exception:
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
38
|
+
from package_skill import DESC_LIMIT, NAME, REQUIRED # noqa: E402 (复用进包清单,避免两处维护)
|
|
39
|
+
import extract_snippet as ES # noqa: E402 (⑥ 任务路由审计复用同一解析器,避免第三源)
|
|
40
|
+
|
|
41
|
+
ROOT = Path(__file__).resolve().parent.parent
|
|
42
|
+
REF = ROOT / 'references'
|
|
43
|
+
QUIET = '--quiet' in sys.argv[1:]
|
|
44
|
+
|
|
45
|
+
# 组件/图表族物理文件(components.md / charts.md 为逻辑索引;§编号跨文件稳定)
|
|
46
|
+
COMPONENT_FILES = (
|
|
47
|
+
'components.md', 'components-atoms.md', 'layouts-research.md',
|
|
48
|
+
'layouts-architecture.md', 'layouts-combo.md',
|
|
49
|
+
)
|
|
50
|
+
CHART_FILES = (
|
|
51
|
+
'charts.md', 'charts-basic.md', 'charts-extended.md', 'charts-discipline.md',
|
|
52
|
+
)
|
|
53
|
+
OWNER_SRC = tuple(
|
|
54
|
+
[(fn, r'^## (\d+[a-z]?)\. ') for fn in COMPONENT_FILES + CHART_FILES]
|
|
55
|
+
+ [('infographics.md', r'^### (7[1-9]|8\d)\.? '),
|
|
56
|
+
('infographics-stats.md', r'^### (?:§)?(\d+[a-z]?)\.?\s'),
|
|
57
|
+
('infographics-structure.md', r'^### (?:§)?(\d+[a-z]?)\.?\s')]
|
|
58
|
+
)
|
|
59
|
+
# design-system.md 自有子节编号(不算悬空)
|
|
60
|
+
DS_OWN = {'1a', '1b', '1c', '1d', '1e', '1f', '9b', '9c', '9d'}
|
|
61
|
+
DUP_PRE = re.compile(r'`(components|charts|infographics)\.md`\s*[((]?\s*`?\1\.md')
|
|
62
|
+
BARE_PRE = re.compile(r'(?<![`\w])(components|charts|infographics)\.md\s*§')
|
|
63
|
+
REF_ID = re.compile(r'§\s*(\d+[a-zA-Z]?)') # 节号后缀大小写均收录(§38b / §46C 一并受审计)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def doc_files():
|
|
67
|
+
files = ['SKILL.md', 'README.md']
|
|
68
|
+
files += ['references/%s' % p.name for p in sorted(REF.glob('*.md'))]
|
|
69
|
+
return files
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def main() -> int:
|
|
73
|
+
errs: list[str] = []
|
|
74
|
+
lc = json.loads((ROOT / 'scripts' / 'layout-constants.json').read_text(encoding='utf-8'))
|
|
75
|
+
ms = json.loads((ROOT / 'scripts' / 'model-schema.json').read_text(encoding='utf-8'))
|
|
76
|
+
|
|
77
|
+
owner: dict[str, str] = {}
|
|
78
|
+
for f, pat in OWNER_SRC:
|
|
79
|
+
p = REF / f
|
|
80
|
+
if not p.exists():
|
|
81
|
+
continue
|
|
82
|
+
for m in re.finditer(pat, p.read_text(encoding='utf-8'), re.M):
|
|
83
|
+
owner[m.group(1)] = f
|
|
84
|
+
|
|
85
|
+
# ① § 引用可解析性
|
|
86
|
+
dangling = 0
|
|
87
|
+
for rel in doc_files():
|
|
88
|
+
for i, ln in enumerate((ROOT / rel).read_text(encoding='utf-8').splitlines()):
|
|
89
|
+
for m in REF_ID.finditer(ln):
|
|
90
|
+
i_d = m.group(1)
|
|
91
|
+
if i_d in owner or i_d in DS_OWN:
|
|
92
|
+
continue
|
|
93
|
+
dangling += 1
|
|
94
|
+
errs.append(f'悬空引用 {rel} L{i+1}: §{i_d}')
|
|
95
|
+
print(f'① § 引用可解析性:悬空 {dangling} 处(编号空间 {len(owner)} 项)')
|
|
96
|
+
|
|
97
|
+
# ② 文件前缀规范性
|
|
98
|
+
prefix_bad = 0
|
|
99
|
+
for rel in doc_files():
|
|
100
|
+
for i, ln in enumerate((ROOT / rel).read_text(encoding='utf-8').splitlines()):
|
|
101
|
+
if DUP_PRE.search(ln):
|
|
102
|
+
prefix_bad += 1
|
|
103
|
+
errs.append(f'重复文件前缀 {rel} L{i+1}')
|
|
104
|
+
if BARE_PRE.search(ln):
|
|
105
|
+
prefix_bad += 1
|
|
106
|
+
errs.append(f'裸文件前缀 {rel} L{i+1}')
|
|
107
|
+
print(f'② 文件前缀规范性:异常 {prefix_bad} 处')
|
|
108
|
+
|
|
109
|
+
# ③ §46 选型表覆盖度(物理文件 layouts-combo.md;逻辑名 components.md 亦可)
|
|
110
|
+
combo = REF / 'layouts-combo.md'
|
|
111
|
+
if not combo.exists():
|
|
112
|
+
combo = REF / 'components.md'
|
|
113
|
+
comp = combo.read_text(encoding='utf-8')
|
|
114
|
+
sec46 = re.search(r'## 46\. .*?\n(.*?)\n## 46b\.', comp, re.S)
|
|
115
|
+
table = sec46.group(1) if sec46 else ''
|
|
116
|
+
chart_types = lc['charts']['types']
|
|
117
|
+
page_types = [k for k in ms['pageTypes'] if not k.startswith('$')]
|
|
118
|
+
miss_chart = [t for t in chart_types if t not in table]
|
|
119
|
+
miss_page = [t for t in page_types if ('`%s`' % t) not in table]
|
|
120
|
+
if miss_chart:
|
|
121
|
+
errs.append(f'§46 未覆盖图表类型:{miss_chart}')
|
|
122
|
+
if miss_page:
|
|
123
|
+
errs.append(f'§46 未覆盖页型:{miss_page}')
|
|
124
|
+
print(f'③ §46 选型表覆盖:图表 {len(chart_types) - len(miss_chart)}/{len(chart_types)} · '
|
|
125
|
+
f'页型 {len(page_types) - len(miss_page)}/{len(page_types)}')
|
|
126
|
+
|
|
127
|
+
# ④ 图表代码节齐备(拆分后的 charts-* + infographics)
|
|
128
|
+
charts_txt = ''
|
|
129
|
+
for fn in CHART_FILES + ('infographics.md', 'infographics-stats.md', 'infographics-structure.md'):
|
|
130
|
+
p = REF / fn
|
|
131
|
+
if p.exists():
|
|
132
|
+
charts_txt += p.read_text(encoding='utf-8')
|
|
133
|
+
no_code = [t for t in chart_types
|
|
134
|
+
if not re.search(r'data-chart="%s"' % re.escape(t), charts_txt)]
|
|
135
|
+
if no_code:
|
|
136
|
+
errs.append(f'缺代码节的图表类型:{no_code}')
|
|
137
|
+
print(f'④ 图表代码节:{len(chart_types) - len(no_code)}/{len(chart_types)} 齐备')
|
|
138
|
+
|
|
139
|
+
# ⑤ 篇数与必需文件
|
|
140
|
+
n_ref = len(list(REF.glob('*.md')))
|
|
141
|
+
sk = (ROOT / 'SKILL.md').read_text(encoding='utf-8')
|
|
142
|
+
d = re.search(r'^description:\s*"(.*)"\s*$', sk, re.M | re.S)
|
|
143
|
+
dlen = len(d.group(1)) if d else -1
|
|
144
|
+
nm = re.search(r'^name:\s*(\S+)\s*$', sk, re.M)
|
|
145
|
+
missing = [r for r in REQUIRED if not (ROOT / r).exists()]
|
|
146
|
+
if dlen < 0 or dlen > DESC_LIMIT:
|
|
147
|
+
errs.append(f'SKILL.md description 长度异常:{dlen}(上限 {DESC_LIMIT})')
|
|
148
|
+
if not nm or nm.group(1) != NAME:
|
|
149
|
+
errs.append(f'SKILL.md name 与技能名 {NAME} 不一致')
|
|
150
|
+
if missing:
|
|
151
|
+
errs.append(f'缺必需文件:{missing}')
|
|
152
|
+
print(f'⑤ 规范篇数 {n_ref} · description {dlen} 字符 · 必需文件缺 {len(missing)} 项')
|
|
153
|
+
|
|
154
|
+
# ⑥ 任务路由可解析性(--task 与 validate_report FIX_GUIDE 的修复链路依赖它;
|
|
155
|
+
# 实现与 regression ⑦-b 共用 ES.verify_routes,防两处维护)
|
|
156
|
+
route_bad = ES.verify_routes()
|
|
157
|
+
unresolved = [x for x in route_bad if not x.endswith('空路由')]
|
|
158
|
+
empty_routes = [x for x in route_bad if x.endswith('空路由')]
|
|
159
|
+
if unresolved:
|
|
160
|
+
errs.append(f'任务路由不可解析(--task/FIX_GUIDE 修复链路断裂):{unresolved}')
|
|
161
|
+
if empty_routes:
|
|
162
|
+
errs.append(f'任务路由空节(整读大文件,应补节号或标题关键词):{empty_routes}')
|
|
163
|
+
print(f'⑥ 任务路由可解析性:{len(ES.TASK_ROUTES)} 条路由 · 不可解析 {len(unresolved)} · 空节 {len(empty_routes)}')
|
|
164
|
+
|
|
165
|
+
print()
|
|
166
|
+
if errs:
|
|
167
|
+
print('审计未通过:')
|
|
168
|
+
for e in errs:
|
|
169
|
+
print(f' ✗ {e}')
|
|
170
|
+
return 1
|
|
171
|
+
print('文档一致性审计通过:引用可解析 · 前缀规范 · 选型表全覆盖 · 代码节齐备 · 元数据合规')
|
|
172
|
+
return 0
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
if __name__ == '__main__':
|
|
176
|
+
sys.exit(main())
|