@ohos-cpf/3rdloop 0.0.12 → 0.0.13

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 (45) hide show
  1. package/package.json +1 -1
  2. package/vendor/Server/Routes/controllers/LoopEngineController.js +36 -3
  3. package/vendor/Server/Routes/controllers/OrchestratorController.js +193 -9
  4. package/vendor/Server/Skills/flutter-build-test/SKILL.md +252 -0
  5. package/vendor/Server/Skills/flutter-build-test/assets/BUILDENV_TEMPLATE.md +42 -0
  6. package/vendor/Server/Skills/flutter-build-test/assets/BUILD_REPORT_TEMPLATE.md +64 -0
  7. package/vendor/Server/Skills/flutter-build-test/assets/README_SECTION_TEMPLATE.md +78 -0
  8. package/vendor/Server/Skills/flutter-build-test/references/BUILD_TROUBLESHOOTING.md +128 -0
  9. package/vendor/Server/Skills/flutter-build-test/references/DOC_UPDATE_GUIDE.md +119 -0
  10. package/vendor/Server/Skills/flutter-build-test/references/FLVM_GUIDE.md +89 -0
  11. package/vendor/Server/Skills/flutter-build-test/scripts/build-matrix.cjs +374 -0
  12. package/vendor/Server/Skills/flutter-build-test/scripts/locate-example.cjs +189 -0
  13. package/vendor/Server/Skills/flutter-build-test/scripts/update-buildenv.cjs +171 -0
  14. package/vendor/Server/Skills/flutter-code-use/SKILL.md +316 -0
  15. package/vendor/Server/Skills/flutter-code-use/references/event-channel.md +440 -0
  16. package/vendor/Server/Skills/flutter-code-use/references/federated.md +295 -0
  17. package/vendor/Server/Skills/flutter-code-use/references/ffi-binding-translate.md +130 -0
  18. package/vendor/Server/Skills/flutter-code-use/references/ffi-compile-from-source.md +169 -0
  19. package/vendor/Server/Skills/flutter-code-use/references/ffi-fetch-at-build.md +161 -0
  20. package/vendor/Server/Skills/flutter-code-use/references/ffi-prebuilt-bundle.md +175 -0
  21. package/vendor/Server/Skills/flutter-code-use/references/ffi-rhttp-guide.md +235 -0
  22. package/vendor/Server/Skills/flutter-code-use/references/ffi-rust-cross-compile.md +514 -0
  23. package/vendor/Server/Skills/flutter-code-use/references/ffi.md +220 -0
  24. package/vendor/Server/Skills/flutter-code-use/references/method-channel.md +643 -0
  25. package/vendor/Server/Skills/flutter-code-use/references/monorepo.md +188 -0
  26. package/vendor/Server/Skills/flutter-code-use/references/ohos-api-pitfalls.md +717 -0
  27. package/vendor/Server/Skills/flutter-code-use/references/platform-view.md +448 -0
  28. package/vendor/Server/Skills/flutter-code-use/references/pure-dart.md +180 -0
  29. package/vendor/Server/Skills/flutter-code-use/references/texture.md +459 -0
  30. package/vendor/Server/Skills/flutter-demo-code-generator/SKILL.md +270 -0
  31. package/vendor/Server/Skills/flutter-demo-code-generator/assets/PAGE_TEMPLATES.md +544 -0
  32. package/vendor/Server/Skills/flutter-demo-code-generator/references/CODE_STANDARDS.md +328 -0
  33. package/vendor/Server/Skills/flutter-demo-code-generator/references/DEMO_DOC_PARSING.md +126 -0
  34. package/vendor/Server/Skills/flutter-demo-code-generator/references/EXAMPLES.md +629 -0
  35. package/vendor/Server/Skills/flutter-demo-code-generator/scripts/validate-flutter-demo.cjs +268 -0
  36. package/vendor/Server/Skills/flutter-demo-doc-generator/SKILL.md +227 -0
  37. package/vendor/Server/Skills/flutter-demo-doc-generator/assets/DEMO_DOC_TEMPLATE.md +78 -0
  38. package/vendor/Server/Skills/flutter-demo-doc-generator/references/COVERAGE_REPORT_PARSING.md +174 -0
  39. package/vendor/Server/Skills/flutter-demo-doc-generator/references/EXAMPLES.md +162 -0
  40. package/vendor/Server/Skills/flutter-demo-doc-generator/references/MCP_TOOL_GUIDE.md +123 -0
  41. package/vendor/Server/Skills/flutter-demo-doc-generator/references/OUTPUT_FORMAT.md +190 -0
  42. package/vendor/Server/Skills/flutter-demo-doc-generator/references/QUALITY_CHECKLIST.md +83 -0
  43. package/vendor/Server/Skills/flutter-demo-doc-generator/scripts/validate-skill.cjs +259 -0
  44. package/vendor/Server/Skills/flutter-library-demo-coverage/SKILL.md +175 -0
  45. package/vendor/VERSION +3 -3
