@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.
- package/CHANGELOG.md +139 -123
- package/README.md +126 -103
- package/bin/tms-skills.js +160 -160
- package/package.json +48 -48
- package/top-ppt-html/README.md +254 -253
- package/top-ppt-html/SKILL.md +111 -111
- package/top-ppt-html/assets/examples/2026-09-09-architecture-graphite-dark.html +3873 -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 +3873 -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 +4272 -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 +4272 -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 +4272 -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 +5474 -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 +5474 -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 +5474 -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 +5474 -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 +559 -589
- package/top-ppt-html/assets/templates/architecture.html +3675 -3728
- package/top-ppt-html/assets/templates/engine.css +787 -840
- package/top-ppt-html/assets/templates/presentation.html +3685 -3738
- package/top-ppt-html/assets/templates/research.html +3964 -4017
- package/top-ppt-html/assets/templates/ui.js +520 -520
- package/top-ppt-html/assets/theme-overview-architecture.png +0 -0
- package/top-ppt-html/assets/theme-overview-presentation.png +0 -0
- package/top-ppt-html/assets/theme-overview-research.png +0 -0
- package/top-ppt-html/assets/theme-overview.png +0 -0
- 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 +175 -0
- package/top-ppt-html/package.json +30 -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 +471 -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 +336 -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 +293 -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 +3309 -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 +307 -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
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
id,should_trigger,kind,prompt,expect
|
|
2
|
-
t01,true,explicit,"用 TopPPT HTML 做一份 10 页左右的产品发布演示,风格要克制一点","触发技能;产出单文件 HTML;mode=presentation;先给参考图"
|
|
3
|
-
t02,true,implicit,"帮我把这份调研材料做成一份能坐读的咨询风格研究报告","触发技能;mode=research;行动标题为结论句;含 Exhibit 编号"
|
|
4
|
-
t03,true,implicit,"把这套系统架构画成一张汇报图,评审会上看结构","触发技能;mode=architecture;图为王;节点 ≤24"
|
|
5
|
-
t04,true,context,"下周三要给董事会汇报年度经营复盘,材料在 attachments 里,需要能继续改的 PPT","触发技能;HTML+PPTX;research 或 presentation;交付前 strict 0/0"
|
|
6
|
-
t05,false,negative,"给我的 React 项目加一下 Tailwind 样式","不应触发技能(增量代码修改)"
|
|
7
|
-
t06,false,negative,"把这段 Python 脚本改成异步的","不应触发技能(纯代码工程)"
|
|
8
|
-
t07,true,implicit,"这份 HTML 报告我想转成能编辑的 PPT,尽量 1:1 还原","触发技能;走精导通道;命中深度模式触发词(1:1)"
|
|
9
|
-
t08,true,implicit,"现有的报告排版太挤了,帮我优化一下版式、配色和密度","触发技能;以原报告为起点重新生成"
|
|
10
|
-
t09,true,context,"客户要求把我们的平台能力做成一张飞轮图 + 一张分层架构图","触发技能;mode=architecture;结构图形族(loop / diagram)"
|
|
11
|
-
t10,true,implicit,"做一份 15 页的行业对标分析,要有对标矩阵、分布对比和流向分析","触发技能;mode=research;页型应含 matrix / boxplot / sankey"
|
|
12
|
-
t11,false,negative,"帮我生成一段 30 秒的产品宣传视频","不应触发技能(视频生成)"
|
|
13
|
-
t12,false,negative,"直接改一下这个 .docx 里的第二段文字","不应触发技能(Office 文件级编辑)"
|
|
14
|
-
t13,true,implicit,"要做一页把转化漏斗和主要流失原因讲清楚的图","触发技能;图表选型应为 funnel 或 sankey(不是默认柱状图)"
|
|
15
|
-
t14,true,implicit,"帮我准备一份季度复盘,数据多、细节多,别给我简化掉","触发技能;mode=research;细节保全(不砍口径列/维度);组合版式"
|
|
1
|
+
id,should_trigger,kind,prompt,expect
|
|
2
|
+
t01,true,explicit,"用 TopPPT HTML 做一份 10 页左右的产品发布演示,风格要克制一点","触发技能;产出单文件 HTML;mode=presentation;先给参考图"
|
|
3
|
+
t02,true,implicit,"帮我把这份调研材料做成一份能坐读的咨询风格研究报告","触发技能;mode=research;行动标题为结论句;含 Exhibit 编号"
|
|
4
|
+
t03,true,implicit,"把这套系统架构画成一张汇报图,评审会上看结构","触发技能;mode=architecture;图为王;节点 ≤24"
|
|
5
|
+
t04,true,context,"下周三要给董事会汇报年度经营复盘,材料在 attachments 里,需要能继续改的 PPT","触发技能;HTML+PPTX;research 或 presentation;交付前 strict 0/0"
|
|
6
|
+
t05,false,negative,"给我的 React 项目加一下 Tailwind 样式","不应触发技能(增量代码修改)"
|
|
7
|
+
t06,false,negative,"把这段 Python 脚本改成异步的","不应触发技能(纯代码工程)"
|
|
8
|
+
t07,true,implicit,"这份 HTML 报告我想转成能编辑的 PPT,尽量 1:1 还原","触发技能;走精导通道;命中深度模式触发词(1:1)"
|
|
9
|
+
t08,true,implicit,"现有的报告排版太挤了,帮我优化一下版式、配色和密度","触发技能;以原报告为起点重新生成"
|
|
10
|
+
t09,true,context,"客户要求把我们的平台能力做成一张飞轮图 + 一张分层架构图","触发技能;mode=architecture;结构图形族(loop / diagram)"
|
|
11
|
+
t10,true,implicit,"做一份 15 页的行业对标分析,要有对标矩阵、分布对比和流向分析","触发技能;mode=research;页型应含 matrix / boxplot / sankey"
|
|
12
|
+
t11,false,negative,"帮我生成一段 30 秒的产品宣传视频","不应触发技能(视频生成)"
|
|
13
|
+
t12,false,negative,"直接改一下这个 .docx 里的第二段文字","不应触发技能(Office 文件级编辑)"
|
|
14
|
+
t13,true,implicit,"要做一页把转化漏斗和主要流失原因讲清楚的图","触发技能;图表选型应为 funnel 或 sankey(不是默认柱状图)"
|
|
15
|
+
t14,true,implicit,"帮我准备一份季度复盘,数据多、细节多,别给我简化掉","触发技能;mode=research;细节保全(不砍口径列/维度);组合版式"
|
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$comment": "TopPPT HTML · 风格目标 rubric(结构化评分)。确定性检查回答「是否做了基本动作」,本 schema 回答「是否按我们想要的方式做的」。用宿主工具的 --output-schema / 结构化输出约束评审模型,字段稳定便于跨版本比较。用法:python evals/run_evals.py --help。",
|
|
3
|
-
"type": "object",
|
|
4
|
-
"properties": {
|
|
5
|
-
"overall_pass": { "type": "boolean", "description": "是否达到交付标准(任一 check 的 score < 60 即 false)" },
|
|
6
|
-
"score": { "type": "integer", "minimum": 0, "maximum": 100, "description": "加权总分:内容完整度 30 / 版式丰富度 20 / 图表选型 20 / 信息图表达 15 / 去AI味 15" },
|
|
7
|
-
"checks": {
|
|
8
|
-
"type": "array",
|
|
9
|
-
"description": "逐项检查;id 固定为 content / layout / chart / infographic / tone",
|
|
10
|
-
"items": {
|
|
11
|
-
"type": "object",
|
|
12
|
-
"properties": {
|
|
13
|
-
"id": { "type": "string", "enum": ["content", "layout", "chart", "infographic", "tone"] },
|
|
14
|
-
"score": { "type": "integer", "minimum": 0, "maximum": 100 },
|
|
15
|
-
"pass": { "type": "boolean" },
|
|
16
|
-
"notes": { "type": "string", "description": "判定依据:引用具体页码/元素,不要泛泛而谈" }
|
|
17
|
-
},
|
|
18
|
-
"required": ["id", "score", "pass", "notes"],
|
|
19
|
-
"additionalProperties": false
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
},
|
|
23
|
-
"required": ["overall_pass", "score", "checks"],
|
|
24
|
-
"additionalProperties": false
|
|
25
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"$comment": "TopPPT HTML · 风格目标 rubric(结构化评分)。确定性检查回答「是否做了基本动作」,本 schema 回答「是否按我们想要的方式做的」。用宿主工具的 --output-schema / 结构化输出约束评审模型,字段稳定便于跨版本比较。用法:python evals/run_evals.py --help。",
|
|
3
|
+
"type": "object",
|
|
4
|
+
"properties": {
|
|
5
|
+
"overall_pass": { "type": "boolean", "description": "是否达到交付标准(任一 check 的 score < 60 即 false)" },
|
|
6
|
+
"score": { "type": "integer", "minimum": 0, "maximum": 100, "description": "加权总分:内容完整度 30 / 版式丰富度 20 / 图表选型 20 / 信息图表达 15 / 去AI味 15" },
|
|
7
|
+
"checks": {
|
|
8
|
+
"type": "array",
|
|
9
|
+
"description": "逐项检查;id 固定为 content / layout / chart / infographic / tone",
|
|
10
|
+
"items": {
|
|
11
|
+
"type": "object",
|
|
12
|
+
"properties": {
|
|
13
|
+
"id": { "type": "string", "enum": ["content", "layout", "chart", "infographic", "tone"] },
|
|
14
|
+
"score": { "type": "integer", "minimum": 0, "maximum": 100 },
|
|
15
|
+
"pass": { "type": "boolean" },
|
|
16
|
+
"notes": { "type": "string", "description": "判定依据:引用具体页码/元素,不要泛泛而谈" }
|
|
17
|
+
},
|
|
18
|
+
"required": ["id", "score", "pass", "notes"],
|
|
19
|
+
"additionalProperties": false
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"required": ["overall_pass", "score", "checks"],
|
|
24
|
+
"additionalProperties": false
|
|
25
|
+
}
|
|
@@ -1,220 +1,220 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
# -*- coding: utf-8 -*-
|
|
3
|
-
"""TopPPT HTML · Evals 运行器(零依赖 · 确定性检查 + 效率度量 + rubric 落地)
|
|
4
|
-
|
|
5
|
-
用法:
|
|
6
|
-
python evals/run_evals.py --list # 列出 prompt 集(显式/隐式/上下文/负对照)
|
|
7
|
-
python evals/run_evals.py --budget # 打印效率预算
|
|
8
|
-
python evals/run_evals.py --score <报告.html> [--trace <trace.json>] [--json]
|
|
9
|
-
python evals/run_evals.py --score <报告.html> --rubric <rubric.json> # 并入定性评分
|
|
10
|
-
|
|
11
|
-
设计依据(OpenAI《Testing Agent Skills Systematically with Evals》):
|
|
12
|
-
一个 prompt → 一次被捕获的运行(trace + artifacts)→ 一小组检查 → 一个可随时间比较的分数。
|
|
13
|
-
四类目标分别对应:结果(strict 0/0)· 过程(Gate 0 / 轮次)· 风格(rubric)· 效率(轮次 / 工具调用 / 读取字节)。
|
|
14
|
-
|
|
15
|
-
trace.json 格式(由宿主智能体在跑完一条 prompt 后填写;字段都可选):
|
|
16
|
-
{
|
|
17
|
-
"turns": 3, # 交互轮次
|
|
18
|
-
"toolCalls": 22, # 工具调用次数(读文件 + 执行脚本)
|
|
19
|
-
"bytesRead": 41000, # 智能体读取的总字节数
|
|
20
|
-
"readFiles": ["references/playbook.md", ...],
|
|
21
|
-
"gate0ReferenceImage": true, # 首次交互是否先给出 theme-overview 参考图
|
|
22
|
-
"artifacts": ["2026-09-15-主题.html"]
|
|
23
|
-
}
|
|
24
|
-
"""
|
|
25
|
-
from __future__ import annotations
|
|
26
|
-
|
|
27
|
-
import argparse
|
|
28
|
-
import csv
|
|
29
|
-
import json
|
|
30
|
-
import re
|
|
31
|
-
import subprocess
|
|
32
|
-
import sys
|
|
33
|
-
from pathlib import Path
|
|
34
|
-
|
|
35
|
-
try:
|
|
36
|
-
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
|
|
37
|
-
except Exception:
|
|
38
|
-
pass
|
|
39
|
-
|
|
40
|
-
ROOT = Path(__file__).resolve().parent.parent
|
|
41
|
-
EVALS = Path(__file__).resolve().parent
|
|
42
|
-
sys.path.insert(0, str(ROOT / 'scripts'))
|
|
43
|
-
import checks_html # noqa: E402 承载/多样性/组合版式单源
|
|
44
|
-
|
|
45
|
-
LC = json.loads((ROOT / 'scripts' / 'layout-constants.json').read_text(encoding='utf-8'))
|
|
46
|
-
|
|
47
|
-
# ── 效率预算(与 SKILL.md 的"预算(硬)"一致;audit_skill.py 校验声明存在) ──────
|
|
48
|
-
BUDGET = {'turns': 3, 'toolCalls': 25, 'bytesRead': 40 * 1024}
|
|
49
|
-
|
|
50
|
-
# ── 结果/风格检查(确定性部分) ────────────────────────────────────────────────
|
|
51
|
-
CHECK_NAMES = {
|
|
52
|
-
'artifact': '产物存在且单文件零外链',
|
|
53
|
-
'strict': 'validate_report --strict 0/0',
|
|
54
|
-
'model': 'REPORT_MODEL 存在、合法 JSON、mode 一致',
|
|
55
|
-
'variety': '图表多样性达下限(charts.variety)',
|
|
56
|
-
'composite': '组合版式比例达下限(research)',
|
|
57
|
-
'structure': '结构图形/信息图页型被使用(含结构类内容时)',
|
|
58
|
-
'gate0': '过程:Gate 0 先给参考图',
|
|
59
|
-
'efficiency': '效率:轮次 / 工具调用 / 读取字节在预算内',
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
def run_validate(html: Path) -> tuple[bool, str]:
|
|
64
|
-
r = subprocess.run([sys.executable, str(ROOT / 'scripts' / 'validate_report.py'), str(html), '--strict'],
|
|
65
|
-
capture_output=True, text=True, encoding='utf-8')
|
|
66
|
-
out = (r.stdout or '') + (r.stderr or '')
|
|
67
|
-
tail = [ln for ln in out.splitlines() if ln.startswith('PASS ') or ln.startswith('结论')]
|
|
68
|
-
return r.returncode == 0, (tail[-1] if tail else out[-200:])
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
def score_artifact(html: Path) -> dict:
|
|
72
|
-
checks: list[dict] = []
|
|
73
|
-
|
|
74
|
-
def add(cid: str, ok: bool, note: str) -> None:
|
|
75
|
-
checks.append({'id': cid, 'name': CHECK_NAMES[cid], 'ok': ok, 'note': note})
|
|
76
|
-
|
|
77
|
-
if not html.exists():
|
|
78
|
-
add('artifact', False, '文件不存在')
|
|
79
|
-
return {'artifact': str(html), 'pass': False, 'checks': checks}
|
|
80
|
-
|
|
81
|
-
txt = html.read_text(encoding='utf-8')
|
|
82
|
-
ext = re.findall(r'<(?:link|script|img)[^>]+(?:href|src)=["\']https?://', txt)
|
|
83
|
-
add('artifact', not ext, f'{len(ext)} 处外链' if ext else f'{len(txt.encode("utf-8")) / 1024:.0f}KB 单文件')
|
|
84
|
-
|
|
85
|
-
ok, note = run_validate(html)
|
|
86
|
-
add('strict', ok, note)
|
|
87
|
-
|
|
88
|
-
m = re.search(r'window\.REPORT_MODEL\s*=\s*(\{[\s\S]*?\})\s*;', txt)
|
|
89
|
-
model = None
|
|
90
|
-
if m:
|
|
91
|
-
try:
|
|
92
|
-
model = json.loads(m.group(1))
|
|
93
|
-
except json.JSONDecodeError as e:
|
|
94
|
-
model = None
|
|
95
|
-
add('model', False, f'JSON 非法: {e}')
|
|
96
|
-
if m and model is not None:
|
|
97
|
-
dm = re.search(r'<html[^>]*data-mode="([^"]+)"', txt)
|
|
98
|
-
consistent = (not model.get('mode')) or (not dm) or model['mode'] == dm.group(1)
|
|
99
|
-
add('model', consistent, f"mode={model.get('mode')} / data-mode={dm.group(1) if dm else '?'}")
|
|
100
|
-
elif not m:
|
|
101
|
-
add('model', False, '未找到 window.REPORT_MODEL')
|
|
102
|
-
|
|
103
|
-
mode = (model or {}).get('mode') or 'presentation'
|
|
104
|
-
per_page = checks_html.chart_types_per_page(txt)
|
|
105
|
-
used = checks_html.distinct_chart_types(txt)
|
|
106
|
-
v = checks_html.chart_variety(LC)
|
|
107
|
-
n_chart_pages = len([1 for ts in per_page if ts])
|
|
108
|
-
if used:
|
|
109
|
-
floor = checks_html.variety_floor(mode, n_chart_pages, v)
|
|
110
|
-
add('variety', len(used) >= floor, f'{len(used)} 种 / 下限 {floor}: {used}')
|
|
111
|
-
else:
|
|
112
|
-
add('variety', True, '无图表页(不适用)')
|
|
113
|
-
|
|
114
|
-
composite_ratio = checks_html.composite_required(mode, v)
|
|
115
|
-
if composite_ratio > 0:
|
|
116
|
-
multi, n_content = checks_html.composite_pages(txt)
|
|
117
|
-
need = max(1, int(n_content * composite_ratio))
|
|
118
|
-
add('composite', multi >= need, f'{multi}/{n_content} 页(下限 {need})')
|
|
119
|
-
|
|
120
|
-
# 结构图形/信息图:**只在 architecture 模式硬判**(模式 C 契约 = 图为王,必须有结构主图);
|
|
121
|
-
# 其余模式为**信息项**(ok=None,不计入 pass)——信息图页型族对 research/architecture 是
|
|
122
|
-
# "可用"而非"必须",纯演示稿/纯数据页没有结构素材,硬判会制造假阴性(属 rubric 的 C3 风格项)。
|
|
123
|
-
struct_types = {'sankey', 'treemap', 'boxplot', 'network', 'marimekko', 'streamgraph',
|
|
124
|
-
'flow', 'tree', 'sequence', 'loop'}
|
|
125
|
-
has_struct = bool(set(used) & struct_types) or ('class="arch' in txt) or ('class="lane' in txt)
|
|
126
|
-
if mode == 'architecture':
|
|
127
|
-
add('structure', has_struct, '架构模式图为王:'
|
|
128
|
-
+ ('已用结构主图' if has_struct else '未用(架构模式必须有分层带/泳道/结构图形)'))
|
|
129
|
-
else:
|
|
130
|
-
add('structure', None, '信息图/结构图形:' + ('有' if has_struct else '未使用') + '(本模式为信息项,不计入通过判定)')
|
|
131
|
-
|
|
132
|
-
return {'artifact': str(html), 'mode': mode, 'checks': checks}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
def score_trace(trace_path: Path) -> dict:
|
|
136
|
-
t = json.loads(trace_path.read_text(encoding='utf-8'))
|
|
137
|
-
out = []
|
|
138
|
-
for key, limit in (('turns', BUDGET['turns']), ('toolCalls', BUDGET['toolCalls']),
|
|
139
|
-
('bytesRead', BUDGET['bytesRead'])):
|
|
140
|
-
v = t.get(key)
|
|
141
|
-
if v is None:
|
|
142
|
-
out.append({'id': key, 'ok': None, 'note': '未记录'})
|
|
143
|
-
continue
|
|
144
|
-
out.append({'id': key, 'ok': v <= limit, 'note': f'{v} / 上限 {limit}'})
|
|
145
|
-
g0 = t.get('gate0ReferenceImage')
|
|
146
|
-
out.append({'id': 'gate0', 'ok': g0 if g0 is not None else None,
|
|
147
|
-
'note': '首次交互给出参考图' if g0 else ('未给出参考图' if g0 is False else '未记录')})
|
|
148
|
-
return {'trace': str(trace_path), 'checks': out}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
def main() -> int:
|
|
152
|
-
ap = argparse.ArgumentParser(description='TopPPT HTML Evals 运行器')
|
|
153
|
-
ap.add_argument('--list', action='store_true', help='列出 prompt 集')
|
|
154
|
-
ap.add_argument('--budget', action='store_true', help='打印效率预算')
|
|
155
|
-
ap.add_argument('--score', help='对一份报告产物跑确定性检查')
|
|
156
|
-
ap.add_argument('--trace', help='过程/效率 trace JSON')
|
|
157
|
-
ap.add_argument('--rubric', help='已填好的 rubric JSON(定性评分,见 rubric.schema.json)')
|
|
158
|
-
ap.add_argument('--json', action='store_true', help='机器可读输出')
|
|
159
|
-
args = ap.parse_args()
|
|
160
|
-
|
|
161
|
-
if args.list:
|
|
162
|
-
rows = list(csv.DictReader((EVALS / 'prompts.csv').read_text(encoding='utf-8').splitlines()))
|
|
163
|
-
print(f'prompt 集 {len(rows)} 条(显式 / 隐式 / 上下文 / 负对照)\n')
|
|
164
|
-
for r in rows:
|
|
165
|
-
flag = '应触发' if r['should_trigger'] == 'true' else '不应触发'
|
|
166
|
-
print(f" {r['id']} [{r['kind']:<8}] {flag} · {r['prompt']}")
|
|
167
|
-
print(f" 期望:{r['expect']}")
|
|
168
|
-
n_neg = sum(1 for r in rows if r['should_trigger'] != 'true')
|
|
169
|
-
print(f'\n负对照 {n_neg} 条(捕捉"过于急切地触发技能"的假阳性)')
|
|
170
|
-
return 0
|
|
171
|
-
|
|
172
|
-
if args.budget:
|
|
173
|
-
print('效率预算(SKILL.md 的"预算(硬)"与 audit_skill.py 同源):')
|
|
174
|
-
print(f" 交互轮次 ≤ {BUDGET['turns']} · 工具调用 ≤ {BUDGET['toolCalls']} · "
|
|
175
|
-
f"读取字节 ≤ {BUDGET['bytesRead'] // 1024}KB")
|
|
176
|
-
print(' 必读文件 = SKILL.md + references/playbook.md(L1);L2 深度文件按需读、读完即停')
|
|
177
|
-
return 0
|
|
178
|
-
|
|
179
|
-
if not args.score:
|
|
180
|
-
ap.print_help()
|
|
181
|
-
return 2
|
|
182
|
-
|
|
183
|
-
result = score_artifact(Path(args.score))
|
|
184
|
-
if args.trace:
|
|
185
|
-
result['process'] = score_trace(Path(args.trace))
|
|
186
|
-
if args.rubric:
|
|
187
|
-
result['rubric'] = json.loads(Path(args.rubric).read_text(encoding='utf-8'))
|
|
188
|
-
|
|
189
|
-
def all_ok(checks):
|
|
190
|
-
return all(c['ok'] for c in checks if c.get('ok') is not None)
|
|
191
|
-
|
|
192
|
-
result['pass'] = all_ok(result['checks']) and (
|
|
193
|
-
all_ok(result['process']['checks']) if 'process' in result else True)
|
|
194
|
-
|
|
195
|
-
if args.json:
|
|
196
|
-
print(json.dumps(result, ensure_ascii=False, indent=2))
|
|
197
|
-
return 0 if result['pass'] else 1
|
|
198
|
-
|
|
199
|
-
print(f"Eval 计分 · {result['artifact']} (mode={result.get('mode', '?')})")
|
|
200
|
-
print('-' * 60)
|
|
201
|
-
for c in result['checks']:
|
|
202
|
-
mark = '✓' if c['ok'] else ('·' if c['ok'] is None else '✗')
|
|
203
|
-
print(f" {mark} [{c['id']}] {c['name']}:{c['note']}")
|
|
204
|
-
if 'process' in result:
|
|
205
|
-
print(' ── 过程与效率 ──')
|
|
206
|
-
for c in result['process']['checks']:
|
|
207
|
-
mark = '✓' if c['ok'] else ('·' if c['ok'] is None else '✗')
|
|
208
|
-
print(f" {mark} [{c['id']}] {c['note']}")
|
|
209
|
-
if 'rubric' in result:
|
|
210
|
-
r = result['rubric']
|
|
211
|
-
print(f" ── 定性 rubric ── 总分 {r.get('score')} · {'通过' if r.get('overall_pass') else '不通过'}")
|
|
212
|
-
for c in r.get('checks', []):
|
|
213
|
-
print(f" {c.get('score'):>3} [{c.get('id')}] {c.get('notes', '')[:60]}")
|
|
214
|
-
print('-' * 60)
|
|
215
|
-
print('结论:' + ('通过' if result['pass'] else '未通过(见上方 ✗ 项)'))
|
|
216
|
-
return 0 if result['pass'] else 1
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
if __name__ == '__main__':
|
|
220
|
-
sys.exit(main())
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""TopPPT HTML · Evals 运行器(零依赖 · 确定性检查 + 效率度量 + rubric 落地)
|
|
4
|
+
|
|
5
|
+
用法:
|
|
6
|
+
python evals/run_evals.py --list # 列出 prompt 集(显式/隐式/上下文/负对照)
|
|
7
|
+
python evals/run_evals.py --budget # 打印效率预算
|
|
8
|
+
python evals/run_evals.py --score <报告.html> [--trace <trace.json>] [--json]
|
|
9
|
+
python evals/run_evals.py --score <报告.html> --rubric <rubric.json> # 并入定性评分
|
|
10
|
+
|
|
11
|
+
设计依据(OpenAI《Testing Agent Skills Systematically with Evals》):
|
|
12
|
+
一个 prompt → 一次被捕获的运行(trace + artifacts)→ 一小组检查 → 一个可随时间比较的分数。
|
|
13
|
+
四类目标分别对应:结果(strict 0/0)· 过程(Gate 0 / 轮次)· 风格(rubric)· 效率(轮次 / 工具调用 / 读取字节)。
|
|
14
|
+
|
|
15
|
+
trace.json 格式(由宿主智能体在跑完一条 prompt 后填写;字段都可选):
|
|
16
|
+
{
|
|
17
|
+
"turns": 3, # 交互轮次
|
|
18
|
+
"toolCalls": 22, # 工具调用次数(读文件 + 执行脚本)
|
|
19
|
+
"bytesRead": 41000, # 智能体读取的总字节数
|
|
20
|
+
"readFiles": ["references/playbook.md", ...],
|
|
21
|
+
"gate0ReferenceImage": true, # 首次交互是否先给出 theme-overview 参考图
|
|
22
|
+
"artifacts": ["2026-09-15-主题.html"]
|
|
23
|
+
}
|
|
24
|
+
"""
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import argparse
|
|
28
|
+
import csv
|
|
29
|
+
import json
|
|
30
|
+
import re
|
|
31
|
+
import subprocess
|
|
32
|
+
import sys
|
|
33
|
+
from pathlib import Path
|
|
34
|
+
|
|
35
|
+
try:
|
|
36
|
+
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
|
|
37
|
+
except Exception:
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
ROOT = Path(__file__).resolve().parent.parent
|
|
41
|
+
EVALS = Path(__file__).resolve().parent
|
|
42
|
+
sys.path.insert(0, str(ROOT / 'scripts'))
|
|
43
|
+
import checks_html # noqa: E402 承载/多样性/组合版式单源
|
|
44
|
+
|
|
45
|
+
LC = json.loads((ROOT / 'scripts' / 'layout-constants.json').read_text(encoding='utf-8'))
|
|
46
|
+
|
|
47
|
+
# ── 效率预算(与 SKILL.md 的"预算(硬)"一致;audit_skill.py 校验声明存在) ──────
|
|
48
|
+
BUDGET = {'turns': 3, 'toolCalls': 25, 'bytesRead': 40 * 1024}
|
|
49
|
+
|
|
50
|
+
# ── 结果/风格检查(确定性部分) ────────────────────────────────────────────────
|
|
51
|
+
CHECK_NAMES = {
|
|
52
|
+
'artifact': '产物存在且单文件零外链',
|
|
53
|
+
'strict': 'validate_report --strict 0/0',
|
|
54
|
+
'model': 'REPORT_MODEL 存在、合法 JSON、mode 一致',
|
|
55
|
+
'variety': '图表多样性达下限(charts.variety)',
|
|
56
|
+
'composite': '组合版式比例达下限(research)',
|
|
57
|
+
'structure': '结构图形/信息图页型被使用(含结构类内容时)',
|
|
58
|
+
'gate0': '过程:Gate 0 先给参考图',
|
|
59
|
+
'efficiency': '效率:轮次 / 工具调用 / 读取字节在预算内',
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def run_validate(html: Path) -> tuple[bool, str]:
|
|
64
|
+
r = subprocess.run([sys.executable, str(ROOT / 'scripts' / 'validate_report.py'), str(html), '--strict'],
|
|
65
|
+
capture_output=True, text=True, encoding='utf-8')
|
|
66
|
+
out = (r.stdout or '') + (r.stderr or '')
|
|
67
|
+
tail = [ln for ln in out.splitlines() if ln.startswith('PASS ') or ln.startswith('结论')]
|
|
68
|
+
return r.returncode == 0, (tail[-1] if tail else out[-200:])
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def score_artifact(html: Path) -> dict:
|
|
72
|
+
checks: list[dict] = []
|
|
73
|
+
|
|
74
|
+
def add(cid: str, ok: bool, note: str) -> None:
|
|
75
|
+
checks.append({'id': cid, 'name': CHECK_NAMES[cid], 'ok': ok, 'note': note})
|
|
76
|
+
|
|
77
|
+
if not html.exists():
|
|
78
|
+
add('artifact', False, '文件不存在')
|
|
79
|
+
return {'artifact': str(html), 'pass': False, 'checks': checks}
|
|
80
|
+
|
|
81
|
+
txt = html.read_text(encoding='utf-8')
|
|
82
|
+
ext = re.findall(r'<(?:link|script|img)[^>]+(?:href|src)=["\']https?://', txt)
|
|
83
|
+
add('artifact', not ext, f'{len(ext)} 处外链' if ext else f'{len(txt.encode("utf-8")) / 1024:.0f}KB 单文件')
|
|
84
|
+
|
|
85
|
+
ok, note = run_validate(html)
|
|
86
|
+
add('strict', ok, note)
|
|
87
|
+
|
|
88
|
+
m = re.search(r'window\.REPORT_MODEL\s*=\s*(\{[\s\S]*?\})\s*;', txt)
|
|
89
|
+
model = None
|
|
90
|
+
if m:
|
|
91
|
+
try:
|
|
92
|
+
model = json.loads(m.group(1))
|
|
93
|
+
except json.JSONDecodeError as e:
|
|
94
|
+
model = None
|
|
95
|
+
add('model', False, f'JSON 非法: {e}')
|
|
96
|
+
if m and model is not None:
|
|
97
|
+
dm = re.search(r'<html[^>]*data-mode="([^"]+)"', txt)
|
|
98
|
+
consistent = (not model.get('mode')) or (not dm) or model['mode'] == dm.group(1)
|
|
99
|
+
add('model', consistent, f"mode={model.get('mode')} / data-mode={dm.group(1) if dm else '?'}")
|
|
100
|
+
elif not m:
|
|
101
|
+
add('model', False, '未找到 window.REPORT_MODEL')
|
|
102
|
+
|
|
103
|
+
mode = (model or {}).get('mode') or 'presentation'
|
|
104
|
+
per_page = checks_html.chart_types_per_page(txt)
|
|
105
|
+
used = checks_html.distinct_chart_types(txt)
|
|
106
|
+
v = checks_html.chart_variety(LC)
|
|
107
|
+
n_chart_pages = len([1 for ts in per_page if ts])
|
|
108
|
+
if used:
|
|
109
|
+
floor = checks_html.variety_floor(mode, n_chart_pages, v)
|
|
110
|
+
add('variety', len(used) >= floor, f'{len(used)} 种 / 下限 {floor}: {used}')
|
|
111
|
+
else:
|
|
112
|
+
add('variety', True, '无图表页(不适用)')
|
|
113
|
+
|
|
114
|
+
composite_ratio = checks_html.composite_required(mode, v)
|
|
115
|
+
if composite_ratio > 0:
|
|
116
|
+
multi, n_content = checks_html.composite_pages(txt)
|
|
117
|
+
need = max(1, int(n_content * composite_ratio))
|
|
118
|
+
add('composite', multi >= need, f'{multi}/{n_content} 页(下限 {need})')
|
|
119
|
+
|
|
120
|
+
# 结构图形/信息图:**只在 architecture 模式硬判**(模式 C 契约 = 图为王,必须有结构主图);
|
|
121
|
+
# 其余模式为**信息项**(ok=None,不计入 pass)——信息图页型族对 research/architecture 是
|
|
122
|
+
# "可用"而非"必须",纯演示稿/纯数据页没有结构素材,硬判会制造假阴性(属 rubric 的 C3 风格项)。
|
|
123
|
+
struct_types = {'sankey', 'treemap', 'boxplot', 'network', 'marimekko', 'streamgraph',
|
|
124
|
+
'flow', 'tree', 'sequence', 'loop'}
|
|
125
|
+
has_struct = bool(set(used) & struct_types) or ('class="arch' in txt) or ('class="lane' in txt)
|
|
126
|
+
if mode == 'architecture':
|
|
127
|
+
add('structure', has_struct, '架构模式图为王:'
|
|
128
|
+
+ ('已用结构主图' if has_struct else '未用(架构模式必须有分层带/泳道/结构图形)'))
|
|
129
|
+
else:
|
|
130
|
+
add('structure', None, '信息图/结构图形:' + ('有' if has_struct else '未使用') + '(本模式为信息项,不计入通过判定)')
|
|
131
|
+
|
|
132
|
+
return {'artifact': str(html), 'mode': mode, 'checks': checks}
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def score_trace(trace_path: Path) -> dict:
|
|
136
|
+
t = json.loads(trace_path.read_text(encoding='utf-8'))
|
|
137
|
+
out = []
|
|
138
|
+
for key, limit in (('turns', BUDGET['turns']), ('toolCalls', BUDGET['toolCalls']),
|
|
139
|
+
('bytesRead', BUDGET['bytesRead'])):
|
|
140
|
+
v = t.get(key)
|
|
141
|
+
if v is None:
|
|
142
|
+
out.append({'id': key, 'ok': None, 'note': '未记录'})
|
|
143
|
+
continue
|
|
144
|
+
out.append({'id': key, 'ok': v <= limit, 'note': f'{v} / 上限 {limit}'})
|
|
145
|
+
g0 = t.get('gate0ReferenceImage')
|
|
146
|
+
out.append({'id': 'gate0', 'ok': g0 if g0 is not None else None,
|
|
147
|
+
'note': '首次交互给出参考图' if g0 else ('未给出参考图' if g0 is False else '未记录')})
|
|
148
|
+
return {'trace': str(trace_path), 'checks': out}
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def main() -> int:
|
|
152
|
+
ap = argparse.ArgumentParser(description='TopPPT HTML Evals 运行器')
|
|
153
|
+
ap.add_argument('--list', action='store_true', help='列出 prompt 集')
|
|
154
|
+
ap.add_argument('--budget', action='store_true', help='打印效率预算')
|
|
155
|
+
ap.add_argument('--score', help='对一份报告产物跑确定性检查')
|
|
156
|
+
ap.add_argument('--trace', help='过程/效率 trace JSON')
|
|
157
|
+
ap.add_argument('--rubric', help='已填好的 rubric JSON(定性评分,见 rubric.schema.json)')
|
|
158
|
+
ap.add_argument('--json', action='store_true', help='机器可读输出')
|
|
159
|
+
args = ap.parse_args()
|
|
160
|
+
|
|
161
|
+
if args.list:
|
|
162
|
+
rows = list(csv.DictReader((EVALS / 'prompts.csv').read_text(encoding='utf-8').splitlines()))
|
|
163
|
+
print(f'prompt 集 {len(rows)} 条(显式 / 隐式 / 上下文 / 负对照)\n')
|
|
164
|
+
for r in rows:
|
|
165
|
+
flag = '应触发' if r['should_trigger'] == 'true' else '不应触发'
|
|
166
|
+
print(f" {r['id']} [{r['kind']:<8}] {flag} · {r['prompt']}")
|
|
167
|
+
print(f" 期望:{r['expect']}")
|
|
168
|
+
n_neg = sum(1 for r in rows if r['should_trigger'] != 'true')
|
|
169
|
+
print(f'\n负对照 {n_neg} 条(捕捉"过于急切地触发技能"的假阳性)')
|
|
170
|
+
return 0
|
|
171
|
+
|
|
172
|
+
if args.budget:
|
|
173
|
+
print('效率预算(SKILL.md 的"预算(硬)"与 audit_skill.py 同源):')
|
|
174
|
+
print(f" 交互轮次 ≤ {BUDGET['turns']} · 工具调用 ≤ {BUDGET['toolCalls']} · "
|
|
175
|
+
f"读取字节 ≤ {BUDGET['bytesRead'] // 1024}KB")
|
|
176
|
+
print(' 必读文件 = SKILL.md + references/playbook.md(L1);L2 深度文件按需读、读完即停')
|
|
177
|
+
return 0
|
|
178
|
+
|
|
179
|
+
if not args.score:
|
|
180
|
+
ap.print_help()
|
|
181
|
+
return 2
|
|
182
|
+
|
|
183
|
+
result = score_artifact(Path(args.score))
|
|
184
|
+
if args.trace:
|
|
185
|
+
result['process'] = score_trace(Path(args.trace))
|
|
186
|
+
if args.rubric:
|
|
187
|
+
result['rubric'] = json.loads(Path(args.rubric).read_text(encoding='utf-8'))
|
|
188
|
+
|
|
189
|
+
def all_ok(checks):
|
|
190
|
+
return all(c['ok'] for c in checks if c.get('ok') is not None)
|
|
191
|
+
|
|
192
|
+
result['pass'] = all_ok(result['checks']) and (
|
|
193
|
+
all_ok(result['process']['checks']) if 'process' in result else True)
|
|
194
|
+
|
|
195
|
+
if args.json:
|
|
196
|
+
print(json.dumps(result, ensure_ascii=False, indent=2))
|
|
197
|
+
return 0 if result['pass'] else 1
|
|
198
|
+
|
|
199
|
+
print(f"Eval 计分 · {result['artifact']} (mode={result.get('mode', '?')})")
|
|
200
|
+
print('-' * 60)
|
|
201
|
+
for c in result['checks']:
|
|
202
|
+
mark = '✓' if c['ok'] else ('·' if c['ok'] is None else '✗')
|
|
203
|
+
print(f" {mark} [{c['id']}] {c['name']}:{c['note']}")
|
|
204
|
+
if 'process' in result:
|
|
205
|
+
print(' ── 过程与效率 ──')
|
|
206
|
+
for c in result['process']['checks']:
|
|
207
|
+
mark = '✓' if c['ok'] else ('·' if c['ok'] is None else '✗')
|
|
208
|
+
print(f" {mark} [{c['id']}] {c['note']}")
|
|
209
|
+
if 'rubric' in result:
|
|
210
|
+
r = result['rubric']
|
|
211
|
+
print(f" ── 定性 rubric ── 总分 {r.get('score')} · {'通过' if r.get('overall_pass') else '不通过'}")
|
|
212
|
+
for c in r.get('checks', []):
|
|
213
|
+
print(f" {c.get('score'):>3} [{c.get('id')}] {c.get('notes', '')[:60]}")
|
|
214
|
+
print('-' * 60)
|
|
215
|
+
print('结论:' + ('通过' if result['pass'] else '未通过(见上方 ✗ 项)'))
|
|
216
|
+
return 0 if result['pass'] else 1
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
if __name__ == '__main__':
|
|
220
|
+
sys.exit(main())
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$comment": "trace 示例:跑完一条 prompt 后由宿主智能体填写(字段都可选)。run_evals.py --score <报告.html> --trace evals/trace.example.json 会把过程与效率一并计分。预算单源:SKILL.md「预算(硬)」= 轮次 ≤3 / 工具调用 ≤25 / 读取 ≤40KB。",
|
|
3
|
-
"promptId": "t02",
|
|
4
|
-
"turns": 3,
|
|
5
|
-
"toolCalls": 19,
|
|
6
|
-
"bytesRead": 34500,
|
|
7
|
-
"readFiles": [
|
|
8
|
-
"SKILL.md",
|
|
9
|
-
"references/playbook.md",
|
|
10
|
-
"references/components.md",
|
|
11
|
-
"references/charts.md"
|
|
12
|
-
],
|
|
13
|
-
"gate0ReferenceImage": true,
|
|
14
|
-
"artifacts": ["2026-09-15-主题.html"],
|
|
15
|
-
"notes": "轻量路径:1 轮六项问询 + 1 轮交付;只读 L1 + 两份 L2;未整份读取模式模板(用 scaffold_report.py)"
|
|
16
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"$comment": "trace 示例:跑完一条 prompt 后由宿主智能体填写(字段都可选)。run_evals.py --score <报告.html> --trace evals/trace.example.json 会把过程与效率一并计分。预算单源:SKILL.md「预算(硬)」= 轮次 ≤3 / 工具调用 ≤25 / 读取 ≤40KB。",
|
|
3
|
+
"promptId": "t02",
|
|
4
|
+
"turns": 3,
|
|
5
|
+
"toolCalls": 19,
|
|
6
|
+
"bytesRead": 34500,
|
|
7
|
+
"readFiles": [
|
|
8
|
+
"SKILL.md",
|
|
9
|
+
"references/playbook.md",
|
|
10
|
+
"references/components.md",
|
|
11
|
+
"references/charts.md"
|
|
12
|
+
],
|
|
13
|
+
"gate0ReferenceImage": true,
|
|
14
|
+
"artifacts": ["2026-09-15-主题.html"],
|
|
15
|
+
"notes": "轻量路径:1 轮六项问询 + 1 轮交付;只读 L1 + 两份 L2;未整份读取模式模板(用 scaffold_report.py)"
|
|
16
|
+
}
|