dsh-ronge-adaptive-skill-archive 5.2.1 → 5.3.1

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/README.md CHANGED
@@ -31,7 +31,7 @@
31
31
  <sub>会话右侧的「技能」抽屉:推荐 / 组队计划 / 经验 / 安全与更新,都在一个面板里。</sub>
32
32
  </p>
33
33
 
34
- > **v5.2.1 发布说明**:本 README 是当前发布稿。三大新能力 `plan → compose → draft`(任务拆解 / 技能组队 / 按缺口造技能)与 12 领域包为本次发布新增;每条声明的证据、实测状态与未验证项统一记录在 **[docs/claims.md](docs/claims.md)**。benchmark 数字见 **[docs/benchmark.md](docs/benchmark.md)**(2026-10-30 快照)。
34
+ > **v5.3.1 发布说明**:本 README 是当前发布稿。三大新能力 `plan → compose → draft`(任务拆解 / 技能组队 / 按缺口造技能)与 12 领域包为本次发布新增;每条声明的证据、实测状态与未验证项统一记录在 **[docs/claims.md](docs/claims.md)**。benchmark 数字见 **[docs/benchmark.md](docs/benchmark.md)**(2026-10-30 快照)。
35
35
 
36
36
  ## 为什么需要 v5
37
37
 
@@ -59,8 +59,8 @@ v5 的驾驶舱把这条链路补全:
59
59
  npx dsh-ronge-adaptive-skill-archive
60
60
  # 想先体检:npx dsh-ronge-adaptive-skill-archive doctor
61
61
 
62
- # 2) 把打印出的这一行交给任意 DSH 会话里的 Agent 执行(v5.2.1 发布后可用)
63
- plugin_manager install_bundle target=dsh-ronge-adaptive-skill-archive@5.2.1
62
+ # 2) 把打印出的这一行交给任意 DSH 会话里的 Agent 执行(v5.3.1 发布后可用)
63
+ plugin_manager install_bundle target=dsh-ronge-adaptive-skill-archive@5.3.1
64
64
 
65
65
  # 3) 刷新页面 → 右上角「技能」→ 右侧抽屉;计划模式 / 沉浸模式切换
66
66
  ```
@@ -209,6 +209,7 @@ v5.0.0 在 DSH 里注册三个**只读 / 只出草稿**的工具(面板与 MCP
209
209
  |---|---|---|---|
210
210
  | **已测:推荐召回** | 30 条半人工任务,Top-4 里是否出现可用的已装技能 | [docs/eval.md](docs/eval.md) | Recall@4 = 0.9(27/30),运行于 v2.7.2 工作树,**引用需注明口径** |
211
211
  | **已测:库检索** | 24 条任务在 6,399 索引里的类目命中 | [docs/eval.md](docs/eval.md) | Category Hit@5 等指标见文档;Hit@5 偏松,不能证明技能好用 |
212
+ | **v5:真实任务 A/B** | 12 条任务 fixture;pilot 2 条已跑 | [docs/benchmark-live.md](docs/benchmark-live.md) | pilot:成功率打平,plugin token 2.1–2.6×,**不是效率胜利**;修正后待重跑 |
212
213
  | **v5:规划 / 组队 / 效率** | planCoverage **0.333**、compositionValidity **1.00**、capabilityCoverage **0.717**、hintRecall **0.45**、gapDetection **1.00**(宽松口径:不假装已覆盖)、contextTokens mean **131.5** / p95 **193**(延迟为单机快照,不对外作 SLA);基线 keyword 0.083/0.217/0.25/0.532;installLoad **0.3** | [docs/benchmark.md](docs/benchmark.md)(60 条 held-out,2026-10-30) | 指标是半人工基准,不等于真实成功率;installLoad 0.3 必须一起读 |
213
214
 
214
215
  规则:
@@ -246,8 +247,8 @@ v5.0.0 在 DSH 里注册三个**只读 / 只出草稿**的工具(面板与 MCP
246
247
  | 方式 | 命令 | 适合 |
247
248
  |---|---|---|
248
249
  | **npx 助手** | `npx dsh-ronge-adaptive-skill-archive`(体检:`npx dsh-ronge-adaptive-skill-archive doctor`) | 第一次用;不装东西,只体检 + 打印安装命令 |
249
- | **npm** | `plugin_manager install_bundle target=dsh-ronge-adaptive-skill-archive@5.2.1` | v5.2.1 发布到 npm 后可用 |
250
- | **GitHub tag** | `plugin_manager install_bundle target=git+https://github.com/bugtakue/dsh-ronge-adaptive-skill-archive.git#v5.2.1` | 固定版本、可审源码 |
250
+ | **npm** | `plugin_manager install_bundle target=dsh-ronge-adaptive-skill-archive@5.3.1` | v5.3.1 发布到 npm 后可用 |
251
+ | **GitHub tag** | `plugin_manager install_bundle target=git+https://github.com/bugtakue/dsh-ronge-adaptive-skill-archive.git#v5.3.1` | 固定版本、可审源码 |
251
252
  | **本地目录** | `plugin_manager install_bundle target="/absolute/path/to/dsh-ronge-adaptive-skill-archive"` | 开发、改代码、离线 |
