@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.
Files changed (95) hide show
  1. package/CHANGELOG.md +145 -134
  2. package/README.md +1 -1
  3. package/bin/tms-skills.js +160 -160
  4. package/package.json +48 -48
  5. package/top-ppt-html/README.md +254 -254
  6. package/top-ppt-html/SKILL.md +111 -111
  7. package/top-ppt-html/assets/examples/2026-09-09-architecture-graphite-dark.html +3926 -3926
  8. package/top-ppt-html/assets/examples/2026-09-09-architecture-graphite-dark.model.json +167 -167
  9. package/top-ppt-html/assets/examples/2026-09-09-architecture-spectrum.html +3926 -3926
  10. package/top-ppt-html/assets/examples/2026-09-09-architecture-spectrum.model.json +167 -167
  11. package/top-ppt-html/assets/examples/2026-09-09-presentation-apple-mono.html +4325 -4325
  12. package/top-ppt-html/assets/examples/2026-09-09-presentation-apple-mono.model.json +320 -320
  13. package/top-ppt-html/assets/examples/2026-09-09-presentation-brand-red.html +4325 -4325
  14. package/top-ppt-html/assets/examples/2026-09-09-presentation-brand-red.model.json +320 -320
  15. package/top-ppt-html/assets/examples/2026-09-09-presentation-business-blue.html +4325 -4325
  16. package/top-ppt-html/assets/examples/2026-09-09-presentation-business-blue.model.json +320 -320
  17. package/top-ppt-html/assets/examples/2026-09-09-research-deep-teal.html +5527 -5527
  18. package/top-ppt-html/assets/examples/2026-09-09-research-deep-teal.model.json +913 -913
  19. package/top-ppt-html/assets/examples/2026-09-09-research-indigo-violet.html +5527 -5527
  20. package/top-ppt-html/assets/examples/2026-09-09-research-indigo-violet.model.json +913 -913
  21. package/top-ppt-html/assets/examples/2026-09-09-research-mckinsey.html +5527 -5527
  22. package/top-ppt-html/assets/examples/2026-09-09-research-mckinsey.model.json +913 -913
  23. package/top-ppt-html/assets/examples/2026-09-09-research-warm-sand.html +5527 -5527
  24. package/top-ppt-html/assets/examples/2026-09-09-research-warm-sand.model.json +913 -913
  25. package/top-ppt-html/assets/pptx-export.js +1944 -1944
  26. package/top-ppt-html/assets/style-gallery.html +589 -589
  27. package/top-ppt-html/assets/templates/architecture.html +3728 -3728
  28. package/top-ppt-html/assets/templates/engine.css +840 -840
  29. package/top-ppt-html/assets/templates/presentation.html +3738 -3738
  30. package/top-ppt-html/assets/templates/research.html +4017 -4017
  31. package/top-ppt-html/assets/templates/ui.js +520 -520
  32. package/top-ppt-html/evals/prompts.csv +15 -15
  33. package/top-ppt-html/evals/rubric.schema.json +25 -25
  34. package/top-ppt-html/evals/run_evals.py +220 -220
  35. package/top-ppt-html/evals/trace.example.json +16 -16
  36. package/top-ppt-html/package-lock.json +207 -0
  37. package/top-ppt-html/package.json +35 -35
  38. package/top-ppt-html/references/charts-basic.md +624 -624
  39. package/top-ppt-html/references/charts-discipline.md +108 -108
  40. package/top-ppt-html/references/charts-extended.md +482 -482
  41. package/top-ppt-html/references/charts.md +28 -28
  42. package/top-ppt-html/references/components-atoms.md +624 -624
  43. package/top-ppt-html/references/components.md +30 -30
  44. package/top-ppt-html/references/content-rules.md +490 -490
  45. package/top-ppt-html/references/design-system-engine.md +235 -235
  46. package/top-ppt-html/references/design-system.md +478 -478
  47. package/top-ppt-html/references/failure-modes.md +214 -214
  48. package/top-ppt-html/references/high-fidelity.md +127 -127
  49. package/top-ppt-html/references/icons.md +397 -397
  50. package/top-ppt-html/references/industry-benchmark.md +105 -105
  51. package/top-ppt-html/references/infographics-stats.md +308 -308
  52. package/top-ppt-html/references/infographics-structure.md +226 -226
  53. package/top-ppt-html/references/infographics.md +43 -43
  54. package/top-ppt-html/references/layout-grammar.md +315 -315
  55. package/top-ppt-html/references/layouts-architecture.md +108 -108
  56. package/top-ppt-html/references/layouts-combo.md +600 -600
  57. package/top-ppt-html/references/layouts-research.md +160 -160
  58. package/top-ppt-html/references/modes.md +254 -254
  59. package/top-ppt-html/references/outline-design.md +275 -275
  60. package/top-ppt-html/references/playbook.md +266 -266
  61. package/top-ppt-html/references/pptx-export.md +209 -209
  62. package/top-ppt-html/references/reform-plan.md +252 -252
  63. package/top-ppt-html/references/styles.md +370 -370
  64. package/top-ppt-html/references/tech-design.md +138 -138
  65. package/top-ppt-html/scripts/audit_css.py +109 -109
  66. package/top-ppt-html/scripts/audit_docs.py +176 -176
  67. package/top-ppt-html/scripts/audit_skill.py +220 -220
  68. package/top-ppt-html/scripts/audit_styles.py +351 -351
  69. package/top-ppt-html/scripts/build_examples.py +2276 -2276
  70. package/top-ppt-html/scripts/build_pptx.js +2380 -2380
  71. package/top-ppt-html/scripts/capture_theme_overview.js +78 -78
  72. package/top-ppt-html/scripts/checks_html.py +127 -127
  73. package/top-ppt-html/scripts/cross_verify.py +294 -294
  74. package/top-ppt-html/scripts/env_probe.py +158 -158
  75. package/top-ppt-html/scripts/extract_model.py +210 -210
  76. package/top-ppt-html/scripts/extract_snippet.py +374 -374
  77. package/top-ppt-html/scripts/gen_channel_a.js +214 -214
  78. package/top-ppt-html/scripts/layout-constants.json +3377 -3377
  79. package/top-ppt-html/scripts/layout_slots.json +830 -830
  80. package/top-ppt-html/scripts/lib_layout_regions.js +412 -412
  81. package/top-ppt-html/scripts/measure_height.py +178 -178
  82. package/top-ppt-html/scripts/model-schema.json +547 -547
  83. package/top-ppt-html/scripts/negative_tests.py +307 -307
  84. package/top-ppt-html/scripts/package_skill.py +291 -291
  85. package/top-ppt-html/scripts/prepare_images.py +341 -341
  86. package/top-ppt-html/scripts/probe_image_export.py +188 -188
  87. package/top-ppt-html/scripts/quality_gate.py +301 -301
  88. package/top-ppt-html/scripts/regression.py +308 -308
  89. package/top-ppt-html/scripts/render_compare.py +275 -275
  90. package/top-ppt-html/scripts/render_from_model.py +698 -698
  91. package/top-ppt-html/scripts/scaffold_report.py +1054 -1054
  92. package/top-ppt-html/scripts/section-file-map.json +104 -104
  93. package/top-ppt-html/scripts/sync_runtime.py +662 -662
  94. package/top-ppt-html/scripts/validate_pptx.py +1510 -1510
  95. 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())