@topmindspace/tms-skills 2.0.0 → 2.0.2

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 (99) hide show
  1. package/CHANGELOG.md +139 -123
  2. package/README.md +126 -103
  3. package/bin/tms-skills.js +160 -160
  4. package/package.json +48 -48
  5. package/top-ppt-html/README.md +254 -253
  6. package/top-ppt-html/SKILL.md +111 -111
  7. package/top-ppt-html/assets/examples/2026-09-09-architecture-graphite-dark.html +3873 -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 +3873 -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 +4272 -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 +4272 -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 +4272 -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 +5474 -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 +5474 -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 +5474 -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 +5474 -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 +559 -589
  27. package/top-ppt-html/assets/templates/architecture.html +3675 -3728
  28. package/top-ppt-html/assets/templates/engine.css +787 -840
  29. package/top-ppt-html/assets/templates/presentation.html +3685 -3738
  30. package/top-ppt-html/assets/templates/research.html +3964 -4017
  31. package/top-ppt-html/assets/templates/ui.js +520 -520
  32. package/top-ppt-html/assets/theme-overview-architecture.png +0 -0
  33. package/top-ppt-html/assets/theme-overview-presentation.png +0 -0
  34. package/top-ppt-html/assets/theme-overview-research.png +0 -0
  35. package/top-ppt-html/assets/theme-overview.png +0 -0
  36. package/top-ppt-html/evals/prompts.csv +15 -15
  37. package/top-ppt-html/evals/rubric.schema.json +25 -25
  38. package/top-ppt-html/evals/run_evals.py +220 -220
  39. package/top-ppt-html/evals/trace.example.json +16 -16
  40. package/top-ppt-html/package-lock.json +175 -0
  41. package/top-ppt-html/package.json +30 -35
  42. package/top-ppt-html/references/charts-basic.md +624 -624
  43. package/top-ppt-html/references/charts-discipline.md +108 -108
  44. package/top-ppt-html/references/charts-extended.md +482 -482
  45. package/top-ppt-html/references/charts.md +28 -28
  46. package/top-ppt-html/references/components-atoms.md +624 -624
  47. package/top-ppt-html/references/components.md +30 -30
  48. package/top-ppt-html/references/content-rules.md +490 -490
  49. package/top-ppt-html/references/design-system-engine.md +235 -235
  50. package/top-ppt-html/references/design-system.md +471 -478
  51. package/top-ppt-html/references/failure-modes.md +214 -214
  52. package/top-ppt-html/references/high-fidelity.md +127 -127
  53. package/top-ppt-html/references/icons.md +397 -397
  54. package/top-ppt-html/references/industry-benchmark.md +105 -105
  55. package/top-ppt-html/references/infographics-stats.md +308 -308
  56. package/top-ppt-html/references/infographics-structure.md +226 -226
  57. package/top-ppt-html/references/infographics.md +43 -43
  58. package/top-ppt-html/references/layout-grammar.md +315 -315
  59. package/top-ppt-html/references/layouts-architecture.md +108 -108
  60. package/top-ppt-html/references/layouts-combo.md +600 -600
  61. package/top-ppt-html/references/layouts-research.md +160 -160
  62. package/top-ppt-html/references/modes.md +254 -254
  63. package/top-ppt-html/references/outline-design.md +275 -275
  64. package/top-ppt-html/references/playbook.md +266 -266
  65. package/top-ppt-html/references/pptx-export.md +209 -209
  66. package/top-ppt-html/references/reform-plan.md +252 -252
  67. package/top-ppt-html/references/styles.md +336 -370
  68. package/top-ppt-html/references/tech-design.md +138 -138
  69. package/top-ppt-html/scripts/audit_css.py +109 -109
  70. package/top-ppt-html/scripts/audit_docs.py +176 -176
  71. package/top-ppt-html/scripts/audit_skill.py +220 -220
  72. package/top-ppt-html/scripts/audit_styles.py +293 -351
  73. package/top-ppt-html/scripts/build_examples.py +2276 -2276
  74. package/top-ppt-html/scripts/build_pptx.js +2380 -2380
  75. package/top-ppt-html/scripts/capture_theme_overview.js +78 -78
  76. package/top-ppt-html/scripts/checks_html.py +127 -127
  77. package/top-ppt-html/scripts/cross_verify.py +294 -294
  78. package/top-ppt-html/scripts/env_probe.py +158 -158
  79. package/top-ppt-html/scripts/extract_model.py +210 -210
  80. package/top-ppt-html/scripts/extract_snippet.py +374 -374
  81. package/top-ppt-html/scripts/gen_channel_a.js +214 -214
  82. package/top-ppt-html/scripts/layout-constants.json +3309 -3377
  83. package/top-ppt-html/scripts/layout_slots.json +830 -830
  84. package/top-ppt-html/scripts/lib_layout_regions.js +412 -412
  85. package/top-ppt-html/scripts/measure_height.py +178 -178
  86. package/top-ppt-html/scripts/model-schema.json +547 -547
  87. package/top-ppt-html/scripts/negative_tests.py +307 -307
  88. package/top-ppt-html/scripts/package_skill.py +291 -291
  89. package/top-ppt-html/scripts/prepare_images.py +341 -341
  90. package/top-ppt-html/scripts/probe_image_export.py +188 -188
  91. package/top-ppt-html/scripts/quality_gate.py +301 -301
  92. package/top-ppt-html/scripts/regression.py +307 -308
  93. package/top-ppt-html/scripts/render_compare.py +275 -275
  94. package/top-ppt-html/scripts/render_from_model.py +698 -698
  95. package/top-ppt-html/scripts/scaffold_report.py +1054 -1054
  96. package/top-ppt-html/scripts/section-file-map.json +104 -104
  97. package/top-ppt-html/scripts/sync_runtime.py +662 -662
  98. package/top-ppt-html/scripts/validate_pptx.py +1510 -1510
  99. 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())