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