@ohos-cpf/3rdloop 0.0.11 → 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.
- package/lib/cli.js +2 -2
- package/package.json +1 -1
- package/vendor/Server/Routes/controllers/LoopEngineController.js +36 -3
- package/vendor/Server/Routes/controllers/OrchestratorController.js +193 -9
- package/vendor/Server/Skills/flutter-build-test/SKILL.md +252 -0
- package/vendor/Server/Skills/flutter-build-test/assets/BUILDENV_TEMPLATE.md +42 -0
- package/vendor/Server/Skills/flutter-build-test/assets/BUILD_REPORT_TEMPLATE.md +64 -0
- package/vendor/Server/Skills/flutter-build-test/assets/README_SECTION_TEMPLATE.md +78 -0
- package/vendor/Server/Skills/flutter-build-test/references/BUILD_TROUBLESHOOTING.md +128 -0
- package/vendor/Server/Skills/flutter-build-test/references/DOC_UPDATE_GUIDE.md +119 -0
- package/vendor/Server/Skills/flutter-build-test/references/FLVM_GUIDE.md +89 -0
- package/vendor/Server/Skills/flutter-build-test/scripts/build-matrix.cjs +374 -0
- package/vendor/Server/Skills/flutter-build-test/scripts/locate-example.cjs +189 -0
- package/vendor/Server/Skills/flutter-build-test/scripts/update-buildenv.cjs +171 -0
- package/vendor/Server/Skills/flutter-code-use/SKILL.md +316 -0
- package/vendor/Server/Skills/flutter-code-use/references/event-channel.md +440 -0
- package/vendor/Server/Skills/flutter-code-use/references/federated.md +295 -0
- package/vendor/Server/Skills/flutter-code-use/references/ffi-binding-translate.md +130 -0
- package/vendor/Server/Skills/flutter-code-use/references/ffi-compile-from-source.md +169 -0
- package/vendor/Server/Skills/flutter-code-use/references/ffi-fetch-at-build.md +161 -0
- package/vendor/Server/Skills/flutter-code-use/references/ffi-prebuilt-bundle.md +175 -0
- package/vendor/Server/Skills/flutter-code-use/references/ffi-rhttp-guide.md +235 -0
- package/vendor/Server/Skills/flutter-code-use/references/ffi-rust-cross-compile.md +514 -0
- package/vendor/Server/Skills/flutter-code-use/references/ffi.md +220 -0
- package/vendor/Server/Skills/flutter-code-use/references/method-channel.md +643 -0
- package/vendor/Server/Skills/flutter-code-use/references/monorepo.md +188 -0
- package/vendor/Server/Skills/flutter-code-use/references/ohos-api-pitfalls.md +717 -0
- package/vendor/Server/Skills/flutter-code-use/references/platform-view.md +448 -0
- package/vendor/Server/Skills/flutter-code-use/references/pure-dart.md +180 -0
- package/vendor/Server/Skills/flutter-code-use/references/texture.md +459 -0
- package/vendor/Server/Skills/flutter-demo-code-generator/SKILL.md +270 -0
- package/vendor/Server/Skills/flutter-demo-code-generator/assets/PAGE_TEMPLATES.md +544 -0
- package/vendor/Server/Skills/flutter-demo-code-generator/references/CODE_STANDARDS.md +328 -0
- package/vendor/Server/Skills/flutter-demo-code-generator/references/DEMO_DOC_PARSING.md +126 -0
- package/vendor/Server/Skills/flutter-demo-code-generator/references/EXAMPLES.md +629 -0
- package/vendor/Server/Skills/flutter-demo-code-generator/scripts/validate-flutter-demo.cjs +268 -0
- package/vendor/Server/Skills/flutter-demo-doc-generator/SKILL.md +227 -0
- package/vendor/Server/Skills/flutter-demo-doc-generator/assets/DEMO_DOC_TEMPLATE.md +78 -0
- package/vendor/Server/Skills/flutter-demo-doc-generator/references/COVERAGE_REPORT_PARSING.md +174 -0
- package/vendor/Server/Skills/flutter-demo-doc-generator/references/EXAMPLES.md +162 -0
- package/vendor/Server/Skills/flutter-demo-doc-generator/references/MCP_TOOL_GUIDE.md +123 -0
- package/vendor/Server/Skills/flutter-demo-doc-generator/references/OUTPUT_FORMAT.md +190 -0
- package/vendor/Server/Skills/flutter-demo-doc-generator/references/QUALITY_CHECKLIST.md +83 -0
- package/vendor/Server/Skills/flutter-demo-doc-generator/scripts/validate-skill.cjs +259 -0
- package/vendor/Server/Skills/flutter-library-demo-coverage/SKILL.md +175 -0
- package/vendor/VERSION +3 -3
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# 质量检查清单
|
|
2
|
+
|
|
3
|
+
> 生成 Demo 描述文档前的完整校验清单,是 SKILL.md Phase 5.2 的详细展开。逐项检查并记录结果。
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. 报告获取与有效性检查
|
|
8
|
+
|
|
9
|
+
| # | 检查项 | 标准 | 验证方法 |
|
|
10
|
+
|---|--------|------|----------|
|
|
11
|
+
| 1.1 | 报告存在 | `reportPath` 指向的覆盖率报告存在且可读 | 文件读取 |
|
|
12
|
+
| 1.2 | 章节完整 | 包含文件覆盖详情、已覆盖接口详情、未覆盖接口详情 | 检查章节标题 |
|
|
13
|
+
| 1.3 | 覆盖率合理 | 覆盖率异常低(<10% 或 0)时已执行交叉验证/重新生成 | 对照 grep 抽样结果 |
|
|
14
|
+
| 1.4 | 有可描述对象 | "已覆盖接口详情"非空(无 `*暂无已覆盖接口*`) | 检查章节内容 |
|
|
15
|
+
|
|
16
|
+
## 2. 覆盖完整性检查
|
|
17
|
+
|
|
18
|
+
| # | 检查项 | 标准 | 验证方法 |
|
|
19
|
+
|---|--------|------|----------|
|
|
20
|
+
| 2.1 | Demo 文件全覆盖 | "文件覆盖详情"中每个文件至少一条描述(超量拆分时多条) | 对照 `demoFileMap` 与描述列表 |
|
|
21
|
+
| 2.2 | 接口全出现 | 报告中每个已覆盖接口(计入统计口径)至少出现在一个 Demo 描述中 | 对照 `coveredList` 与描述列表/矩阵 |
|
|
22
|
+
| 2.3 | 无多余条目 | 描述列表中没有报告外的 Demo 文件或接口 | 逐条核对来源 |
|
|
23
|
+
| 2.4 | 拆分完整 | 超量拆分文件的接口集合 = 拆分后各条目接口集合的并集 | 集合对账 |
|
|
24
|
+
|
|
25
|
+
## 3. 真实性检查
|
|
26
|
+
|
|
27
|
+
| # | 检查项 | 标准 | 验证方法 |
|
|
28
|
+
|---|--------|------|----------|
|
|
29
|
+
| 3.1 | 签名一致 | 描述中接口签名与报告"已覆盖接口详情"逐字一致 | 文本比对 |
|
|
30
|
+
| 3.2 | 文件路径真实 | "源码位置"与报告"文件覆盖详情"的文件列一致 | 文本比对 |
|
|
31
|
+
| 3.3 | 覆盖关系一致 | 描述/矩阵中的接口→Demo 归属与报告"覆盖文件"关系一致(截断重建已对账) | 对照 `demoFileMap` |
|
|
32
|
+
| 3.4 | 无捏造内容 | 未出现报告中不存在的接口、参数默认值、回调行为 | 逐条核对 |
|
|
33
|
+
| 3.5 | 口径对齐 | 无 constant/enumValue/constructor/type/createState/Object 内置成员出现在描述或矩阵 | 逐一核对接口类型 |
|
|
34
|
+
|
|
35
|
+
## 4. 验证设计质量检查
|
|
36
|
+
|
|
37
|
+
| # | 检查项 | 标准 | 验证方法 |
|
|
38
|
+
|---|--------|------|----------|
|
|
39
|
+
| 4.1 | 步骤可执行 | 步骤以 StatefulWidget 页面为载体,具体到状态变量、按钮、展示区 | 逐条心智演练 |
|
|
40
|
+
| 4.2 | 预期可验证 | 每条预期结果可观测(UI 变化/返回值/计数/无异常) | 逐条确认观测点 |
|
|
41
|
+
| 4.3 | 无死按钮 | 每个接口调用步骤至少对应一条可观测预期结果 | 步骤↔预期逐条配对 |
|
|
42
|
+
| 4.4 | setState 完整 | 计数器/状态标志均注明 `setState()` 更新 | 检查步骤文本 |
|
|
43
|
+
| 4.5 | 双计数器 | 涉及异步链路(Future/回调)的 Demo 设计了 CallCount + CallbackCount 双计数器 | 检查异步接口的 Demo |
|
|
44
|
+
| 4.6 | 调用与签名一致 | 步骤中接口调用的参数类型与签名一致,未编造默认值 | 对照签名 |
|
|
45
|
+
|
|
46
|
+
## 5. 文档规范检查
|
|
47
|
+
|
|
48
|
+
| # | 检查项 | 标准 | 验证方法 |
|
|
49
|
+
|---|--------|------|----------|
|
|
50
|
+
| 5.1 | 来源声明准确 | 概述声明基于覆盖率报告(含报告路径与统计),未声称"基于源码分析/接口规格文档" | 核对概述 |
|
|
51
|
+
| 5.2 | 设计性质声明 | 概述包含"步骤为验证设计,实际实现以源码为准" | 核对概述 |
|
|
52
|
+
| 5.3 | Demo 总览准确 | 数量与描述列表/矩阵一致 | 计数核对 |
|
|
53
|
+
| 5.4 | 矩阵一致 | 矩阵行数 = 已覆盖接口数;"所在 Demo"与描述列表名称严格一致 | 计数与文本比对 |
|
|
54
|
+
| 5.5 | 附录一致 | 未覆盖接口清单与报告"未覆盖接口详情"一致(含为 0 时的"均已覆盖"表述) | 对照 `uncoveredList` |
|
|
55
|
+
| 5.6 | 序号连续 | 描述列表与矩阵分别从 1 连续编号 | 逐行检查 |
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## 6. 追溯性抽查方法(推荐)
|
|
60
|
+
|
|
61
|
+
生成文档后,随机抽取 ≥20% 的描述条目(至少 1 条)执行深度抽查:
|
|
62
|
+
|
|
63
|
+
1. 打开该条目"源码位置"对应的覆盖率报告"文件覆盖详情"行;
|
|
64
|
+
2. 核对描述中的接口集合与报告该文件的覆盖接口列表一致(注意截断重建);
|
|
65
|
+
3. 核对"对应函数接口"签名与报告"已覆盖接口详情"逐字一致;
|
|
66
|
+
4. 发现任何不一致 → 修正描述并重新执行 §2/§3 全量检查。
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## 7. 检查记录模板
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
质量检查记录
|
|
74
|
+
- 检查时间: YYYY-MM-DD HH:mm
|
|
75
|
+
- reportPath: {覆盖率报告路径}(来源: 提供/发现/自动生成)
|
|
76
|
+
- Demo 数量: {N},覆盖接口数量: {M},未覆盖接口数量: {K}
|
|
77
|
+
- 1.报告有效性: ✅/❌ {说明}
|
|
78
|
+
- 2.覆盖完整性: ✅/❌ {说明}
|
|
79
|
+
- 3.真实性: ✅/❌ {说明}
|
|
80
|
+
- 4.验证设计质量: ✅/❌ {死按钮/setState/双计数器检查结果}
|
|
81
|
+
- 5.文档规范: ✅/❌ {说明}
|
|
82
|
+
- 抽查: {条目数}/{总数} 通过
|
|
83
|
+
```
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @tool validate-skill
|
|
4
|
+
* @description 校验SKILL目录是否符合Agent Skills规范,检查frontmatter、结构、渐进式披露。跨平台(Windows/macOS/Linux),仅需 Node.js。
|
|
5
|
+
* @param skillDir: string - SKILL目录绝对路径
|
|
6
|
+
* @hidden
|
|
7
|
+
* validate-skill.cjs
|
|
8
|
+
*
|
|
9
|
+
* 用法:
|
|
10
|
+
* # CLI 模式
|
|
11
|
+
* node validate-skill.cjs /path/to/skill
|
|
12
|
+
*
|
|
13
|
+
* # MCP Gateway 模式(第一个参数为 JSON 字符串)
|
|
14
|
+
* node validate-skill.cjs '{"skillDir":"/path/to/skill"}'
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
'use strict';
|
|
18
|
+
|
|
19
|
+
const fs = require('node:fs');
|
|
20
|
+
const path = require('node:path');
|
|
21
|
+
|
|
22
|
+
// ── 参数解析(兼容 MCP Gateway JSON 模式与 CLI 模式) ──────
|
|
23
|
+
|
|
24
|
+
function parseArgs(argv) {
|
|
25
|
+
if (argv.length === 0) return {};
|
|
26
|
+
const first = argv[0];
|
|
27
|
+
if (typeof first === 'string' && first.startsWith('{')) {
|
|
28
|
+
try { return JSON.parse(first); } catch (e) { return {}; }
|
|
29
|
+
}
|
|
30
|
+
return { skillDir: first };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ── 输出统计 ─────────────────────────────────────────────
|
|
34
|
+
|
|
35
|
+
let PASS = 0;
|
|
36
|
+
let FAIL = 0;
|
|
37
|
+
let WARNINGS = 0;
|
|
38
|
+
|
|
39
|
+
function checkPass(msg) { console.log(` ✅ ${msg}`); PASS += 1; }
|
|
40
|
+
function checkFail(msg) { console.log(` ❌ ${msg}`); FAIL += 1; }
|
|
41
|
+
function checkWarn(msg) { console.log(` ⚠️ ${msg}`); WARNINGS += 1; }
|
|
42
|
+
|
|
43
|
+
// ── Frontmatter 解析 ─────────────────────────────────────
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 解析 SKILL.md 的 frontmatter 与 body。
|
|
47
|
+
* 返回 { frontmatter: object|null, bodyLines: string[] }
|
|
48
|
+
*/
|
|
49
|
+
function parseFrontmatter(content) {
|
|
50
|
+
const lines = content.split(/\r?\n/);
|
|
51
|
+
if (!lines[0] || lines[0].trim() !== '---') {
|
|
52
|
+
return { frontmatter: null, bodyLines: lines };
|
|
53
|
+
}
|
|
54
|
+
let end = -1;
|
|
55
|
+
for (let i = 1; i < lines.length; i++) {
|
|
56
|
+
if (lines[i].trim() === '---') { end = i; break; }
|
|
57
|
+
}
|
|
58
|
+
if (end === -1) {
|
|
59
|
+
return { frontmatter: null, bodyLines: lines };
|
|
60
|
+
}
|
|
61
|
+
const fmLines = lines.slice(1, end);
|
|
62
|
+
const fm = {};
|
|
63
|
+
let currentKey = null;
|
|
64
|
+
for (const line of fmLines) {
|
|
65
|
+
const m = line.match(/^([a-zA-Z_][\w-]*):\s*(.*)$/);
|
|
66
|
+
if (m) {
|
|
67
|
+
currentKey = m[1];
|
|
68
|
+
fm[currentKey] = m[2].trim();
|
|
69
|
+
} else if (currentKey && /^\s+\S/.test(line)) {
|
|
70
|
+
fm[currentKey] += '\n' + line.trim();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return { frontmatter: fm, bodyLines: lines.slice(end + 1) };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ── 主流程 ───────────────────────────────────────────────
|
|
77
|
+
|
|
78
|
+
function main() {
|
|
79
|
+
const args = parseArgs(process.argv.slice(2));
|
|
80
|
+
const skillDir = args.skillDir || '';
|
|
81
|
+
|
|
82
|
+
if (!skillDir) {
|
|
83
|
+
console.log('Usage: node validate-skill.cjs <skillDir>');
|
|
84
|
+
console.log('Error: skillDir parameter is required');
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (!fs.existsSync(skillDir) || !fs.statSync(skillDir).isDirectory()) {
|
|
89
|
+
console.log(`Error: Directory does not exist: ${skillDir}`);
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
console.log('=== SKILL 规范校验 ===');
|
|
94
|
+
console.log(`目录: ${skillDir}`);
|
|
95
|
+
console.log('');
|
|
96
|
+
|
|
97
|
+
// ─── 1. SKILL.md 存在性 ───
|
|
98
|
+
console.log('--- 1. 文件结构检查 ---');
|
|
99
|
+
const skillFile = path.join(skillDir, 'SKILL.md');
|
|
100
|
+
if (fs.existsSync(skillFile) && fs.statSync(skillFile).isFile()) {
|
|
101
|
+
checkPass('SKILL.md 存在');
|
|
102
|
+
} else {
|
|
103
|
+
checkFail('SKILL.md 不存在');
|
|
104
|
+
console.log('无法继续校验,SKILL.md 是必需文件');
|
|
105
|
+
process.exit(1);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const content = fs.readFileSync(skillFile, 'utf-8');
|
|
109
|
+
const { frontmatter, bodyLines } = parseFrontmatter(content);
|
|
110
|
+
|
|
111
|
+
// ─── 2. Frontmatter 检查 ───
|
|
112
|
+
console.log('');
|
|
113
|
+
console.log('--- 2. Frontmatter 检查 ---');
|
|
114
|
+
|
|
115
|
+
if (frontmatter) {
|
|
116
|
+
checkPass('YAML frontmatter 存在');
|
|
117
|
+
} else {
|
|
118
|
+
checkFail('缺少 YAML frontmatter(文件应以 --- 开头并以 --- 结束头部)');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const nameField = frontmatter ? String(frontmatter.name || '') : '';
|
|
122
|
+
if (nameField) {
|
|
123
|
+
checkPass(`name 字段存在: ${nameField}`);
|
|
124
|
+
|
|
125
|
+
// name 字符集校验
|
|
126
|
+
if (/^[a-z0-9]+(-[a-z0-9]+)*$/.test(nameField)) {
|
|
127
|
+
checkPass('name 字符集合规(仅小写字母/数字/连字符)');
|
|
128
|
+
} else {
|
|
129
|
+
checkFail('name 字符集不合规:应仅含 a-z/0-9/-,不能首尾连字符或连续连字符');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// name 长度校验
|
|
133
|
+
const nameLen = nameField.length;
|
|
134
|
+
if (nameLen >= 1 && nameLen <= 64) {
|
|
135
|
+
checkPass(`name 长度合规 (${nameLen} 字符)`);
|
|
136
|
+
} else {
|
|
137
|
+
checkFail(`name 长度不合规:应在 1-64 字符之间(当前 ${nameLen})`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// name 匹配目录名
|
|
141
|
+
const dirName = path.basename(path.resolve(skillDir));
|
|
142
|
+
if (nameField === dirName) {
|
|
143
|
+
checkPass(`name 匹配父目录名 (${dirName})`);
|
|
144
|
+
} else {
|
|
145
|
+
checkFail(`name 不匹配父目录名:name='${nameField}' vs 目录='${dirName}'`);
|
|
146
|
+
}
|
|
147
|
+
} else {
|
|
148
|
+
checkFail('name 字段缺失');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const descField = frontmatter ? String(frontmatter.description || '') : '';
|
|
152
|
+
if (descField) {
|
|
153
|
+
const descLen = descField.length;
|
|
154
|
+
if (descLen <= 1024) {
|
|
155
|
+
checkPass(`description 长度合规 (${descLen} 字符)`);
|
|
156
|
+
} else {
|
|
157
|
+
checkFail(`description 过长:应 ≤1024 字符(当前 ${descLen})`);
|
|
158
|
+
}
|
|
159
|
+
} else {
|
|
160
|
+
checkFail('description 字段缺失或为空');
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (frontmatter && frontmatter.license) {
|
|
164
|
+
checkPass('license 字段存在');
|
|
165
|
+
} else {
|
|
166
|
+
checkWarn('license 字段缺失(推荐添加)');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (frontmatter && frontmatter.metadata) {
|
|
170
|
+
checkPass('metadata 字段存在');
|
|
171
|
+
} else {
|
|
172
|
+
checkWarn('metadata 字段缺失(推荐添加 author/version)');
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ─── 3. Body 检查 ───
|
|
176
|
+
console.log('');
|
|
177
|
+
console.log('--- 3. Body 内容检查 ---');
|
|
178
|
+
|
|
179
|
+
const bodyLineCount = bodyLines.length;
|
|
180
|
+
if (bodyLineCount <= 500) {
|
|
181
|
+
checkPass(`body 行数合规 (${bodyLineCount} 行, ≤500)`);
|
|
182
|
+
} else {
|
|
183
|
+
checkWarn(`body 行数较多 (${bodyLineCount} 行,推荐 < 500,考虑拆分到 references/)`);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (/任务参数|## .*参数|\|\s*参数/.test(content)) {
|
|
187
|
+
checkPass('包含任务参数说明');
|
|
188
|
+
} else {
|
|
189
|
+
checkWarn('未找到任务参数表(推荐添加)');
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (/工作流程|Phase|## Phase/.test(content)) {
|
|
193
|
+
checkPass('包含工作流程概览');
|
|
194
|
+
} else {
|
|
195
|
+
checkWarn('未找到工作流程概览(推荐添加)');
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// ─── 4. 渐进式披露检查 ───
|
|
199
|
+
console.log('');
|
|
200
|
+
console.log('--- 4. 渐进式披露检查 ---');
|
|
201
|
+
|
|
202
|
+
const refsDir = path.join(skillDir, 'references');
|
|
203
|
+
if (fs.existsSync(refsDir) && fs.statSync(refsDir).isDirectory()) {
|
|
204
|
+
const refFiles = fs.readdirSync(refsDir)
|
|
205
|
+
.filter((f) => f.endsWith('.md') && fs.statSync(path.join(refsDir, f)).isFile());
|
|
206
|
+
checkPass(`references/ 目录存在 (${refFiles.length} 个文档)`);
|
|
207
|
+
|
|
208
|
+
// 检查引用深度(一层)
|
|
209
|
+
const nestedDirs = fs.readdirSync(refsDir, { withFileTypes: true })
|
|
210
|
+
.filter((d) => d.isDirectory()).length;
|
|
211
|
+
if (nestedDirs === 0) {
|
|
212
|
+
checkPass('references 保持一层深度');
|
|
213
|
+
} else {
|
|
214
|
+
checkFail(`references 存在嵌套子目录 (${nestedDirs} 个),应保持一层深度`);
|
|
215
|
+
}
|
|
216
|
+
} else {
|
|
217
|
+
checkWarn('无 references/ 目录(详细内容建议拆分到此目录)');
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const assetsDir = path.join(skillDir, 'assets');
|
|
221
|
+
if (fs.existsSync(assetsDir) && fs.statSync(assetsDir).isDirectory()) {
|
|
222
|
+
checkPass('assets/ 目录存在');
|
|
223
|
+
} else {
|
|
224
|
+
checkWarn('无 assets/ 目录(如需模板/资源可添加)');
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const scriptsDir = path.join(skillDir, 'scripts');
|
|
228
|
+
if (fs.existsSync(scriptsDir) && fs.statSync(scriptsDir).isDirectory()) {
|
|
229
|
+
checkPass('scripts/ 目录存在');
|
|
230
|
+
} else {
|
|
231
|
+
checkWarn('无 scripts/ 目录(如需可执行脚本可添加)');
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// 检查文件引用使用相对路径
|
|
235
|
+
const absRefs = (content.match(/\]\(\/[^)]+\)/g) || []).length;
|
|
236
|
+
if (absRefs === 0) {
|
|
237
|
+
checkPass('文件引用使用相对路径');
|
|
238
|
+
} else {
|
|
239
|
+
checkFail(`发现 ${absRefs} 个绝对路径引用,应使用相对路径`);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// ─── 5. 总结 ───
|
|
243
|
+
console.log('');
|
|
244
|
+
console.log('=== 校验总结 ===');
|
|
245
|
+
console.log(` ✅ 通过: ${PASS}`);
|
|
246
|
+
console.log(` ❌ 失败: ${FAIL}`);
|
|
247
|
+
console.log(` ⚠️ 警告: ${WARNINGS}`);
|
|
248
|
+
console.log('');
|
|
249
|
+
|
|
250
|
+
if (FAIL === 0) {
|
|
251
|
+
console.log('ALL CHECKS PASSED');
|
|
252
|
+
process.exit(0);
|
|
253
|
+
} else {
|
|
254
|
+
console.log(`VALIDATION FAILED: ${FAIL} 个必须修复的问题`);
|
|
255
|
+
process.exit(1);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
main();
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: flutter-library-demo-coverage
|
|
3
|
+
description: 分析 Flutter 三方库现有 Demo 对接口的覆盖情况。读取接口规格,扫描 example/lib 下所有 .dart 文件,通过正则匹配识别已调用的接口,计算接口覆盖率百分比,输出已覆盖和未覆盖接口清单。当需要对 Flutter 三方库进行 demo 接口覆盖率缺口填补、未覆盖接口测试规划时调用。
|
|
4
|
+
license: Apache-2.0
|
|
5
|
+
compatibility: 需要 Node.js 运行环境
|
|
6
|
+
metadata:
|
|
7
|
+
version: "1.0.0"
|
|
8
|
+
category: flutter-development
|
|
9
|
+
language: Dart
|
|
10
|
+
useWhen: 分析 Flutter 三方库 Demo 对接口的覆盖情况
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Flutter 三方库 Demo 接口覆盖率分析
|
|
14
|
+
|
|
15
|
+
本 Skill 直接调用本地脚本分析 Demo 源码对库导出接口的覆盖情况,直接输出最终 Markdown 覆盖率报告。
|
|
16
|
+
|
|
17
|
+
> ⚠️ **核心约束**:基于静态 import 分析 + 正则匹配,动态调用(`Function.apply`、反射、方法 tear-off 引用)无法识别,报告中需标注此限制。
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## 脚本依赖
|
|
22
|
+
|
|
23
|
+
本 Skill 直接调用以下本地脚本完成全部分析工作(**不保存脚本副本**):
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
../../../MCP/scripts/flutter-analyze-demo-coverage.cjs
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
> 脚本路径为**相对路径**,基准为本 SKILL 目录:向上三级到仓库根目录,再进入 `MCP/scripts/`。
|
|
30
|
+
|
|
31
|
+
**调用方式**:通过 `node` 命令行直接执行,使用 `--` 参数传参。
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## 任务参数
|
|
36
|
+
|
|
37
|
+
| 参数 | 类型 | 必填 | 说明 |
|
|
38
|
+
|------|------|------|------|
|
|
39
|
+
| `libroot` | string | ✅ | 库源码根目录绝对路径(包含 `lib/` 和 `example/` 子目录,支持 monorepo 自动定位主包) |
|
|
40
|
+
|
|
41
|
+
**可选参数**:
|
|
42
|
+
|
|
43
|
+
| 参数 | 默认值 | 说明 |
|
|
44
|
+
|------|--------|------|
|
|
45
|
+
| `spec` | — | 已有的 `interface-spec.json` 路径(由 `flutter-extract-interfaces.cjs` 生成)。提供时从 JSON 读取接口规格;未提供时自动从 `libroot` 源码提取 |
|
|
46
|
+
| `output` | `02-{库名}-Demo覆盖率报告.md` | 输出 Markdown 报告路径 |
|
|
47
|
+
| `title` | `{库名} Demo 覆盖率分析报告` | 报告标题 |
|
|
48
|
+
| `lib` | 从 `pubspec.yaml` 自动提取 | 包名覆盖(import 匹配用的 package 名) |
|
|
49
|
+
| `src` | 自动定位 `example/lib` | Demo 源码目录覆盖(默认按 `example/lib` → `examples/lib` → `demo/lib` 顺序探测) |
|
|
50
|
+
|
|
51
|
+
**路径约定**:
|
|
52
|
+
- 脚本统一用**相对路径**引用:`../../../MCP/scripts/flutter-analyze-demo-coverage.cjs`(相对本 SKILL 目录向上三级,位于仓库根目录 `MCP/scripts/` 下)
|
|
53
|
+
- 执行时以任务 prompt 中「SKILL 目录」下发的路径为解析基准:`{SKILL目录}/../../../MCP/scripts/flutter-analyze-demo-coverage.cjs`
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## 输入依赖
|
|
58
|
+
|
|
59
|
+
- **前置SKILL**: FlutterInterfaceSkill(可选;若提供 `spec` 参数则使用其生成的 JSON,否则脚本自动从源码提取)
|
|
60
|
+
- **输入文件**: 库源码目录(含 `lib/` 和 `example/lib/`)
|
|
61
|
+
- **输出文件**: Markdown 覆盖率报告(脚本直接生成)
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## 工作流程概览
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
Phase 1: 运行 flutter-analyze-demo-coverage.cjs → Phase 2: 确认报告输出 → Phase 3: 结果合理性校验
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
> 全程只有一条 `node` 命令,脚本直接输出最终 Markdown 报告,无中间文件,无需清理。
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Phase 1:运行 Demo 覆盖率分析脚本
|
|
76
|
+
|
|
77
|
+
> ⚠️ **脚本一步完成:提取/读取接口规格 + 扫描 Demo 源码 + 正则覆盖率匹配 + 生成 Markdown 报告。无中间 JSON 文件。**
|
|
78
|
+
|
|
79
|
+
运行脚本:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
node ../../../MCP/scripts/flutter-analyze-demo-coverage.cjs \
|
|
83
|
+
--libroot "{{libroot}}" \
|
|
84
|
+
--output "{{outputDir}}/02-{{libName}}-Demo覆盖率报告.md"
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
其中:
|
|
88
|
+
- 脚本路径相对本 SKILL 目录解析(向上三级进入 `MCP/scripts/`),即 `{SKILL目录}/../../../MCP/scripts/flutter-analyze-demo-coverage.cjs`
|
|
89
|
+
- `{{libroot}}` = 库源码根目录(包含 `lib/` 和 `example/` 子目录)
|
|
90
|
+
- `{{outputDir}}` = 输出目录(通常与接口规格文档同目录)
|
|
91
|
+
- 包名从 `pubspec.yaml` 或 spec JSON 自动提取,无需 `--lib` 参数
|
|
92
|
+
|
|
93
|
+
若已有 `interface-spec.json`,可通过 `--spec` 参数传入避免重复提取:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
node ../../../MCP/scripts/flutter-analyze-demo-coverage.cjs \
|
|
97
|
+
--libroot "{{libroot}}" \
|
|
98
|
+
--spec "{{outputDir}}/{{libName}}-interface-spec.json" \
|
|
99
|
+
--output "{{outputDir}}/02-{{libName}}-Demo覆盖率报告.md"
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**脚本自动完成**:
|
|
103
|
+
1. 获取接口规格(从 `spec` JSON 读取,或从 `libroot` 源码自动提取,monorepo 自动定位主包)
|
|
104
|
+
2. 定位 Demo 源码目录(按 `example/lib` → `examples/lib` → `demo/lib` 顺序探测),递归扫描 `.dart` 文件(跳过 `.g.dart` / `.freezed.dart` 生成文件,排除 `.dart_tool`、`build`、`.fvm` 及 `ios`/`android`/`ohos`/`macos`/`windows`/`web`/`linux` 平台目录)
|
|
105
|
+
3. 解析 Dart import(`show`/`hide`/`as` 别名均支持)并追踪实例变量 → 类名映射(含类型注解追踪)
|
|
106
|
+
4. 正则匹配覆盖率:静态方法/属性(`ClassName.member`)、构造函数(含命名构造 `ClassName.named()`)、枚举值、实例方法/属性(`varName.member`,extension 方法同时匹配 `onType`)、链式调用(`ClassName().method()`)
|
|
107
|
+
5. 对象字面量/命名参数兜底匹配(`propertyName:`)与 aliased import 兜底(`alias.ClassName.member`)
|
|
108
|
+
6. **过滤低价值接口**(对齐 XTS 覆盖率口径):跳过 `constant`/`enumValue`/`constructor`/`type`(typedef)四类不计入统计分母;排除 `createState` 框架生命周期方法与 `hashCode`/`toString`/`noSuchMethod`/`runtimeType` 等 Object 内置成员
|
|
109
|
+
7. **直接生成最终 Markdown 报告**(含覆盖率统计、按类型/按类分组、已覆盖/未覆盖清单、文件覆盖详情)
|
|
110
|
+
|
|
111
|
+
> ⚠️ **Demo 范围界定**:扫描范围默认 `example/lib/`(或 `examples/lib`、`demo/lib`),仅指官方示例工程代码。绝不扩展到 `test/`、`integration_test/` 等测试目录(测试目录由 `flutter-analyze-xts-coverage.cjs` 覆盖)。
|
|
112
|
+
|
|
113
|
+
**退出码处理**:
|
|
114
|
+
- `0` → 成功
|
|
115
|
+
- 其他 → 失败,终止流程并标注 `状态: 失败`
|
|
116
|
+
- 未找到 Demo 源码目录时脚本直接退出(报 `Demo 源码目录不存在`),需先确认 `libroot` 下是否存在 `example/lib` 等目录
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Phase 2:确认报告输出
|
|
121
|
+
|
|
122
|
+
脚本执行完毕后,Markdown 报告已直接生成在 `output` 指定路径(默认为 `02-{库名}-Demo覆盖率报告.md`)。
|
|
123
|
+
|
|
124
|
+
报告包含以下章节:
|
|
125
|
+
1. **基本信息** — 库名、源文件数、生成时间
|
|
126
|
+
2. **覆盖率总览** — 方法级覆盖率百分比 + 进度条 + S/N/M 统计 + 排除类型说明
|
|
127
|
+
3. **按接口类型统计** — method/constructor/property/field/constant/enumValue/function/type/variable 分组覆盖率(排除类型标注「不计入覆盖率」)
|
|
128
|
+
4. **类级覆盖率** — 每个类/接口的覆盖统计(按覆盖率升序排列)
|
|
129
|
+
5. **未覆盖接口详情** — 按类分组,含参数、返回类型、说明
|
|
130
|
+
6. **已覆盖接口详情** — 按类分组,含签名和覆盖来源文件
|
|
131
|
+
7. **文件覆盖详情** — 每个 Demo 文件覆盖的接口列表
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Phase 3:结果合理性校验
|
|
136
|
+
|
|
137
|
+
> ⚠️ 正则匹配存在固有精度边界,交付前须完成本环节校验,禁止直接采信首轮结果。
|
|
138
|
+
|
|
139
|
+
### 3.1 覆盖率异常时交叉验证
|
|
140
|
+
|
|
141
|
+
覆盖率 < 10% 或为 0%,且 Demo 源码确实存在 `import 'package:{包名}/...'` 时:
|
|
142
|
+
|
|
143
|
+
1. **抽样 grep 交叉验证**:对报告中「未覆盖」的接口,在 Demo 源码目录统计真实调用数,与报告比对:
|
|
144
|
+
```bash
|
|
145
|
+
grep -rh "\.方法名(" example/lib/ --include="*.dart" | wc -l
|
|
146
|
+
```
|
|
147
|
+
2. **快速诊断口诀**:"import 有、覆盖无 → 查 import 是否带 `as` 别名或 `show` 列表;部分漏、变量追踪失败 → 查变量是否声明为 `dynamic`/复杂表达式赋值"
|
|
148
|
+
|
|
149
|
+
### 3.2 正则匹配已知限制
|
|
150
|
+
|
|
151
|
+
以下场景可能漏判(低估覆盖率),交付报告时应主动注明:
|
|
152
|
+
- **方法 tear-off**:`final f = obj.method;`(不带调用括号的方法引用)无法识别
|
|
153
|
+
- **动态调用**:`Function.apply`、反射、`call` 方法间接调用无法识别
|
|
154
|
+
- **多级链式调用**:`ClassName().a().b()` 中 `b()` 若非 `ClassName` 直接成员则不可见
|
|
155
|
+
- **命名参数兜底误报**:`propertyName:` 兜底规则可能将 Demo 中同名属性误判为已覆盖,类级覆盖率接近 100% 时建议抽样核对
|
|
156
|
+
|
|
157
|
+
### 3.3 统计口径说明
|
|
158
|
+
|
|
159
|
+
**排除类型**:报告默认排除 `constant`(const/final 常量)、`enumValue`(枚举常量)、`constructor`(实例化入口,Demo 中常通过间接方式获得对象)、`type`(typedef 类型别名)四类,不计入覆盖率分母,与 `flutter-analyze-xts-coverage.cjs` 口径完全一致。同时排除 `createState` 框架生命周期方法(通过 `testWidgets`/`pumpWidget` 间接覆盖)与 `hashCode`/`toString`/`noSuchMethod`/`runtimeType` 等 Object 基类内置成员(所有 Dart 对象自动继承,不属于库自身公开 API)。下游 Demo 场景规划 SKILL 消费的未覆盖清单中不会出现这些低价值接口,避免为纯常量类生成无意义的「常量展示页」。
|
|
160
|
+
|
|
161
|
+
**生成代码排除**:`.g.dart`(json_serializable/build_runner 生成)与 `.freezed.dart`(freezed 生成)文件不参与扫描,其接口不计入统计。
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## 注意事项
|
|
166
|
+
|
|
167
|
+
核心要点:
|
|
168
|
+
1. **脚本本地调用** — 全部分析工作由 `node` 直接执行 `flutter-analyze-demo-coverage.cjs` 一步完成,禁止 AI 手工解析源码或编写报告表格
|
|
169
|
+
2. **正则匹配引擎** — Dart 源码使用正则模式匹配(import show/hide/as 解析 + 实例变量追踪 + 多级兜底),无 AST 模式
|
|
170
|
+
3. **Demo 范围** — 仅 `example/lib/`(或 `examples/lib`、`demo/lib`),测试目录不由本脚本覆盖
|
|
171
|
+
4. **动态调用限制** — tear-off 引用、`Function.apply`、反射无法识别,报告中标注此限制
|
|
172
|
+
5. **无中间文件** — 脚本直接输出最终 Markdown 报告,无需清理
|
|
173
|
+
6. **接口规格来源灵活** — 可通过 `--spec` 参数传入已有 JSON(由 `flutter-interface` SKILL 生成),也可不传 `--spec` 让脚本自动从源码提取
|
|
174
|
+
7. **包名自动提取** — 默认从 `pubspec.yaml` 或 spec JSON 读取(monorepo 自动定位主包),无需手工指定
|
|
175
|
+
8. **结果合理性校验** — 覆盖率异常低/为 0 时先做 grep 抽样交叉验证再采信(见 Phase 3);覆盖率接近 100% 时注意命名参数兜底可能引入误报
|
package/vendor/VERSION
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
0.0.
|
|
2
|
-
built=2026-09-
|
|
3
|
-
sha=
|
|
1
|
+
0.0.13
|
|
2
|
+
built=2026-09-05T09:30:00.370Z
|
|
3
|
+
sha=a8a8125bd10b8f4f
|