252
253
 
253
254
  > v5.0.0 打 tag / 发布 npm 之前,`@5.0.0` 会 404:请先用本地目录,或安装当时最新的已发布版本。安装后 DSH 需**重启**才会加载新的 Host 模块;浏览器刷新加载 Client。
@@ -356,7 +357,7 @@ MIT。见 [LICENSE](LICENSE)。索引中的技能元数据归原作者,边界
356
357
  **Install** (in a DSH session):
357
358
 
358
359
  ```bash
359
- plugin_manager install_bundle target=dsh-ronge-adaptive-skill-archive@5.2.1
360
+ plugin_manager install_bundle target=dsh-ronge-adaptive-skill-archive@5.3.1
360
361
  # then open any session → 「技能」 → switch Plan / Immersive
361
362
  ```
362
363
 
package/client.js CHANGED
@@ -271,6 +271,8 @@ window.__ModuleLoader__.load({
271
271
  intelStages: '阶段',
272
272
  intelInstalledCount: '已安装技能',
273
273
  intelReviewGap: '缺少独立复核技能:计划未假装复核',
274
+ intelReviewSuggestions: '可安装的复核候选',
275
+ intelDraftQuality: '草稿质量',
274
276
  intelPlanOptions: '方案',
275
277
  intelPlanFast: '快速',
276
278
  intelPlanBalanced: '均衡',
@@ -565,6 +567,8 @@ window.__ModuleLoader__.load({
565
567
  intelStages: 'Stages',
566
568
  intelInstalledCount: 'Installed skills',
567
569
  intelReviewGap: 'No independent reviewer: plan does not fake a review',
570
+ intelReviewSuggestions: 'Reviewer candidates',
571
+ intelDraftQuality: 'Draft quality',
568
572
  intelPlanOptions: 'Plans',
569
573
  intelPlanFast: 'Fast',
570
574
  intelPlanBalanced: 'Balanced',
@@ -4118,7 +4122,18 @@ window.__ModuleLoader__.load({
4118
4122
  ),
4119
4123
  ),
4120
4124
  plan.reviewGap
4121
- ? h('div', { className: 'sf-intel-note' }, t('intelReviewGap'))
4125
+ ? h(
4126
+ 'div',
4127
+ { className: 'sf-intel-note' },
4128
+ t('intelReviewGap'),
4129
+ Array.isArray(plan.reviewGapSuggestions) && plan.reviewGapSuggestions.length
4130
+ ? h(
4131
+ 'span',
4132
+ { className: 'sf-intel-suggest' },
4133
+ ' · ' + t('intelReviewSuggestions') + ': ' + plan.reviewGapSuggestions.join(', '),
4134
+ )
4135
+ : null,
4136
+ )
4122
4137
  : null,
4123
4138
  intelPlans.length > 1
4124
4139
  ? h(
@@ -4346,6 +4361,13 @@ window.__ModuleLoader__.load({
4346
4361
  )
4347
4362
  : null,
4348
4363
  ),
4364
+ draft.quality && Number.isFinite(Number(draft.quality.score))
4365
+ ? h(
4366
+ 'div',
4367
+ { className: 'sf-intel-meta' },
4368
+ h('span', { className: 'sf-badge' }, t('intelDraftQuality') + ': ' + draft.quality.score + ' (' + String(draft.quality.grade || '') + ')'),
4369
+ )
4370
+ : null,
4349
4371
  badgeRow(
4350
4372
  [
4351
4373
  h('span', { className: 'sf-intel-k', key: 'intel-safety-label' }, t('intelSafety')),
package/docs/claims.md CHANGED
@@ -15,7 +15,7 @@
15
15
  | 智能评测 | `test/fixtures/intelligence-cases.json` + `scripts/benchmark.mjs` | 60 条;数字见下 |
16
16
  | 已安装推荐评测 | `docs/eval.md` + `scripts/eval-real.mjs` | 30 条,v2.7.2 工作树;需 v5 复跑 |
17
17
  | 库检索评测 | `docs/eval.md` + `scripts/eval-discover.mjs` | 24 条,v2.8.1 工作树;需 v5 复跑 |
18
- | 单元/集成测试 | `npm test` | **120/120 通过**(2026-10-30) |
18
+ | 单元/集成测试 | `npm test` | **126/126 通过**(2026-10-30) |
19
19
  | Host E2E | `verify/host-e2e.mjs` | **20/20**(含 v5 端点) |
20
20
  | UI E2E | `verify/ui-e2e.mjs` | **27/27** |
21
21
  | DSH 兼容 | `docs/dsh-compatibility.md` | DSH Desktop 0.2.1-rc.3;安装/重启/迁移 |
@@ -57,7 +57,8 @@
57
57
  | C3 | **installLoad 0.3**:计划中约 30.0% 技能需先安装;planCoverage 不是成功率、不是已装可用率 | 同上 | verified(必须一起引用) |
58
58
  | C4 | hintRecall 0.45 是当前短板(AI 领域 0.00,多个领域 0.20);latency 是机器相关单次墙钟,不作为对外 SLA | `docs/benchmark.md` | verified |
59
59
  | C5 | 已安装推荐 Recall@4 = 0.9(27/30)、discover Hit@5 0.958 等为旧版半人工基线 | `docs/eval.md` | needs-human(v5 复跑) |
60
- | C6 | 效率提升百分比 | `efficiencyEstimate` 只给 `method:'estimate'`;benchmark 不测真实任务 token 节省 | **unsupported**(禁止写具体百分比) |
60
+ | C6 | 真实任务 A/B(pilot 2 条) | `docs/benchmark-live-pilot.json` + `docs/benchmark-live.md` | **pilot recorded:成功率 2/2 打平,plugin token 2.1–2.6×,不是效率胜利** |
61
+ | C7 | 效率提升百分比 | `efficiencyEstimate` 只给 `method:'estimate'`;benchmark 不测真实任务 token 节省 | **unsupported**(禁止写具体百分比) |
61
62
 
62
63
  ### D. v5 新能力(已交付)
63
64
 
@@ -74,7 +75,7 @@
74
75
 
75
76
  | # | 事项 | 状态 |
76
77
  |---|---|---|
77
- | E1 | 测试数 55/57 旧冲突 | 已统一为 **120/120**(2026-10-30);发布前以 `npm test` 为准 |
78
+ | E1 | 测试数 55/57 旧冲突 | 已统一为 **126/126**(2026-10-30);发布前以 `npm test` 为准 |
78
79
  | E2 | README 版本 3.0.0 | 已改为 v5.0.0;发布前与 package/tag 同步 |
79
80
  | E3 | 技能条数 vs 仓库数 | 已分开写:6,399 条技能 / 4,007 仓库 |
80
81
  | E4 | 索引 vs stars 快照日期 | 已分别标注 2026-10-28 / 2026-10-30 |
@@ -7,10 +7,10 @@
7
7
 
8
8
  ```bash
9
9
  # npm(推荐)
10
- plugin_manager install_bundle target=dsh-ronge-adaptive-skill-archive@5.2.1
10
+ plugin_manager install_bundle target=dsh-ronge-adaptive-skill-archive@5.3.1
11
11
 
12
12
  # GitHub tag(固定版本、可审源码)
13
- plugin_manager install_bundle target=git+https://github.com/bugtakue/dsh-ronge-adaptive-skill-archive.git#v5.2.1
13
+ plugin_manager install_bundle target=git+https://github.com/bugtakue/dsh-ronge-adaptive-skill-archive.git#v5.3.1
14
14
 
15
15
  # 本地目录(开发)
16
16
  plugin_manager install_bundle target=/absolute/path/to/dsh-ronge-adaptive-skill-archive
@@ -38,11 +38,11 @@ plugin_manager install_bundle target=/absolute/path/to/dsh-ronge-adaptive-skill-
38
38
 
39
39
  ## 已验证
40
40
 
41
- - `npm test`:120/120(含 DSH 兼容性静态检查)
41
+ - `npm test`:126/126(含 DSH 兼容性静态检查)
42
42
  - Host E2E:20/20(真实 loopback HTTP + raw.githubusercontent.com)
43
43
  - UI E2E:27/27
44
44
  - `npm run check:dsh`:manifest / factory id / slots / routes / 数据迁移 / market index 全部通过
45
- - npm registry 安装 v3.0.0 历史验证;v5.2.1 发布后以 registry 为准;市场索引 format 1 托管在 `https://bugtakue.github.io/dsh-ronge-adaptive-skill-archive/plugin-market.json`
45
+ - npm registry 安装 v3.0.0 历史验证;v5.3.1 发布后以 registry 为准;市场索引 format 1 托管在 `https://bugtakue.github.io/dsh-ronge-adaptive-skill-archive/plugin-market.json`
46
46
 
47
47
  ## 已知边界
48
48
 
@@ -260,7 +260,11 @@ function rankForCapability(pool, capability, context, options = {}) {
260
260
  .sort((a, b) => b.score - a.score || a.candidate.name.localeCompare(b.candidate.name))
261
261
  }
262
262
 
263
+ const TEXT_SAFE_DOMAINS = new Set(['writing', 'marketing', 'research', 'productivity', 'ai', 'unknown'])
264
+
263
265
  function selectSkills({ pool, decomposition, installedCount, experience, maxSkills }) {
266
+ const textMode = decomposition.outputMode === 'text'
267
+ const selectionPool = textMode ? pool.filter((candidate) => TEXT_SAFE_DOMAINS.has(candidate.profile.primaryDomain)) : pool
264
268
  const required = uniqueCaps(decomposition.capabilities)
265
269
  const requiredDomains = uniqueCaps([...(decomposition.domains || [])])
266
270
  const context = { requiredDomains, experience, taskText: decomposition.goal }
@@ -271,7 +275,7 @@ function selectSkills({ pool, decomposition, installedCount, experience, maxSkil
271
275
  for (const capability of required) {
272
276
  if (covered.has(capability)) continue
273
277
  if (selected.length >= maxSkills) break
274
- const ranked = rankForCapability(pool.filter((candidate) => candidate.installed), capability, context)
278
+ const ranked = rankForCapability(selectionPool.filter((candidate) => candidate.installed), capability, context)
275
279
  const best = ranked.find((entry) => !selectedNames.has(entry.candidate.name))
276
280
  if (!best) continue
277
281
  const threshold = best.candidate.installed ? 0.55 : 0.6
@@ -287,7 +291,7 @@ function selectSkills({ pool, decomposition, installedCount, experience, maxSkil
287
291
  const uncovered = required.filter((capability) => !covered.has(capability))
288
292
  for (const capability of uncovered) {
289
293
  if (selected.length >= Math.min(MIN_SKILLS, maxSkills)) break
290
- const ranked = rankForCapability(pool.filter((candidate) => candidate.installed), capability, context)
294
+ const ranked = rankForCapability(selectionPool.filter((candidate) => candidate.installed), capability, context)
291
295
  const best = ranked.find((entry) => !selectedNames.has(entry.candidate.name) && entry.score >= 0.5)
292
296
  if (!best) continue
293
297
  selected.push(best.candidate)
@@ -309,6 +313,7 @@ function buildMissing({ required, covered, pool, decomposition }) {
309
313
  const domain = capabilityDomain(capability) || decomposition.domains[0] || 'unknown'
310
314
  const suggestions = pool
311
315
  .filter((candidate) => !candidate.installed)
316
+ .filter((candidate) => decomposition.outputMode !== 'text' || TEXT_SAFE_DOMAINS.has(candidate.profile.primaryDomain))
312
317
  .map((candidate) => {
313
318
  let score = 0
314
319
  if ((candidate.profile.capabilities || []).includes(capability)) score += 2
@@ -609,6 +614,27 @@ export function composeSkillPlan(input = {}) {
609
614
  pool,
610
615
  decomposition,
611
616
  })
617
+ if (decomposition.outputMode === 'text' && selection.selected.length === 0) {
618
+ return {
619
+ ok: true,
620
+ ready: true,
621
+ reviewGap: false,
622
+ reviewGapSuggestions: [],
623
+ pattern: 'sequential',
624
+ stages: [],
625
+ missing: [],
626
+ conflicts: [],
627
+ estimates: { efficiency: efficiencyEstimate({ stages: [], missing: [], selected: [], pattern: 'sequential', baselineSkills: installedSkills.length }), skills: 0, stages: 0 },
628
+ assumptions: ['纯文本交付:不需要加载技能,直接生成正文;不把 office/video 技能强塞进计划。'],
629
+ confidence: Math.max(0.5, Number(decomposition.confidence) || 0.5),
630
+ coverage: { required: selection.required, covered: [], ratio: 0, primaryIntentCovered: false },
631
+ selected: [],
632
+ primaryIntent: decomposition.primaryIntent,
633
+ secondaryIntents: decomposition.secondaryIntents,
634
+ deliverables: (decomposition.deliverables || []).map((deliverable) => deliverable.id),
635
+ goal: truncate(decomposition.goal || task, 120),
636
+ }
637
+ }
612
638
  const conflicts = buildConflicts({
613
639
  selected: selection.selected,
614
640
  requiredDomains: selection.requiredDomains,
@@ -673,6 +699,16 @@ export function composeSkillPlan(input = {}) {
673
699
 
674
700
  const coverageRatio = selection.required.length ? selection.covered.size / selection.required.length : 1
675
701
  const criticalMissing = missing.filter((entry) => entry.critical).length
702
+ const reviewGapSuggestions = reviewGap
703
+ ? pool
704
+ .filter((candidate) => !candidate.installed)
705
+ .filter((candidate) => {
706
+ const caps = candidate.profile.capabilities || []
707
+ return ['code-review', 'security-audit', 'fact-checking', 'layout-review', 'contract-review', 'testing'].some((capability) => caps.includes(capability))
708
+ })
709
+ .slice(0, 3)
710
+ .map((candidate) => candidate.name)
711
+ : []
676
712
  const cleanStages = trimStages(stages, maxSkills, 5)
677
713
  const estimates = {
678
714
  efficiency: efficiencyEstimate({
@@ -704,6 +740,7 @@ export function composeSkillPlan(input = {}) {
704
740
  ok: cleanStages.length > 0,
705
741
  ready: cleanStages.length > 0 && criticalMissing === 0 && missing.length === 0,
706
742
  reviewGap,
743
+ reviewGapSuggestions,
707
744
  pattern: ALLOWED_PATTERNS.has(pattern) ? pattern : 'sequential',
708
745
  stages: cleanStages,
709
746
  missing,
@@ -326,6 +326,16 @@ function buildAssumptions({ task, deliverables, primaryIntent, secondaryIntents,
326
326
  * Decompose a user task into an executable intent/deliverable graph.
327
327
  * @param {{task?:string, cwd?:string, context?:string, mode?:string}} input
328
328
  */
329
+ const TEXT_OUTPUT_RE = /(正文|直接返回|不写文件|不需要文件|不用文件|仅文字|纯文本|作为文本|return (?:the )?text|no files?)/i
330
+ const FILE_OUTPUT_RE = /(\.docx|\.pdf|\.pptx|\.xlsx|\.mp4|保存为|写到|导出为|生成文件|make a file|create a file)/i
331
+
332
+ export function detectOutputMode(rawTask) {
333
+ const text = String(rawTask || '')
334
+ if (TEXT_OUTPUT_RE.test(text)) return 'text'
335
+ if (FILE_OUTPUT_RE.test(text)) return 'file'
336
+ return 'unknown'
337
+ }
338
+
329
339
  export function decomposeTask(input = {}) {
330
340
  const rawTask = typeof input.task === 'string' ? input.task : String(input.task || '')
331
341
  const task = normalizeText(rawTask)
@@ -343,6 +353,7 @@ export function decomposeTask(input = {}) {
343
353
  subTasks: [],
344
354
  assumptions: ['任务为空:未做任何推断。'],
345
355
  confidence: 0,
356
+ outputMode: 'unknown',
346
357
  language: 'unknown',
347
358
  }
348
359
  }
@@ -432,6 +443,7 @@ export function decomposeTask(input = {}) {
432
443
  subTasks,
433
444
  assumptions,
434
445
  confidence,
446
+ outputMode: detectOutputMode(rawTask),
435
447
  language: detectLanguage(rawTask),
436
448
  }
437
449
  }
@@ -306,6 +306,21 @@ function resolveContext(input) {
306
306
  * metadata, never writes or executes anything.
307
307
  * @param {{task?:string, plan?:object, missing?:object[], existingNames?:string[], context?:string}} input
308
308
  */
309
+ function draftScore({ lint, safety, evalCases, content, name }) {
310
+ let score = 100
311
+ const issues = lint && Array.isArray(lint.issues) ? lint.issues : []
312
+ score -= issues.filter((issue) => issue && issue.severity === 'error').length * 20
313
+ score -= issues.filter((issue) => issue && issue.severity === 'warn').length * 5
314
+ if (!content || content.length < 500) score -= 15
315
+ if (!Array.isArray(evalCases) || evalCases.length < 3) score -= 10
316
+ if (safety && safety.available && safety.level === 'high') score -= 60
317
+ else if (safety && safety.available && safety.level === 'medium') score -= 15
318
+ if (!name || name.length < 6) score -= 10
319
+ score = Math.max(0, Math.min(100, Math.round(score)))
320
+ const grade = score >= 85 ? 'A' : score >= 70 ? 'B' : score >= 55 ? 'C' : 'D'
321
+ return { score, grade }
322
+ }
323
+
309
324
  export function draftSkill(input = {}) {
310
325
  const { plan, task, decomposition } = resolveContext(input)
311
326
  const missing = Array.isArray(input.missing) && input.missing.length
@@ -430,9 +445,11 @@ export function draftSkill(input = {}) {
430
445
  })
431
446
  const safety = runSecurityScan(content)
432
447
  const similar = findSimilarNames(name, existingNames)
448
+ const quality = draftScore({ lint, safety, evalCases, content, name })
433
449
 
434
450
  return {
435
451
  ok: lint.ok && !(safety.available && safety.level === 'high'),
452
+ quality,
436
453
  name,
437
454
  description,
438
455
  whenToUse,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-ronge-adaptive-skill-archive",
3
- "version": "5.2.1",
3
+ "version": "5.3.1",
4
4
  "description": "DSH-native skill intelligence cockpit: task decomposition, cross-domain multi-skill composition, skill authoring, local recommendations and safety scans for DeepSeek Harness.",
5
5
  "type": "module",
6
6
  "repository": {
@@ -0,0 +1,83 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Live A/B protocol for task-level evidence. This script NEVER fabricates
4
+ * model runs: it either prints the protocol or aggregates records produced by
5
+ * a real DSH run.
6
+ *
7
+ * usage:
8
+ * node scripts/benchmark-live.mjs --dry-run
9
+ * node scripts/benchmark-live.mjs --record runs.json --out report.json
10
+ *
11
+ * run record shape:
12
+ * [{ taskId, arm: "baseline"|"plugin", success: true, tokens: 12000,
13
+ * toolCalls: 8, wallMs: 45000, notes: "..." }]
14
+ */
15
+ import fs from 'node:fs'
16
+ import path from 'node:path'
17
+ import { fileURLToPath } from 'node:url'
18
+
19
+ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
20
+ const TASKS_FILE = path.join(ROOT, 'test', 'fixtures', 'live-ab-tasks.json')
21
+ const args = process.argv.slice(2)
22
+ const flag = (name) => args.indexOf(name)
23
+ const value = (name) => (flag(name) >= 0 ? args[flag(name) + 1] : null)
24
+ const tasks = JSON.parse(fs.readFileSync(TASKS_FILE, 'utf8'))
25
+ const dryRun = flag('--dry-run') >= 0
26
+ const recordFile = value('--record')
27
+ const outFile = value('--out')
28
+
29
+ if (!dryRun && !recordFile) {
30
+ console.error('use --dry-run to print the protocol, or --record <file> to aggregate real run records')
31
+ process.exit(1)
32
+ }
33
+
34
+ function aggregate(records) {
35
+ const byArm = {}
36
+ for (const arm of ['baseline', 'plugin']) {
37
+ const rows = records.filter((record) => record.arm === arm)
38
+ const success = rows.filter((record) => record.success === true).length
39
+ const sum = (key) => rows.reduce((total, record) => total + (Number(record[key]) || 0), 0)
40
+ byArm[arm] = {
41
+ runs: rows.length,
42
+ successRate: rows.length ? Math.round((success / rows.length) * 1000) / 1000 : null,
43
+ meanTokens: rows.length ? Math.round(sum('tokens') / rows.length) : null,
44
+ meanToolCalls: rows.length ? Math.round((sum('toolCalls') / rows.length) * 10) / 10 : null,
45
+ meanWallMs: rows.length ? Math.round(sum('wallMs') / rows.length) : null,
46
+ }
47
+ }
48
+ return byArm
49
+ }
50
+
51
+ if (dryRun) {
52
+ const protocol = {
53
+ schema: 'skill-intelligence-live-ab/v1',
54
+ status: 'awaiting-real-runs',
55
+ tasks: tasks.length,
56
+ arms: ['baseline', 'plugin'],
57
+ instruction: 'Run each task twice (same model, same tools, same time budget). Arm "baseline" = no skill archive tools/panel; arm "plugin" = use skill_archive_plan then execute. Record success/tokens/toolCalls/wallMs per run.',
58
+ tasksDetail: tasks,
59
+ recordShape: { taskId: 'video-01', arm: 'baseline|plugin', success: true, tokens: 12000, toolCalls: 8, wallMs: 45000, notes: 'evidence' },
60
+ note: 'No numbers are produced until a real run is recorded; this script does not call a model.',
61
+ }
62
+ console.log(JSON.stringify(protocol, null, 2))
63
+ process.exit(0)
64
+ }
65
+
66
+ const raw = JSON.parse(fs.readFileSync(path.resolve(process.cwd(), recordFile), 'utf8'))
67
+ if (!Array.isArray(raw)) throw new Error('record file must be a JSON array')
68
+ const taskIds = new Set(tasks.map((task) => task.id))
69
+ for (const record of raw) {
70
+ if (!record || !taskIds.has(record.taskId)) throw new Error('unknown taskId: ' + JSON.stringify(record && record.taskId))
71
+ if (record.arm !== 'baseline' && record.arm !== 'plugin') throw new Error('arm must be baseline or plugin')
72
+ }
73
+ const report = {
74
+ schema: 'skill-intelligence-live-ab/v1',
75
+ status: 'recorded',
76
+ generatedAt: new Date().toISOString(),
77
+ tasks: tasks.length,
78
+ records: raw.length,
79
+ arms: aggregate(raw),
80
+ caveat: 'Task-level A/B; success is human/rubric reviewed, tokens/time are runner-reported. Do not compare across models or task sets.',
81
+ }
82
+ if (outFile) fs.writeFileSync(path.resolve(process.cwd(), outFile), JSON.stringify(report, null, 2) + '\n')
83
+ console.log(JSON.stringify(report, null, 2))
@@ -0,0 +1,157 @@
1
+ [
2
+ {
3
+ "id": "video-01",
4
+ "domain": "video",
5
+ "task": "做一条 30 秒产品发布宣传片,竖屏,带字幕和配乐",
6
+ "deliverable": "mp4",
7
+ "checks": [
8
+ "时长 30±3 秒",
9
+ "竖屏 1080x1920",
10
+ "字幕与音频同步"
11
+ ]
12
+ },
13
+ {
14
+ "id": "video-02",
15
+ "domain": "video",
16
+ "task": "把这段培训录屏剪成 2 分钟横屏视频,加章节标题",
17
+ "deliverable": "mp4",
18
+ "checks": [
19
+ "横屏 1920x1080",
20
+ "章节标题出现",
21
+ "导出可播放"
22
+ ]
23
+ },
24
+ {
25
+ "id": "office-01",
26
+ "domain": "office",
27
+ "task": "把会议录音整理成 Word 纪要,含决议、行动项和负责人",
28
+ "deliverable": "docx",
29
+ "checks": [
30
+ "有决议/行动项/负责人",
31
+ "docx 可打开",
32
+ "无占位符"
33
+ ]
34
+ },
35
+ {
36
+ "id": "office-02",
37
+ "domain": "office",
38
+ "task": "把 Excel 销售数据做成 8 页发布 PPT,带图表和结论",
39
+ "deliverable": "pptx",
40
+ "checks": [
41
+ "8 页左右",
42
+ "至少 2 张图表",
43
+ "结论页存在"
44
+ ]
45
+ },
46
+ {
47
+ "id": "office-03",
48
+ "domain": "office",
49
+ "task": "检查这份 docx 合同的金额、日期和付款条款是否一致",
50
+ "deliverable": "docx-report",
51
+ "checks": [
52
+ "逐条列出差异",
53
+ "金额/日期有出处",
54
+ "风险项标注"
55
+ ]
56
+ },
57
+ {
58
+ "id": "code-01",
59
+ "domain": "coding",
60
+ "task": "修复 React 组件测试失败并补一个回归测试",
61
+ "deliverable": "code",
62
+ "checks": [
63
+ "测试通过",
64
+ "新增回归用例",
65
+ "说明根因"
66
+ ]
67
+ },
68
+ {
69
+ "id": "data-01",
70
+ "domain": "data",
71
+ "task": "分析这份 CSV 的月度趋势,输出图表和三条结论",
72
+ "deliverable": "report",
73
+ "checks": [
74
+ "图表可打开",
75
+ "结论有数字支撑",
76
+ "口径说明"
77
+ ]
78
+ },
79
+ {
80
+ "id": "research-01",
81
+ "domain": "research",
82
+ "task": "调研 2026 年 AI 视频工具市场,给出 5 个来源和对比",
83
+ "deliverable": "report",
84
+ "checks": [
85
+ "至少 5 个来源",
86
+ "有对比维度",
87
+ "引用可访问"
88
+ ]
89
+ },
90
+ {
91
+ "id": "marketing-01",
92
+ "domain": "marketing",
93
+ "task": "为新品写一版小红书种草文案,含 3 个卖点和行动号召",
94
+ "deliverable": "text",
95
+ "checks": [
96
+ "3 个卖点",
97
+ "有 CTA",
98
+ "符合平台语气"
99
+ ]
100
+ },
101
+ {
102
+ "id": "security-01",
103
+ "domain": "security",
104
+ "task": "审查一个第三方 SKILL.md 的安装脚本风险,重点看 shell 与凭据",
105
+ "deliverable": "report",
106
+ "checks": [
107
+ "列出风险级别",
108
+ "有复现/证据",
109
+ "给修复建议"
110
+ ]
111
+ },
112
+ {
113
+ "id": "plugin-01",
114
+ "domain": "engineering",
115
+ "task": "做一个 DSH 面板插件,加一个设置项并验证安装/卸载",
116
+ "deliverable": "code",
117
+ "checks": [
118
+ "插件可启用",
119
+ "设置项生效",
120
+ "可卸载"
121
+ ]
122
+ },
123
+ {
124
+ "id": "productivity-01",
125
+ "domain": "productivity",
126
+ "task": "把本周零散笔记整理成周报,附下周三个优先事项",
127
+ "deliverable": "docx",
128
+ "checks": [
129
+ "结构完整",
130
+ "三个优先级",
131
+ "无虚构事实"
132
+ ]
133
+ },
134
+ {
135
+ "id": "pilot-brief",
136
+ "domain": "marketing",
137
+ "task": "写一份技能驾驶舱 v5.3 发布简报(150–250 字),含发布日期、3 个能力点、1 个风险提示、1 句行动号召;直接返回正文",
138
+ "deliverable": "text",
139
+ "checks": [
140
+ "日期 2026-10-30",
141
+ "3 个能力点",
142
+ "1 个风险提示",
143
+ "1 句 CTA"
144
+ ]
145
+ },
146
+ {
147
+ "id": "pilot-video-plan",
148
+ "domain": "video",
149
+ "task": "为 Excel 数据分析产品做 30 秒竖屏宣传视频,给出 3 阶段执行计划,每阶段含技能、输入、输出/交接物、验收标准",
150
+ "deliverable": "plan",
151
+ "checks": [
152
+ "3 阶段",
153
+ "每阶段输入/输出",
154
+ "每阶段验收标准"
155
+ ]
156
+ }
157
+ ]