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