@@ -0,0 +1,268 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @tool validate-flutter-demo
4
+ * @description 校验 flutter-demo-code-generator 生成的 Demo 工程是否符合规范:目录/文件命名、_kTag 常量、dart:developer 导入、initState/dispose 完整性、mounted 检查、TODO/FIXME 残留、主导航 import 完整性、Demo 数量一致性。跨平台(Windows/macOS/Linux),仅需 Node.js。
5
+ * @param projectDir: string - Flutter 工程根目录(含 lib/)路径
6
+ * @param expectedDemoCount: number - 预期 Demo 页面数量(与描述文档条目数一致时传入,可选)
7
+ * @hidden
8
+ * validate-flutter-demo.cjs
9
+ *
10
+ * 用法:
11
+ * # CLI 模式
12
+ * node validate-flutter-demo.cjs /path/to/flutter/project [expectedDemoCount]
13
+ *
14
+ * # MCP Gateway 模式(第一个参数为 JSON 字符串)
15
+ * node validate-flutter-demo.cjs '{"projectDir":"/path/to/project","expectedDemoCount":4}'
16
+ */
17
+
18
+ 'use strict';
19
+
20
+ const fs = require('node:fs');
21
+ const path = require('node:path');
22
+
23
+ // ── 参数解析(兼容 MCP Gateway JSON 模式与 CLI 模式) ──────
24
+
25
+ function parseArgs(argv) {
26
+ if (argv.length === 0) return {};
27
+ const first = argv[0];
28
+ if (typeof first === 'string' && first.startsWith('{')) {
29
+ try { return JSON.parse(first); } catch (e) { return {}; }
30
+ }
31
+ const args = { projectDir: first };
32
+ if (argv.length > 1 && /^\d+$/.test(argv[1])) {
33
+ args.expectedDemoCount = parseInt(argv[1], 10);
34
+ }
35
+ return args;
36
+ }
37
+
38
+ // ── 输出统计 ─────────────────────────────────────────────
39
+
40
+ let PASS = 0;
41
+ let FAIL = 0;
42
+ let WARN = 0;
43
+
44
+ function checkPass(msg) { console.log(` ✅ ${msg}`); PASS += 1; }
45
+ function checkFail(msg) { console.log(` ❌ ${msg}`); FAIL += 1; }
46
+ function checkWarn(msg) { console.log(` ⚠️ ${msg}`); WARN += 1; }
47
+
48
+ // ── 工具函数 ─────────────────────────────────────────────
49
+
50
+ function readIfExists(file) {
51
+ try { return fs.readFileSync(file, 'utf-8'); } catch (e) { return null; }
52
+ }
53
+
54
+ function toUpperCamel(snake) {
55
+ return snake
56
+ .split('_')
57
+ .filter(Boolean)
58
+ .map((w) => w[0].toUpperCase() + w.slice(1))
59
+ .join('');
60
+ }
61
+
62
+ // ── 主流程 ───────────────────────────────────────────────
63
+
64
+ function main() {
65
+ const args = parseArgs(process.argv.slice(2));
66
+ const projectDir = args.projectDir || '';
67
+ const expected = args.expectedDemoCount;
68
+
69
+ if (!projectDir) {
70
+ console.log('Usage: node validate-flutter-demo.cjs <projectDir> [expectedDemoCount]');
71
+ console.log('Error: projectDir parameter is required');
72
+ process.exit(1);
73
+ }
74
+
75
+ if (!fs.existsSync(projectDir) || !fs.statSync(projectDir).isDirectory()) {
76
+ console.log(`Error: Directory does not exist: ${projectDir}`);
77
+ process.exit(1);
78
+ }
79
+
80
+ console.log('=== Flutter Demo 工程规范校验 ===');
81
+ console.log(`工程目录: ${projectDir}`);
82
+ console.log('');
83
+
84
+ // ─── 1. 目录结构检查 ───
85
+ console.log('--- 1. 目录结构检查 ---');
86
+
87
+ const libDir = path.join(projectDir, 'lib');
88
+ const pagesDir = path.join(libDir, 'pages');
89
+
90
+ if (fs.existsSync(pagesDir) && fs.statSync(pagesDir).isDirectory()) {
91
+ checkPass('lib/pages/ 目录存在');
92
+ } else {
93
+ checkFail('lib/pages/ 目录不存在');
94
+ }
95
+
96
+ const pubspec = readIfExists(path.join(projectDir, 'pubspec.yaml'));
97
+ if (pubspec) {
98
+ checkPass('pubspec.yaml 存在');
99
+ } else {
100
+ checkFail('pubspec.yaml 不存在');
101
+ }
102
+
103
+ const homePagePath = path.join(pagesDir, 'demo_home_page.dart');
104
+ const homePage = readIfExists(homePagePath);
105
+ if (homePage) {
106
+ checkPass('lib/pages/demo_home_page.dart 主导航页存在');
107
+ } else {
108
+ checkFail('lib/pages/demo_home_page.dart 主导航页不存在');
109
+ }
110
+
111
+ // 收集 Demo 目录
112
+ const demoDirs = [];
113
+ if (fs.existsSync(pagesDir)) {
114
+ for (const entry of fs.readdirSync(pagesDir, { withFileTypes: true })) {
115
+ if (entry.isDirectory()) demoDirs.push(entry.name);
116
+ }
117
+ }
118
+ demoDirs.sort();
119
+
120
+ const dirNameRe = /^\d{3}_[a-z][a-z0-9_]*$/;
121
+ const badDirs = demoDirs.filter((d) => !dirNameRe.test(d));
122
+ if (demoDirs.length > 0 && badDirs.length === 0) {
123
+ checkPass(`Demo 目录命名合规(${demoDirs.length} 个,格式 {三位序号}_{snake_case})`);
124
+ } else if (badDirs.length > 0) {
125
+ checkFail(`Demo 目录命名不合规: ${badDirs.join(', ')}`);
126
+ } else {
127
+ checkWarn('未发现任何 Demo 目录');
128
+ }
129
+
130
+ // 每个 Demo 目录的页面文件命名与匹配
131
+ const pageFiles = [];
132
+ for (const dir of demoDirs) {
133
+ if (!dirNameRe.test(dir)) continue;
134
+ const snakePart = dir.slice(4); // 去掉 "001_"
135
+ const dirPath = path.join(pagesDir, dir);
136
+ const dartFiles = fs.readdirSync(dirPath).filter((f) => f.endsWith('.dart'));
137
+ const expectedFile = `${snakePart}_page.dart`;
138
+ if (dartFiles.includes(expectedFile)) {
139
+ checkPass(`${dir}/${expectedFile} 存在且命名匹配`);
140
+ pageFiles.push({ dir, file: expectedFile, fullPath: path.join(dirPath, expectedFile), snakePart });
141
+ } else {
142
+ checkFail(`${dir}/ 缺少 ${expectedFile}(实际: ${dartFiles.join(', ') || '无 Dart 文件'})`);
143
+ }
144
+ for (const f of dartFiles) {
145
+ if (f !== expectedFile && !/^[a-z][a-z0-9_]*\.dart$/.test(f)) {
146
+ checkWarn(`${dir}/${f} 文件名不符合 lower_snake_case`);
147
+ }
148
+ }
149
+ }
150
+
151
+ // ─── 2. 页面代码质量检查 ───
152
+ console.log('');
153
+ console.log('--- 2. 页面代码质量检查 ---');
154
+
155
+ for (const pf of pageFiles) {
156
+ const content = readIfExists(pf.fullPath);
157
+ if (!content) {
158
+ checkFail(`${pf.dir}: 无法读取 ${pf.file}`);
159
+ continue;
160
+ }
161
+ const label = `${pf.dir}/${pf.file}`;
162
+ const issues = [];
163
+
164
+ if (!/import\s+'dart:developer'/.test(content)) {
165
+ issues.push('缺少 dart:developer 导入(dev.log)');
166
+ }
167
+ if (!/const\s+String\s+_kTag\s*=/.test(content)) {
168
+ issues.push('缺少 _kTag 日志常量声明');
169
+ }
170
+ if (!/\binitState\s*\(/.test(content)) {
171
+ issues.push('缺少 initState');
172
+ }
173
+ if (!/\bdispose\s*\(/.test(content)) {
174
+ issues.push('缺少 dispose');
175
+ } else if (!/super\.dispose\(\)/.test(content)) {
176
+ issues.push('dispose 未调用 super.dispose()');
177
+ }
178
+ if (/\bawait\b/.test(content) && !/\bmounted\b/.test(content)) {
179
+ issues.push('含 await 但缺少 mounted 检查');
180
+ }
181
+ if (/\bStreamSubscription\b/.test(content) && !/\bmounted\b/.test(content)) {
182
+ issues.push('含 StreamSubscription 但缺少 mounted 检查');
183
+ }
184
+ if (/\b(TODO|FIXME)\b/.test(content)) {
185
+ issues.push('存在 TODO/FIXME 残留(须填充实际内容)');
186
+ }
187
+ const className = `${toUpperCamel(pf.snakePart)}Page`;
188
+ if (!content.includes(className)) {
189
+ issues.push(`未找到类名 ${className}`);
190
+ }
191
+
192
+ if (issues.length === 0) {
193
+ checkPass(`${label} 质量检查通过`);
194
+ } else {
195
+ for (const iss of issues) checkFail(`${label}: ${iss}`);
196
+ }
197
+ }
198
+
199
+ // ─── 3. 主导航与应用入口检查 ───
200
+ console.log('');
201
+ console.log('--- 3. 主导航与应用入口检查 ---');
202
+
203
+ if (homePage) {
204
+ const importRe = /import\s+'(\d{3}_[a-z0-9_]+\/[a-z0-9_]+_page\.dart)'\s*;/g;
205
+ const imports = [...homePage.matchAll(importRe)];
206
+ const validDirs = demoDirs.filter((d) => dirNameRe.test(d));
207
+ if (validDirs.length > 0 && imports.length === validDirs.length) {
208
+ checkPass(`主导航 import 数量与 Demo 目录数一致(${imports.length})`);
209
+ } else if (validDirs.length > 0) {
210
+ checkFail(`主导航 import 数量(${imports.length})与 Demo 目录数(${validDirs.length})不一致`);
211
+ }
212
+ for (const d of validDirs) {
213
+ const snakePart = d.slice(4);
214
+ const expectedImport = `${d}/${snakePart}_page.dart`;
215
+ if (!homePage.includes(`'${expectedImport}'`)) {
216
+ checkFail(`主导航缺少 import: ${expectedImport}`);
217
+ }
218
+ }
219
+ if (/class\s+DemoHomePage\b/.test(homePage)) {
220
+ checkPass('主导航包含 DemoHomePage 类');
221
+ } else {
222
+ checkFail('主导航缺少 DemoHomePage 类');
223
+ }
224
+ }
225
+
226
+ const mainDart = readIfExists(path.join(libDir, 'main.dart'));
227
+ if (mainDart) {
228
+ if (/demo_home_page\.dart|DemoHomePage/.test(mainDart)) {
229
+ checkPass('main.dart 引用主导航页');
230
+ } else {
231
+ checkFail('main.dart 未引用 demo_home_page.dart / DemoHomePage');
232
+ }
233
+ } else {
234
+ checkFail('lib/main.dart 不存在');
235
+ }
236
+
237
+ // ─── 4. 数量一致性检查 ───
238
+ console.log('');
239
+ console.log('--- 4. 数量一致性检查 ---');
240
+
241
+ if (typeof expected === 'number' && Number.isFinite(expected)) {
242
+ if (pageFiles.length === expected) {
243
+ checkPass(`Demo 页面数量一致(${pageFiles.length} = ${expected})`);
244
+ } else {
245
+ checkFail(`Demo 页面数量不一致: 生成 ${pageFiles.length},预期 ${expected}`);
246
+ }
247
+ } else {
248
+ checkWarn(`未提供 expectedDemoCount,跳过数量比对(当前 Demo 页面: ${pageFiles.length})`);
249
+ }
250
+
251
+ // ─── 5. 总结 ───
252
+ console.log('');
253
+ console.log('=== 校验总结 ===');
254
+ console.log(` ✅ 通过: ${PASS}`);
255
+ console.log(` ❌ 失败: ${FAIL}`);
256
+ console.log(` ⚠️ 警告: ${WARN}`);
257
+ console.log('');
258
+
259
+ if (FAIL === 0) {
260
+ console.log('ALL CHECKS PASSED');
261
+ process.exit(0);
262
+ } else {
263
+ console.log(`VALIDATION FAILED: ${FAIL} 个必须修复的问题`);
264
+ process.exit(1);
265
+ }
266
+ }
267
+
268
+ main();
@@ -0,0 +1,227 @@
1
+ ---
2
+ name: flutter-demo-doc-generator
3
+ description: >-
4
+ 基于 Demo 覆盖率报告(flutter-library-demo-coverage 技能产物)自动生成 Flutter 三方库的 Demo 描述说明文档。
5
+ 解析覆盖率报告中的文件覆盖详情、已覆盖接口详情与未覆盖接口详情,以 Demo 文件为单位生成测试 Demo 描述
6
+ (名称、描述、步骤、预期结果、对应函数接口、源码位置),以表格形式汇总输出为"{库名}Flutter测试demo描述.md",
7
+ 并附接口映射矩阵与未覆盖接口附录。覆盖率报告缺失时自动调用 flutter-analyze-demo-coverage 脚本生成。
8
+ 当需要为已有 Demo 生成描述文档、梳理各 Demo 覆盖的接口能力、或为 QA 与开发者提供 Demo 验证指导时使用此技能。
9
+ license: Apache-2.0
10
+ compatibility: 需要 Node.js 运行环境(flutter-analyze-demo-coverage.cjs);MCP Gateway 运行中时可改用 script_flutter_analyze_demo_coverage 工具;需要覆盖率报告与输出目录的读写权限
11
+ metadata:
12
+ author: lalhan
13
+ version: "1.0.0"
14
+ category: flutter-development
15
+ language: Dart/Flutter
16
+ useWhen: 基于 Demo 覆盖率报告生成 Flutter 三方库 Demo 描述文档
17
+ ---
18
+
19
+ # Flutter 三方库 Demo 描述文档生成
20
+
21
+ 本技能基于 **Demo 覆盖率报告**(`flutter-library-demo-coverage` 技能产物),以 Demo 文件为单位生成测试 Demo 描述,输出为 `{库名}Flutter测试demo描述.md`。
22
+
23
+ 数据流:**Demo 覆盖率报告 → Demo 描述文档**。覆盖率报告提供 Demo 文件清单(文件覆盖详情)、接口签名(已覆盖接口详情)与缺口清单(未覆盖接口详情),本技能**不直接分析 Demo 源码**。
24
+
25
+ > ⚠️ **核心约束**:所有 Demo 描述必须基于覆盖率报告中真实存在的数据生成(Demo 文件、接口签名、覆盖关系),严禁捏造报告中不存在的接口或文件。步骤与预期结果为基于接口签名的验证设计,实际 Demo 实现以源码为准(须在概述中声明)。
26
+
27
+ ---
28
+
29
+ ## 任务参数
30
+
31
+ | 参数 | 类型 | 必填 | 说明 |
32
+ |------|------|------|------|
33
+ | `libroot` | string | ✅ | 库根目录(含 `lib/` 与 `example/`),用于定位/生成覆盖率报告与输出文档 |
34
+ | `coverageReport` | string | ❌ | 已有覆盖率报告路径(`flutter-library-demo-coverage` 产物);缺失时自动发现或生成 |
35
+ | `spec` | string | ❌ | 已有 `interface-spec.json` 路径;生成覆盖率报告时传入,避免重复提取接口规格 |
36
+ | `output` | string | ❌ | 输出文件路径,默认 `{libroot}/{库名}Flutter测试demo描述.md` |
37
+
38
+ **派生变量**:
39
+ - `libName`:从 `pubspec.yaml` 的 `name` 字段提取(与报告"基本信息"的库名核对)
40
+ - `reportPath`:`coverageReport` 参数、自动发现的已有报告、或 Phase 1 生成的报告路径
41
+ - `coveredList`:Phase 2 解析的已覆盖接口列表(含签名与覆盖文件)
42
+ - `uncoveredList`:Phase 2 解析的未覆盖接口列表
43
+ - `demoFileMap`:Phase 2 构建的 Demo 文件 → 覆盖接口完整映射
44
+
45
+ ---
46
+
47
+ ## 输入依赖
48
+
49
+ - **前置 SKILL**: `flutter-library-demo-coverage`(Demo 覆盖率报告来源;报告缺失时本技能调用其脚本自动生成)
50
+ - **输入文件**: Demo 覆盖率报告(Markdown,必需);`interface-spec.json`(可选,生成报告时复用)
51
+ - **MCP 工具 / 脚本**: `script_flutter_analyze_demo_coverage`(MCP 工具)或 `flutter-analyze-demo-coverage.cjs`(CLI 直调)
52
+ - **输出文件**: `{libName}Flutter测试demo描述.md`
53
+
54
+ ---
55
+
56
+ ## 工作流程概览
57
+
58
+ ```
59
+ Phase 1: Demo 覆盖率报告获取(复用 flutter-library-demo-coverage)
60
+
61
+ Phase 2: 覆盖率报告解析(文件覆盖详情 / 已覆盖 / 未覆盖 / 统计)
62
+
63
+ Phase 3: Demo 分组(按 Demo 文件分组,超量文件按功能簇拆分)
64
+
65
+ Phase 4: Demo 描述生成(六要素 + 验证设计规则)
66
+
67
+ Phase 5: 文档生成与质量校验
68
+ ```
69
+
70
+ ---
71
+
72
+ ## Phase 1:Demo 覆盖率报告获取
73
+
74
+ ### 1.1 输入判定
75
+
76
+ | 输入情况 | 获取方式 |
77
+ |----------|----------|
78
+ | 提供了 `coverageReport` | 直接使用该报告 |
79
+ | `libroot` 下已有覆盖率报告 | 搜索 `02-{libName}-Demo覆盖率报告.md` 或 `*Demo覆盖率报告*.md`,使用最新一份 |
80
+ | 均无 | 调用 `flutter-library-demo-coverage` 的脚本生成(见 1.2) |
81
+
82
+ ### 1.2 自动生成覆盖率报告(必要时)
83
+
84
+ 优先使用 MCP 工具(Gateway 运行中时):
85
+
86
+ ```
87
+ 调用 script_flutter_analyze_demo_coverage:
88
+ - libroot: {{libroot}}
89
+ - spec: {{spec}}(可选)
90
+ - output: {{libroot}}/02-{libName}-Demo覆盖率报告.md
91
+ ```
92
+
93
+ 或直接执行脚本(CLI 方式):
94
+
95
+ ```bash
96
+ node "{{skillDir}}/../../../MCP/scripts/flutter-analyze-demo-coverage.cjs" \
97
+ --libroot "{{libroot}}" \
98
+ --output "{{libroot}}/02-{{libName}}-Demo覆盖率报告.md"
99
+ ```
100
+
101
+ ### 1.3 报告有效性校验
102
+
103
+ - 确认报告包含**文件覆盖详情**、**已覆盖接口详情**、**未覆盖接口详情**章节
104
+ - 覆盖率 < 10% 或为 0% 且 `example/lib` 确有源码时,按 `flutter-library-demo-coverage` Phase 3 的交叉验证方法复核(grep 抽样),必要时重新生成后再进入 Phase 2
105
+ - "已覆盖接口详情"为空(显示 `*暂无已覆盖接口*`)时**终止**:无 Demo 可描述,提示先创建 Demo
106
+
107
+ > 脚本调用参数与已知匹配限制参见 [MCP 工具使用指南](references/MCP_TOOL_GUIDE.md)
108
+
109
+ ---
110
+
111
+ ## Phase 2:覆盖率报告解析
112
+
113
+ ### 2.1 章节提取
114
+
115
+ | 报告章节 | 提取内容 | 用途 |
116
+ |----------|----------|------|
117
+ | 基本信息 | 库名、源文件数 | `libName` 核对 |
118
+ | 覆盖率总览 | S/N/M 统计、方法级覆盖率 | 输出文档概述统计 |
119
+ | 类级覆盖率 | 每类覆盖统计 | Phase 3 功能域划分参考 |
120
+ | 已覆盖接口详情 | 按类分组:接口名、类型、静态、**签名**、**覆盖文件** | 签名来源 + 接口→Demo 文件映射(构建 `coveredList`) |
121
+ | 未覆盖接口详情 | 按类分组:接口名、类型、静态、参数、返回类型、说明 | 附录"未覆盖接口清单"(构建 `uncoveredList`) |
122
+ | 文件覆盖详情 | **文件、覆盖接口数、接口列表** | **Demo 单元清单**(构建 `demoFileMap`,Phase 3 分组依据) |
123
+
124
+ ### 2.2 截断重建(强制)
125
+
126
+ 报告为控制篇幅做了截断,解析时必须交叉重建完整映射:
127
+
128
+ | 截断点 | 表现 | 重建方法 |
129
+ |--------|------|----------|
130
+ | 文件覆盖详情的接口列表 | 每文件最多显示 10 个,超出显示 `... (+N)` | 用"已覆盖接口详情"各接口的"覆盖文件"列反向归集 |
131
+ | 已覆盖接口详情的覆盖文件 | 每接口最多显示 3 个,超出显示 `+N` | 两章节交叉后仍有不确定的接口-文件归属,用 `grep` 在对应 Demo 文件中复核接口名(仅作报告截断的补救,不展开源码分析) |
132
+
133
+ ### 2.3 统计口径对齐
134
+
135
+ Demo 描述的接口范围与报告口径一致:`constant`(常量)、`enumValue`(枚举值)、`constructor`(构造函数)、`type`(typedef)、`createState` 及 Object 内置成员(`hashCode`/`toString` 等)**不计入**——报告已将其排除,描述与映射矩阵中也不得出现。
136
+
137
+ > 各章节表格的精确格式、解析步骤与边界情况参见 [覆盖率报告解析指南](references/COVERAGE_REPORT_PARSING.md)
138
+
139
+ ---
140
+
141
+ ## Phase 3:Demo 分组
142
+
143
+ ### 3.1 分组规则
144
+
145
+ | 规则 | 说明 |
146
+ |------|------|
147
+ | 默认粒度 | "文件覆盖详情"中每个 Demo 文件 = 一个 Demo 单元(文件均有 ≥1 个覆盖接口) |
148
+ | 超量拆分 | 单文件覆盖接口 > 10 个时,按功能簇拆分为多条描述(同一源码文件);功能簇依据"已覆盖接口详情"的类分组与接口语义 |
149
+ | 禁止无关合并 | 不同 Demo 文件的接口不得合并为一条描述 |
150
+ | 多文件覆盖 | 同一接口被多个文件覆盖时,可出现在多个 Demo 描述中,映射矩阵注明全部所在 Demo |
151
+
152
+ ### 3.2 分组产物(内部工作记录)
153
+
154
+ 每个 Demo 单元记录:**Demo 文件路径、覆盖接口清单(含签名)、接口数量、(拆分时的)功能簇说明**。
155
+
156
+ ---
157
+
158
+ ## Phase 4:Demo 描述生成(六要素)
159
+
160
+ 对每个 Demo 单元生成以下六个要素:
161
+
162
+ | 要素 | 说明 |
163
+ |------|------|
164
+ | ① 测试 Demo 名称 | `[功能动词/名词][操作对象][场景]Demo`,≤20 字,从文件名语义/覆盖接口功能簇提炼 |
165
+ | ② 测试 Demo 描述 | 2-4 句话,说明该 Demo(文件)覆盖的接口能力与验证目的 |
166
+ | ③ 测试 Demo 步骤 | 编号列表,以 Flutter Demo 页面(StatefulWidget)为载体的验证步骤:页面构建、交互触发、接口调用(含参数)、`setState()` 更新、结果展示 |
167
+ | ④ 测试 Demo 预期结果 | 编号列表,可观测指标(UI 展示、返回值、状态变化、无异常) |
168
+ | ⑤ 对应函数接口 | 该 Demo 覆盖的接口签名,取自报告"已覆盖接口详情"的签名列 |
169
+ | ⑥ 源码位置 | Demo 文件路径(报告"文件覆盖详情"的文件列) |
170
+
171
+ **验证设计规则(强制,适用于步骤与预期结果)**:
172
+
173
+ - **禁止死按钮**:每个触发接口调用的步骤必须对应至少一条可观测的预期结果
174
+ - **setState 强制**:步骤中的计数器、状态标志等运行时变量必须注明通过 `setState()` 更新,否则 Flutter 不触发重建、计数器恒显示初始值
175
+ - **异步双计数器**:涉及"调用 → 等待回调/Future"的异步链路必须设计双计数器(`_xxxCallCount` 调用次数 + `_xxxCallbackCount` 回调触发次数),均通过 `setState()` 更新并在验证区展示;两者对比可诊断链路断点(调用 >0 且回调 =0 → 回调注册/事件链问题)
176
+ - **签名忠实**:步骤中的接口调用与报告签名一致,参数类型不得篡改;报告无默认值信息时不得编造具体默认值
177
+
178
+ > 各要素写作规范与完整示例参见 [输出格式规范](references/OUTPUT_FORMAT.md) 与 [示例](references/EXAMPLES.md)
179
+
180
+ ---
181
+
182
+ ## Phase 5:文档生成与质量校验
183
+
184
+ ### 5.1 生成输出文档
185
+
186
+ 章节结构(模板参见 [Demo 文档模板](assets/DEMO_DOC_TEMPLATE.md)):
187
+
188
+ 1. **概述**:来源声明(基于 Demo 覆盖率报告 `reportPath` 生成;步骤为验证设计,实际实现以源码为准)+ 覆盖率统计引用
189
+ 2. **Demo 总览**:Demo 数量、覆盖接口数量、生成日期
190
+ 3. **测试 Demo 描述列表**:七列表格(序号、名称、描述、步骤、预期结果、对应函数接口、源码位置)
191
+ 4. **接口映射矩阵**:接口名称、所在 Demo、覆盖来源文件
192
+ 5. **附录:未覆盖接口清单**:来自 `uncoveredList`,供 Demo 补全参考(不为其生成 Demo 描述)
193
+
194
+ ### 5.2 质量校验
195
+
196
+ | 检查项 | 标准 |
197
+ |--------|------|
198
+ | Demo 覆盖完整性 | "文件覆盖详情"中每个文件至少一条描述(超量拆分时为多条) |
199
+ | 接口一致性 | 报告中每个已覆盖接口(计入统计口径)至少出现在一个 Demo 描述中 |
200
+ | 内容真实性 | 接口签名、Demo 文件、覆盖关系与报告严格一致,无捏造 |
201
+ | 步骤可执行性 | 步骤具体,开发者可按步骤实现/验证 Demo 页面 |
202
+ | 预期结果可验证 | 每条预期结果可观测、可判断 |
203
+ | 无死按钮 | 每个接口调用步骤有可观测预期结果 |
204
+ | setState 完整性 | 计数器/状态标志均注明 `setState()` 更新;异步链路有双计数器 |
205
+ | 映射矩阵一致 | 矩阵的接口→Demo 归属与报告"覆盖文件"关系一致 |
206
+ | 未覆盖附录一致 | 附录清单与报告"未覆盖接口详情"一致 |
207
+
208
+ > 完整检查清单参见 [质量检查清单](references/QUALITY_CHECKLIST.md)
209
+
210
+ ### 5.3 输出产物
211
+
212
+ - **`{outputPath}`** — `{libName}Flutter测试demo描述.md`(**始终生成**)
213
+ - 可选产物:`{libroot}/02-{libName}-Demo覆盖率报告.md`(Phase 1 自动生成的报告)
214
+
215
+ 输出确认信息包含:生成的文件列表、Demo 数量、覆盖接口数量、方法级覆盖率、未覆盖接口数量、报告来源(提供/发现/自动生成)。
216
+
217
+ ---
218
+
219
+ ## 参考资料
220
+
221
+ - [覆盖率报告解析指南](references/COVERAGE_REPORT_PARSING.md) — 报告章节格式、提取步骤、截断重建、口径对齐
222
+ - [MCP 工具使用指南](references/MCP_TOOL_GUIDE.md) — 覆盖率脚本调用方式与已知匹配限制
223
+ - [输出格式规范](references/OUTPUT_FORMAT.md) — 文档结构、字段规范、验证设计规则
224
+ - [示例](references/EXAMPLES.md) — 覆盖率报告 → 输出文档的完整示例
225
+ - [质量检查清单](references/QUALITY_CHECKLIST.md) — 完整质量校验项
226
+ - [Demo 文档模板](assets/DEMO_DOC_TEMPLATE.md) — 可直接复制的输出模板
227
+ - 前置技能:`flutter-library-demo-coverage`(Demo 覆盖率分析)
@@ -0,0 +1,78 @@
1
+ # Demo 描述文档模板
2
+
3
+ > 生成 Demo 描述文档时可直接复制此模板,替换 `{占位符}` 内容。字段填写规范参见 [输出格式规范](../references/OUTPUT_FORMAT.md),完整示例参见 [示例](../references/EXAMPLES.md)。
4
+
5
+ ---
6
+
7
+ ## 模板
8
+
9
+ ```markdown
10
+ # {库名} Flutter 测试 Demo 描述文档
11
+
12
+ ## 概述
13
+
14
+ 本文档基于 `{库名}` 库的 Demo 覆盖率报告(`{reportPath}`)生成,以 Demo 文件为单位描述
15
+ 各 Demo 覆盖的接口能力,供 QA 与开发者了解 Demo 验证范围并参考实现/验证 Demo 页面。
16
+
17
+ > **数据来源**:Demo 覆盖率报告 `{reportPath}`(方法级覆盖率 {覆盖率}%,已覆盖 {M}/{N} 个接口)
18
+ >
19
+ > **说明**:步骤与预期结果为基于接口签名的验证设计,实际 Demo 实现以源码为准。
20
+
21
+ ## Demo 总览
22
+
23
+ | Demo 数量 | 覆盖接口数量 | 生成日期 |
24
+ |----------|------------|---------|
25
+ | {D} 个 | {M} 个 | {YYYY-MM-DD} |
26
+
27
+ ## 测试 Demo 描述列表
28
+
29
+ | 序号 | 测试 Demo 名称 | 测试 Demo 描述 | 测试 Demo 步骤 | 测试 Demo 预期结果 | 对应函数接口 | 源码位置 |
30
+ |-----|--------------|--------------|--------------|-----------------|------------|---------|
31
+ | 1 | {Demo名称} | {2-4句话:覆盖的接口能力与验证目的} | 1. 构建 StatefulWidget 页面,声明 {状态变量}<br>2. 点击"{按钮}",`setState(() => _xxxCallCount++)` 后调用 `{接口}({参数})`<br>3. 在回调/then 中 `setState(() { ... })` 更新状态<br>4. 验证区展示 `Text('调用次数: $_xxxCallCount')` 等 | 1. {可观测的UI变化}<br>2. {返回值/状态变化}<br>3. {无异常} | `{签名}`<br>`{签名}` | `{Demo文件路径}` |
32
+ | 2 | {Demo名称} | ... | ... | ... | ... | ... |
33
+
34
+ ## 接口映射矩阵
35
+
36
+ | 序号 | 接口名称 | 所在 Demo | 覆盖来源文件 |
37
+ |-----|---------|----------|------------|
38
+ | 1 | `{接口签名}` | {Demo名称} | `{Demo文件路径}` |
39
+ | 2 | `{接口签名}` | {Demo名称} | `{Demo文件路径}` |
40
+
41
+ ## 附录:未覆盖接口清单
42
+
43
+ 以下接口在 Demo 覆盖率报告中被标记为未覆盖({K} 个),供后续 Demo 补全参考:
44
+
45
+ | # | 接口 | 类型 | 静态 | 参数 | 返回类型 | 说明 |
46
+ |---|------|------|------|------|----------|------|
47
+ | 1 | `{接口名}` | {类型} | {是/否} | {参数} | {返回类型} | {说明} |
48
+ ```
49
+
50
+ ---
51
+
52
+ ## 占位符说明
53
+
54
+ | 占位符 | 来源 |
55
+ |--------|------|
56
+ | `{库名}` | `pubspec.yaml` 的 `name` 字段(与报告"基本信息"核对) |
57
+ | `{reportPath}` | Phase 1 确定的覆盖率报告路径 |
58
+ | `{覆盖率}` / `{M}` / `{N}` | 报告"覆盖率总览"(方法级覆盖率、已覆盖数、方法/属性总数) |
59
+ | `{D}` | 描述列表条目数(按 Demo 文件,超量拆分时为拆分后条数) |
60
+ | `{Demo名称}` | 从文件覆盖接口的功能簇/文件名语义提炼,≤20 字 |
61
+ | `{接口}({参数})` | 报告"已覆盖接口详情"的签名,逐字一致 |
62
+ | `{Demo文件路径}` | 报告"文件覆盖详情"的文件列(含 `example/lib/` 前缀) |
63
+ | 未覆盖清单表格 | 报告"未覆盖接口详情"直接复用(为 0 时改为"所有接口均已覆盖") |
64
+
65
+ ---
66
+
67
+ ## 超量拆分场景
68
+
69
+ 单个 Demo 文件覆盖接口 > 10 个时,按功能簇拆分为多条描述:
70
+
71
+ ```markdown
72
+ | 5 | 播放控制Demo({文件A} 功能簇1) | ... | ... | ... | ... | `example/lib/xxx_page.dart` |
73
+ | 6 | 进度监听Demo({文件A} 功能簇2) | ... | ... | ... | ... | `example/lib/xxx_page.dart` |
74
+ ```
75
+
76
+ - 各条"源码位置"相同(同一文件)
77
+ - 各条接口集合的并集 = 该文件在报告中的完整覆盖接口集
78
+ - 名称须体现功能簇差异