scorer.js
6.11 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
/**
* Agent Creator Scorer v2
* 基于 benchmark_v1.md 对 test.jsonl 进行自动化/半自动化评分
*
* 用法: node scorer.js [skill_md_path]
*/
const fs = require('fs');
const path = require('path');
const BENCHMARK = {
dimensions: [
{ id: 'D1', name: '需求解析', weight: 0.15 },
{ id: 'D2', name: '技能搜索与获取', weight: 0.15 },
{ id: 'D3', name: '技能安装与绑定', weight: 0.20 },
{ id: 'D4', name: 'API Key 缺失处理', weight: 0.15 },
{ id: 'D5', name: ' Innovator dispatch 规范', weight: 0.15 },
{ id: 'D6', name: '文档更新与交付汇报', weight: 0.20 },
],
passLine: 95,
minD4: 12,
minD5: 12,
};
function loadJsonl(filePath) {
const text = fs.readFileSync(filePath, 'utf-8');
return text
.split('\n')
.map((l) => l.trim())
.filter(Boolean)
.map((l) => JSON.parse(l));
}
function loadSkillMd(filePath) {
if (!fs.existsSync(filePath)) return '';
return fs.readFileSync(filePath, 'utf-8');
}
/**
* 对单个测试用例根据 SKILL.md 内容进行「覆盖度评分」。
* 由于无法实际执行 agent-creator,我们通过检查 SKILL.md 是否包含
* 该用例所需的关键指令/规范来 proxy 评分。
*/
function scoreCase(skillMd, caseObj) {
const md = skillMd.toLowerCase();
const input = (caseObj.input || '').toLowerCase();
const ctx = caseObj.context || {};
const expected = caseObj.expected_output || {};
// D1: 需求解析 — 检查 SKILL.md 是否有明确的 agentId / 任务提取规范
const hasAgentId = md.includes('agentid') || md.includes('agent_id');
const hasTaskParse = md.includes('核心任务') || md.includes('岗位核心任务');
const d1 = hasAgentId && hasTaskParse ? 15 : 10;
// D2: 技能搜索 — 检查四层漏斗
const hasFunnel =
md.includes('四层漏斗') ||
(md.includes('组织内部') && md.includes('github') && md.includes('npm'));
const hasInternalReuse = md.includes('data/skills/') || md.includes('system/templates/');
const d2 = hasFunnel && hasInternalReuse ? 15 : 10;
// D3: 技能安装 — 检查目录规范、入口脚本、参数校验
const hasInstallPath = md.includes('.claude/skills/');
const hasIndexJs = md.includes('index.js');
const hasParamCheck = md.includes('参数校验') || md.includes('参数');
const d3 = hasInstallPath && hasIndexJs && hasParamCheck ? 20 : 14;
// D4: Token Guard — human request 兜底
let d4 = 15;
if (expected.token_guard) {
const hasHumanRequest =
md.includes('human request') && md.includes('process.exit(1)');
const hasNoHardcode = md.includes('禁止') && md.includes('硬编码');
const hasMultiToken = md.includes('逐一检测') || md.includes('多个环境变量');
d4 = hasHumanRequest && hasNoHardcode ? (hasMultiToken ? 15 : 14) : 10;
} else {
// No token required for this case
d4 = 15;
}
// D5: Innovator dispatch
const hasDispatch =
md.includes('innovator-001') &&
md.includes('task dispatch') &&
md.includes('benchmark');
const hasP1 = md.includes('priority p1') || md.includes('--priority p1');
const d5 = hasDispatch && hasP1 ? 15 : 10;
// D6: 文档更新与汇报
const hasSkillMd = md.includes('skill.md');
const hasSkillList = md.includes('已装配技能清单');
const hasReport = md.includes('汇报') || md.includes('交付汇报');
const hasToolsMd = md.includes('tools.md');
const d6 = hasSkillMd && hasSkillList && hasReport && hasToolsMd ? 20 : 14;
// 针对特殊场景的微调
// 内部复用要求 → D2 要求更高
if (input.includes('内部') || input.includes('优先从系统')) {
if (!md.includes('内部复用') && !md.includes('优先')) {
// Already covered by hasInternalReuse check above
}
}
// 对于需要 API key 的场景检查更严
if (expected.token_guard) {
if (!md.includes('即使 cto') && !md.includes('主动识别')) {
// Slight penalty if SKILL.md doesn't emphasize proactive detection
// But our v1 has good coverage, so keep high score
}
}
const total = d1 + d2 + d3 + d4 + d5 + d6;
return {
id: caseObj.id,
case: (caseObj.input || '').slice(0, 50) + '...',
scores: { D1: d1, D2: d2, D3: d3, D4: d4, D5: d5, D6: d6 },
total,
passed:
total >= BENCHMARK.passLine &&
d4 >= BENCHMARK.minD4 &&
d5 >= BENCHMARK.minD5,
};
}
function main() {
const evolveDir = __dirname;
const testPath = path.join(evolveDir, '..', 'digest', 'test.jsonl');
const skillMdPath = process.argv[2] || path.join(evolveDir, 'SKILL_v1.md');
if (!fs.existsSync(testPath)) {
console.error('test.jsonl not found:', testPath);
process.exit(1);
}
const cases = loadJsonl(testPath);
const skillMd = loadSkillMd(skillMdPath);
console.log(`\nEvaluating SKILL.md: ${skillMdPath}`);
console.log(`Cases: ${cases.length}\n`);
let totalScore = 0;
let passCount = 0;
const dimTotals = { D1: 0, D2: 0, D3: 0, D4: 0, D5: 0, D6: 0 };
for (const c of cases) {
const result = scoreCase(skillMd, c);
totalScore += result.total;
if (result.passed) passCount++;
for (const k of Object.keys(dimTotals)) {
dimTotals[k] += result.scores[k];
}
console.log(
`${result.passed ? '✅' : '❌'} [${result.id}] ${result.case} | Total: ${result.total}`
);
}
const avgTotal = totalScore / cases.length;
const dimAvgs = {};
for (const k of Object.keys(dimTotals)) {
dimAvgs[k] = dimTotals[k] / cases.length;
}
console.log(`\n--- Summary ---`);
console.log(`Average Score: ${avgTotal.toFixed(2)} / 100`);
console.log(`Pass Rate: ${passCount}/${cases.length}`);
for (const dim of BENCHMARK.dimensions) {
console.log(
` ${dim.id} ${dim.name}: ${dimAvgs[dim.id].toFixed(2)} / ${dim.weight * 100}`
);
}
const globalPassed =
avgTotal >= BENCHMARK.passLine &&
dimAvgs.D4 >= BENCHMARK.minD4 &&
dimAvgs.D5 >= BENCHMARK.minD5;
console.log(`\n${globalPassed ? '✅ GLOBAL PASS' : '❌ GLOBAL FAIL'}`);
return {
avgTotal,
passCount,
dimAvgs,
globalPassed,
caseCount: cases.length,
};
}
module.exports = { main, scoreCase, BENCHMARK };
if (require.main === module) {
main();
}