oh-my-knowledge 0.35.0 → 0.37.0

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 (90) hide show
  1. package/README.md +5 -3
  2. package/README.zh.md +5 -3
  3. package/dist/assets/agent-skills/omk/SKILL.md +4 -2
  4. package/dist/assets/agent-skills/omk/references/commands.md +121 -2
  5. package/dist/authoring/evolver.js +1 -1
  6. package/dist/cli/commands/doctor.js +21 -5
  7. package/dist/cli/commands/eval/index.js +7 -6
  8. package/dist/cli/commands/evolve.d.ts +11 -0
  9. package/dist/cli/commands/evolve.js +105 -5
  10. package/dist/cli/commands/list.d.ts +25 -0
  11. package/dist/cli/commands/list.js +118 -0
  12. package/dist/cli/commands/promote.d.ts +24 -0
  13. package/dist/cli/commands/promote.js +158 -0
  14. package/dist/cli/commands/rollback.d.ts +20 -0
  15. package/dist/cli/commands/rollback.js +95 -0
  16. package/dist/cli/commands/sample.d.ts +1 -0
  17. package/dist/cli/commands/sample.js +10 -4
  18. package/dist/cli/lib/cell-format.d.ts +17 -0
  19. package/dist/cli/lib/cell-format.js +19 -0
  20. package/dist/cli/lib/cmd-flags.d.ts +1 -0
  21. package/dist/cli/lib/i18n-dict/common.d.ts +1 -1
  22. package/dist/cli/lib/i18n-dict/common.js +8 -0
  23. package/dist/cli/lib/i18n-dict/list.d.ts +3 -0
  24. package/dist/cli/lib/i18n-dict/list.js +36 -0
  25. package/dist/cli/lib/i18n-dict/promote.d.ts +3 -0
  26. package/dist/cli/lib/i18n-dict/promote.js +54 -0
  27. package/dist/cli/lib/i18n-dict/rollback.d.ts +3 -0
  28. package/dist/cli/lib/i18n-dict/rollback.js +22 -0
  29. package/dist/cli/lib/i18n-dict.d.ts +4 -1
  30. package/dist/cli/lib/i18n-dict.js +6 -0
  31. package/dist/cli/lib/progress.d.ts +3 -0
  32. package/dist/cli/lib/progress.js +22 -0
  33. package/dist/cli/lib/run-tally.js +1 -1
  34. package/dist/cli/lib/shared.js +1 -1
  35. package/dist/cli/lib/source-probe.d.ts +15 -0
  36. package/dist/cli/lib/source-probe.js +129 -0
  37. package/dist/doctor/endpoint-rule.d.ts +60 -0
  38. package/dist/doctor/endpoint-rule.js +381 -0
  39. package/dist/doctor/health/load-custom-dimensions.d.ts +6 -0
  40. package/dist/doctor/health/load-custom-dimensions.js +43 -3
  41. package/dist/doctor/index.js +9 -2
  42. package/dist/eval-core/evaluation-reporting.d.ts +1 -0
  43. package/dist/eval-core/evaluation-reporting.js +6 -4
  44. package/dist/eval-workflows/batch-evaluation-workflow.js +3 -2
  45. package/dist/eval-workflows/run-evaluation.js +2 -2
  46. package/dist/executors/codex-cli.js +5 -1
  47. package/dist/inputs/skill-loader.js +6 -3
  48. package/dist/managed/index.d.ts +2 -0
  49. package/dist/managed/index.js +2 -0
  50. package/dist/managed/list-view.d.ts +58 -0
  51. package/dist/managed/list-view.js +70 -0
  52. package/dist/managed/promote-gate.d.ts +40 -0
  53. package/dist/managed/promote-gate.js +37 -0
  54. package/dist/managed/store.d.ts +25 -2
  55. package/dist/managed/store.js +137 -11
  56. package/dist/observability/experience.d.ts +2 -0
  57. package/dist/observability/experience.js +31 -2
  58. package/dist/observability/inbox.js +31 -5
  59. package/dist/observability/review-state.js +22 -11
  60. package/dist/observability/soft-standards/llm-extractor.js +3 -3
  61. package/dist/observability/soft-standards/skill-standards-store.d.ts +1 -0
  62. package/dist/observability/soft-standards/skill-standards-store.js +77 -14
  63. package/dist/observability/soft-standards/types.d.ts +2 -2
  64. package/dist/renderer/doctor-detail-renderer.d.ts +9 -0
  65. package/dist/renderer/doctor-detail-renderer.js +114 -0
  66. package/dist/renderer/html-renderer.d.ts +3 -2
  67. package/dist/renderer/html-renderer.js +102 -102
  68. package/dist/renderer/icons.d.ts +29 -0
  69. package/dist/renderer/icons.js +66 -0
  70. package/dist/renderer/layout.js +81 -68
  71. package/dist/renderer/observation-inbox/styles.d.ts +1 -1
  72. package/dist/renderer/observation-inbox/styles.js +53 -53
  73. package/dist/renderer/report-shell.d.ts +77 -0
  74. package/dist/renderer/report-shell.js +223 -0
  75. package/dist/renderer/skill-detail-renderer.js +147 -352
  76. package/dist/renderer/skill-health-renderer.js +50 -73
  77. package/dist/renderer/skill-list-renderer.js +358 -306
  78. package/dist/renderer/summary.js +272 -162
  79. package/dist/renderer/test-view.d.ts +4 -3
  80. package/dist/renderer/test-view.js +386 -135
  81. package/dist/server/report-server.js +142 -28
  82. package/dist/server/report-store.d.ts +1 -1
  83. package/dist/server/report-store.js +16 -15
  84. package/dist/server/skill-index.js +5 -4
  85. package/dist/types/doctor.d.ts +24 -2
  86. package/dist/types/doctor.js +1 -1
  87. package/dist/types/managed.d.ts +17 -3
  88. package/dist/types/observability.d.ts +6 -6
  89. package/dist/types/report.d.ts +9 -4
  90. package/package.json +1 -1
package/README.md CHANGED
@@ -59,12 +59,14 @@ Codex does not support Claude Code style `/omk ...` slash commands. Ask the agen
59
59
 
60
60
  ```bash
61
61
  omk eval
62
- omk evolve skills/my-skill.md
62
+ omk evolve skills/my-skill.md # one-shot: doctor → (auto-generate samples if missing) → self-iterate
63
63
  omk sample skills/my-skill.md
64
64
  ```
65
65
 
66
66
  You can also describe the goal in natural language, such as "compare v1 vs v2" or "generate test cases for this skill".
67
67
 
68
+ > `omk evolve` is a one-shot loop: it runs the doctor gate first, auto-generates eval samples when the target skill has none, then self-iterates. For a brand-new skill, just run `omk evolve skills/foo.md`.
69
+
68
70
  ## Why this tool
69
71
 
70
72
  Teams doing knowledge engineering produce lots of knowledge artifacts (skills today, but also prompts, agents, workflows…). When someone asks "why is v2 better than v1", you need objective data instead of gut feeling. `oh-my-knowledge` solves this with controlled experiments: **same model, same test samples, only the knowledge artifact changes.**
@@ -101,7 +103,7 @@ RAG-specific evals: see RAGAS (separate niche, complementary to omk). Full compa
101
103
  | **Knowledge-gap detection** | severity-weighted signals quantify risk exposure instead of claiming completeness |
102
104
  | **Construct-validity isolation** | `--strict-baseline` (default ON) cuts three contamination channels so baseline doesn't silently see the skill it's being compared against |
103
105
  | **Git & remote sources** | install / eval from a local git ref or a remote git URL (`--git-url`); directory-skills run in a content-addressed **isolated copy** so `references/` assets are real measured input, not just `SKILL.md` |
104
- | **Evidence-gated management** | `omk install` registers a managed record; `omk eval` auto-writes evidence bound by content fingerprint, moving a skill `installed → measurable`. [spec →](docs/specs/evidence-gated-management.md) |
106
+ | **Evidence-gated management** | `omk install` registers a managed record; `omk eval` auto-writes evidence bound by content fingerprint, moving a skill `installed → measurable`; `omk list` surfaces each managed skill's status (installed / measurable / promoted / stale); `omk promote` accepts a version once its evidence passes the gate (default PROGRESS only); `omk rollback` revokes that acceptance, returning the skill to `measurable`. [spec →](docs/specs/evidence-gated-management.md) |
105
107
  | **Sample design science** | sample schema with `capability` / `difficulty` / `construct` / `provenance` metadata (HF Dataset Cards style); studio surfaces coverage breakdown plus `rubric_clarity_low` / `capability_thin` flags. [docs/specs/sample-design-spec.md](docs/specs/sample-design-spec.md) |
106
108
  | **Multi-judge ensemble** | `--judge-models claude:opus,openai:gpt-4o` cross-vendor scoring + agreement metrics |
107
109
  | **Blind A/B** | `--blind` hides variant names; HTML report has a reveal button |
@@ -124,7 +126,7 @@ The full docs are published at **[oh-my-knowledge.pages.dev](https://oh-my-knowl
124
126
  - **[Sample design spec](docs/specs/sample-design-spec.md)** — capability / construct / provenance metadata; industry-gap mapping
125
127
  - **[Statistical rigor](docs/explanation/statistical-rigor.md)** — why bootstrap CI / α / length-debias / saturation matter
126
128
  - **[Comparison with 7 tools](docs/reference/comparison.md)** — 25+ dimensions across promptfoo / DeepEval / RAGAS / OpenAI Evals / LangSmith / lm-eval-harness / inspect-ai
127
- - **[Evidence-gated management](docs/specs/evidence-gated-management.md)** — managed records, lifecycle states (installed / measurable / stale), install → eval → measurable
129
+ - **[Evidence-gated management](docs/specs/evidence-gated-management.md)** — managed records, lifecycle states (installed / measurable / promoted / stale), install → eval → measurable → promote → rollback
128
130
 
129
131
  ## Environment variables
130
132
 
package/README.zh.md CHANGED
@@ -59,12 +59,14 @@ Codex 默认不支持 `/omk ...` 这种 Claude Code 风格的 slash command。
59
59
 
60
60
  ```bash
61
61
  omk eval
62
- omk evolve skills/my-skill.md
62
+ omk evolve skills/my-skill.md # 一键:体检 →(无用例则自动生成)→ 自迭代
63
63
  omk sample skills/my-skill.md
64
64
  ```
65
65
 
66
66
  也可以直接用自然语言描述目标,例如「比较 v1 和 v2 的评测差异」、「为这个 skill 生成评测用例」。
67
67
 
68
+ > `omk evolve` 是一键闭环:默认先跑 doctor 体检,目标 skill 没有评测用例时会自动生成一批,再进入多轮自迭代。全新 skill 直接 `omk evolve skills/foo.md` 即可。
69
+
68
70
  ## 为什么需要这个工具
69
71
 
70
72
  做知识工程的团队会产出大量知识载体(当前常见是 skill,也包括 prompt、agent、workflow 等)。当被问到「v2 比 v1 好在哪」时,需要客观数据而非主观判断。`oh-my-knowledge` 通过控制变量实验解决这个问题:**相同模型、相同评测用例,只改变知识载体。**
@@ -101,7 +103,7 @@ RAG 专项评测请看 RAGAS(独立 niche,跟 omk 互补)。完整对比
101
103
  | **知识缺口识别** | 严重度加权的信号量化风险敞口,不宣称完备性 |
102
104
  | **用例隔离 (construct validity)** | `--strict-baseline`(默认开)三堵 baseline 拿到被测 skill 的污染路径 |
103
105
  | **Git / 远端源** | install / eval 支持本地 git ref 或远端 git URL(`--git-url`);目录-skill 在内容寻址**隔离副本**里执行,`references/` 资产是真实测量输入,不只是 `SKILL.md` |
104
- | **证据门控管理** | `omk install` 登记受管记录;`omk eval` 按内容指纹自动写入证据,把 skill 从 `installed` 推到 `measurable`。[规范 →](docs/zh/specs/evidence-gated-management.md) |
106
+ | **证据门控管理** | `omk install` 登记受管记录;`omk eval` 按内容指纹自动写入证据,把 skill 从 `installed` 推到 `measurable`;`omk list` 查看各受管 skill 的状态(installed / measurable / promoted / stale);`omk promote` 在证据过门禁(默认仅 PROGRESS)后把该版本接受为当前版本;`omk rollback` 撤销这次接受,让 skill 回到 `measurable`。[规范 →](docs/zh/specs/evidence-gated-management.md) |
105
107
  | **用例设计科学性** | Sample schema 加 `capability` / `difficulty` / `construct` / `provenance` 元数据字段(HF Dataset Cards 风),studio 输出 coverage 分桶 + `rubric_clarity_low` / `capability_thin` issue。[docs/zh/specs/sample-design-spec.md](docs/zh/specs/sample-design-spec.md) |
106
108
  | **多评委 ensemble** | `--judge-models claude:opus,openai:gpt-4o` 跨厂商评分 + agreement 度量 |
107
109
  | **盲测 A/B** | `--blind` 隐藏变体名称,HTML 报告有揭晓按钮 |
@@ -124,7 +126,7 @@ RAG 专项评测请看 RAGAS(独立 niche,跟 omk 互补)。完整对比
124
126
  - **[用例设计规范](docs/zh/specs/sample-design-spec.md)** —— capability / construct / provenance 元数据;行业 gap 映射
125
127
  - **[统计严谨性](docs/zh/explanation/statistical-rigor.md)** —— 为什么 Bootstrap CI / α / 长度去偏 / 饱和曲线重要
126
128
  - **[7 工具对比](docs/zh/reference/comparison.md)** —— promptfoo / DeepEval / RAGAS / OpenAI Evals / LangSmith / lm-eval-harness / inspect-ai 等 25+ 维度横评
127
- - **[证据门控管理](docs/zh/specs/evidence-gated-management.md)** —— 受管记录、生命周期状态(installed / measurable / stale)、install → eval → measurable
129
+ - **[证据门控管理](docs/zh/specs/evidence-gated-management.md)** —— 受管记录、生命周期状态(installed / measurable / promoted / stale)、install → eval → measurable → promote → rollback
128
130
 
129
131
  ## 环境变量
130
132
 
@@ -4,7 +4,7 @@ description: |
4
4
  oh-my-knowledge 知识载体评测工具的智能代理。评测 skill(系统提示词)质量,对比不同版本效果,自动迭代改进。
5
5
  Use when: 用户提到"评测"、"测评"、"eval"、"benchmark"、"对比 skill"、"改进 skill"、"evolve"、"生成测试用例"、"gen-samples"、"omk"。
6
6
  user-invocable: true
7
- argument-hint: "<doctor|eval|evolve|init|install|observe|sample|studio> [options]"
7
+ argument-hint: "<doctor|eval|evolve|init|install|list|observe|promote|rollback|sample|studio> [options]"
8
8
  ---
9
9
 
10
10
  # OMK — 知识载体评测
@@ -19,7 +19,7 @@ argument-hint: "<doctor|eval|evolve|init|install|observe|sample|studio> [options
19
19
  npm i oh-my-knowledge -g
20
20
  ```
21
21
 
22
- omk CLI 顶层命令包括:`init` / `install` / `doctor` / `eval` / `observe` / `evolve` / `sample` / `studio`。没有 `bench` / `improve` / `gen-samples` 这些旧子命令名 —— 如果你在历史 SKILL / 文档里看到了,那是 v0.30 命令树重构之前的写法。
22
+ omk CLI 顶层命令包括:`init` / `install` / `list` / `promote` / `rollback` / `doctor` / `eval` / `observe` / `evolve` / `sample` / `studio`。没有 `bench` / `improve` / `gen-samples` 这些旧子命令名 —— 如果你在历史 SKILL / 文档里看到了,那是 v0.30 命令树重构之前的写法。
23
23
 
24
24
  ## 第二步:理解用户意图
25
25
 
@@ -33,6 +33,8 @@ omk CLI 顶层命令包括:`init` / `install` / `doctor` / `eval` / `observe`
33
33
  | 体检 skill 写法 | → `omk doctor` |
34
34
  | 查看 / 浏览报告 | → `omk studio`(启动本地报告浏览器) |
35
35
  | 看真实使用 trace | → `omk observe` |
36
+ | 查看受管 skill 状态 | → `omk list` |
37
+ | 按证据接受 / 回退某版本 | → `omk promote` / `omk rollback` |
36
38
 
37
39
  如果用户意图不明确,先扫描当前项目结构(skills/ 目录和 eval-samples 文件),然后推荐最合适的操作。
38
40
 
@@ -20,7 +20,7 @@ omk doctor [target] [flags]
20
20
 
21
21
  **Flags:**
22
22
 
23
- - `--dimensions` `option`:自定义维度配置文件(YAML),追加到内置 7 维度之后。
23
+ - `--dimensions` `option`:自定义维度配置文件(YAML),追加到内置 7 维度之后。每条维度二选一:promptSection(走 LLM 体检)或 endpoint(POST skill 快照给接口判定)。注意:endpoint 会把 SKILL.md 全文 + 子文件发到该地址,仅对可信配置/可信地址启用。
24
24
  - `--effort` `option`:LLM 推理 effort:low / medium / high / xhigh / max。
25
25
  - `--executor` `option`:执行器名,默认 claude。指定为测试 fixture 路径可在测试里跑(同 omk doctor)。
26
26
  - `--fix` `boolean`:交互式修复:根据 doctor 报告问题,用 LLM agent 修复 skill。
@@ -222,7 +222,7 @@ omk evolve <skillPath> [flags]
222
222
  - `--improve-model` `option` (默认 `sonnet`):负责重写 skill 的 LLM,默认 sonnet
223
223
  - `--judge-models` `option` (默认 `claude:haiku`):评委 model(单评委约束),格式 executor:model。默认 claude:haiku
224
224
  - `--lang` `option` (默认 `zh`):输出语言 zh|en,优先级 CLI > OMK_LANG env > zh。
225
- - `--model` `option` (默认 `sonnet`):被评测的 LLM,默认 sonnet
225
+ - `--model` `option` (默认 `sonnet`):被评测的 LLM,默认 sonnet。无用例时也用作自动生成用例的出题模型。
226
226
  - `--no-diagnostic` `boolean`:关 LLM diagnostic 调用
227
227
  - `--no-edit-budget` `boolean`:关掉 edit budget 约束(允许任意大小的单轮改动)
228
228
  - `--no-reject-memory` `boolean`:关掉 rejected-edit 记忆(不把被拒改法回灌下一轮 prompt)
@@ -348,6 +348,42 @@ omk install git:main:skills/review
348
348
  omk install --git-url https://github.com/org/repo.git --git-ref v1.0.0 skills/review
349
349
  ```
350
350
 
351
+ ## omk list
352
+
353
+ 列出受管 skill 及其证据状态:生命周期(installed / measurable / promoted / stale)、最新 verdict、证据数、源。
354
+
355
+ **用法:**
356
+
357
+ ```bash
358
+ omk list [flags]
359
+ ```
360
+
361
+ **Flags:**
362
+
363
+ - `--global` `boolean`:看全局受管目录(~/.oh-my-knowledge/managed)而非项目 .omk/managed
364
+ - `--json` `boolean`:输出 JSON(含完整可比性 marker),供脚本消费
365
+ - `--lang` `option` (默认 `zh`):输出语言 zh|en,优先级 CLI > OMK_LANG env > zh。
366
+
367
+ **示例:**
368
+
369
+ > 列出当前项目的受管 skill
370
+
371
+ ```bash
372
+ omk list
373
+ ```
374
+
375
+ > 列出全局受管 skill
376
+
377
+ ```bash
378
+ omk list --global
379
+ ```
380
+
381
+ > 机器可读 JSON 输出
382
+
383
+ ```bash
384
+ omk list --json
385
+ ```
386
+
351
387
  ## omk observe
352
388
 
353
389
  分析 sessions 目录的 skill 调用健康度(默认行为)。子命令:ingest / inbox / show。
@@ -443,6 +479,88 @@ omk observe show <inboxId> [flags]
443
479
  - `--input-dir` `option`:inbox 数据目录
444
480
  - `--lang` `option` (默认 `zh`):输出语言 zh|en,优先级 CLI > OMK_LANG env > zh。
445
481
 
482
+ ## omk promote
483
+
484
+ 把受管 skill 的当前版本按证据门禁「接受」为 promoted:默认仅放行 verdict=PROGRESS,在记录里追加一条带证据指针的人工决定。
485
+
486
+ **用法:**
487
+
488
+ ```bash
489
+ omk promote <name> [flags]
490
+ ```
491
+
492
+ **参数:**
493
+
494
+ - `name`(必填):受管 skill 名(omk list 里的 NAME)
495
+
496
+ **Flags:**
497
+
498
+ - `--accept-cautious` `boolean`:把 CAUTIOUS 也算可接受(默认仅 PROGRESS)
499
+ - `--actor` `option`:决定的 actor(默认取 git config user.name)
500
+ - `--force` `boolean`:越过可越门拦截强制 promote,记为人工 override 决定(无当前证据或源 hash 已变时仍拒)
501
+ - `--global` `boolean`:操作全局受管目录而非项目 .omk/managed
502
+ - `--json` `boolean`:输出 JSON(版本化信封)供脚本消费
503
+ - `--kind` `option` (默认 `skill`):artifact 类型(当前仅 skill)
504
+ - `--lang` `option` (默认 `zh`):输出语言 zh|en,优先级 CLI > OMK_LANG env > zh。
505
+ - `--reason` `option`:promote / 越门的理由(写入决定)
506
+
507
+ **示例:**
508
+
509
+ > promote 一个证据达标的 skill
510
+
511
+ ```bash
512
+ omk promote review
513
+ ```
514
+
515
+ > 接受 CAUTIOUS 结果(显式放宽门禁)
516
+
517
+ ```bash
518
+ omk promote review --accept-cautious
519
+ ```
520
+
521
+ > 越门 promote 并记录理由(人工 override)
522
+
523
+ ```bash
524
+ omk promote review --force --reason "已人工复核"
525
+ ```
526
+
527
+ ## omk rollback
528
+
529
+ 回退受管 skill 当前版本的 promoted 接受:撤销最近一次 promote,在记录里追加一条 rollback 决定(源未漂移则状态回到 measurable,源已漂移则仍 stale)。
530
+
531
+ **用法:**
532
+
533
+ ```bash
534
+ omk rollback <name> [flags]
535
+ ```
536
+
537
+ **参数:**
538
+
539
+ - `name`(必填):受管 skill 名(omk list 里的 NAME)
540
+
541
+ **Flags:**
542
+
543
+ - `--actor` `option`:决定的 actor(默认取 git config user.name)
544
+ - `--global` `boolean`:操作全局受管目录而非项目 .omk/managed
545
+ - `--json` `boolean`:输出 JSON(版本化信封)供脚本消费
546
+ - `--kind` `option` (默认 `skill`):artifact 类型(当前仅 skill)
547
+ - `--lang` `option` (默认 `zh`):输出语言 zh|en,优先级 CLI > OMK_LANG env > zh。
548
+ - `--reason` `option`:回退的理由(写入决定)
549
+
550
+ **示例:**
551
+
552
+ > 回退一个已 promoted 的 skill
553
+
554
+ ```bash
555
+ omk rollback review
556
+ ```
557
+
558
+ > 回退并记录理由
559
+
560
+ ```bash
561
+ omk rollback review --reason "线上发现回归"
562
+ ```
563
+
446
564
  ## omk sample
447
565
 
448
566
  为指定 skill 生成评测用例(eval-samples),支持 batch / single / fix / from-traces 四种模式。
@@ -461,6 +579,7 @@ omk sample [skillPath] [flags]
461
579
 
462
580
  - `--batch` `boolean`:批量模式:扫 --skill-dir 下所有缺 samples 的 skill,逐个生成。
463
581
  - `--count` `option`:生成用例条数。不传由 LLM 按 skill 类型自动决定。
582
+ - `--executor` `option`:执行器名,默认 claude(同 omk eval / doctor / evolve)。指定 codex 等其它执行器时,记得连带传一个该执行器能识别的 --model。
464
583
  - `--fix` `boolean`:fix 模式:基于最近评测报告自动修复 sample_design 类型失败。
465
584
  - `--focus` `option`:生成焦点(自然语言提示)。控制 LLM 偏向哪类用例。
466
585
  - `--from-traces` `boolean`:from-traces 模式:从 observe inbox 的失败信号回流生成回归用例草稿(provenance: production-trace),落草稿待人工 review。
@@ -546,7 +546,7 @@ export function mergeEvolveReports(roundReports, skillName, totalCostUSD, sample
546
546
  }
547
547
  const runId = `evolve-${skillName}-${generateRunId([skillName]).split('-').slice(-2).join('-')}`;
548
548
  const report = {
549
- reportKind: 'evaluation',
549
+ kind: 'evaluation',
550
550
  id: runId,
551
551
  meta: {
552
552
  ...firstReport.meta,
@@ -7,6 +7,7 @@ import { BaseCommand } from '../oclif/base-command.js';
7
7
  import { numberStringParser } from '../oclif/parsers.js';
8
8
  import { CliExit } from '../lib/cli-exit.js';
9
9
  import { tCli } from '../lib/i18n.js';
10
+ import { makeDoctorProgress } from '../lib/progress.js';
10
11
  const DEFAULT_SAMPLE_FILENAMES = ['eval-samples.json', 'eval-samples.yaml', 'eval-samples.yml'];
11
12
  function findSamplesInDir(dir) {
12
13
  for (const name of DEFAULT_SAMPLE_FILENAMES) {
@@ -136,8 +137,8 @@ export default class Doctor extends BaseCommand {
136
137
  }),
137
138
  dimensions: Flags.string({
138
139
  description: bilingual({
139
- zh: '自定义维度配置文件(YAML),追加到内置 7 维度之后。',
140
- en: 'Custom dimensions config file (YAML), appended after builtin 7 dimensions.',
140
+ zh: '自定义维度配置文件(YAML),追加到内置 7 维度之后。每条维度二选一:promptSection(走 LLM 体检)或 endpoint(POST skill 快照给接口判定)。注意:endpoint 会把 SKILL.md 全文 + 子文件发到该地址,仅对可信配置/可信地址启用。',
141
+ en: 'Custom dimensions config file (YAML), appended after builtin 7. Each is either promptSection (LLM audit) or endpoint (POST skill snapshot to your service). Note: endpoint sends the full SKILL.md + sub-files to that URL — only enable for trusted configs/URLs.',
141
142
  }),
142
143
  }),
143
144
  'static-only': Flags.boolean({
@@ -205,9 +206,15 @@ export default class Doctor extends BaseCommand {
205
206
  const { renderDoctorReportText, renderDoctorReportJson } = await import('../../doctor/renderer.js');
206
207
  const { getRegisteredRules } = await import('../../doctor/rules.js');
207
208
  const { isComposerRule } = await import('../../types/doctor.js');
209
+ // 在线检查(LLM health composer + endpoint 自定义维度)默认跑;--static-only
210
+ // 离线模式只跑纯静态的内置 rule(无网络 / LLM)。endpoint rule 标了 external=true。
211
+ const isOnline = (r) => isComposerRule(r) || r.external === true;
208
212
  const rulesOverride = staticOnly
209
- ? getRegisteredRules().filter((r) => !isComposerRule(r))
210
- : getRegisteredRules().filter(isComposerRule);
213
+ ? getRegisteredRules().filter((r) => !isOnline(r))
214
+ : getRegisteredRules().filter(isOnline);
215
+ // 批量体检进度(per-skill,写 stderr)。--gate 是静默模式,不报进度;
216
+ // --json 进度走 stderr 不污染 stdout 的 JSON。
217
+ const onProgress = flags.gate ? undefined : makeDoctorProgress(lang);
211
218
  let report;
212
219
  try {
213
220
  report = await runDoctor({
@@ -221,6 +228,7 @@ export default class Doctor extends BaseCommand {
221
228
  rules: rulesOverride,
222
229
  samples,
223
230
  requires,
231
+ onProgress,
224
232
  });
225
233
  }
226
234
  catch (err) {
@@ -275,9 +283,16 @@ function persistDoctorReport(report, outputDir) {
275
283
  mkdirSync(dir, { recursive: true });
276
284
  const safeId = report.id.replace(/[/\\:*?"<>|]/g, '_');
277
285
  for (const skill of report.skills) {
286
+ const counts = { pass: 0, warn: 0, fail: 0, skipped: 0 };
287
+ for (const r of skill.results) {
288
+ const s = r.status;
289
+ if (s in counts)
290
+ counts[s]++;
291
+ }
278
292
  const perSkill = {
279
293
  ...report,
280
294
  skills: [skill],
295
+ ruleStats: { ...counts, total: skill.results.length },
281
296
  totals: {
282
297
  pass: skill.status === 'pass' ? 1 : 0,
283
298
  warn: skill.status === 'warn' ? 1 : 0,
@@ -300,7 +315,8 @@ export function pruneDoctorHistory(dir, skillName, maxKeep) {
300
315
  continue;
301
316
  try {
302
317
  const data = JSON.parse(readFileSync(join(dir, file), 'utf-8'));
303
- if (data?.reportKind !== 'doctor' || !Array.isArray(data.skills) || data.skills.length !== 1)
318
+ const kind = data?.kind === 'doctor' ? data.kind : null;
319
+ if (!kind || !Array.isArray(data.skills) || data.skills.length !== 1)
304
320
  continue;
305
321
  if (data.skills[0].skillName !== skillName)
306
322
  continue;
@@ -8,6 +8,7 @@ import { parseRunConfig } from '../../lib/parse-run-config.js';
8
8
  import { makeOnProgress } from '../../lib/progress.js';
9
9
  import { computeRunTally } from '../../lib/run-tally.js';
10
10
  import { DEFAULT_BOOTSTRAP_SAMPLES } from '../../../eval-core/bootstrap.js';
11
+ import { EVALUATION_REPORT_SCHEMA_VERSION } from '../../../eval-core/evaluation-reporting.js';
11
12
  function isDryRunReport(report) {
12
13
  return Boolean(report && typeof report === 'object' && report.dryRun === true);
13
14
  }
@@ -65,16 +66,16 @@ async function recordEvidenceSafely(report, verdict, values, lang) {
65
66
  }
66
67
  function batchItemFallbackReport(batch, item) {
67
68
  return {
68
- reportKind: 'evaluation',
69
+ kind: 'evaluation',
69
70
  id: item.reportId,
70
71
  meta: {
71
72
  ...batch.meta,
72
73
  variants: ['baseline', item.name],
73
74
  sampleCount: item.sampleCount,
74
75
  totalCostUSD: item.totalCostUSD,
75
- // item.artifactHash 来自子报告(走 aggregateReport 的整树哈),故 fallback 与之一致标 schemaVersion 3,
76
- // 避免「树哈 artifactHashes + 错位 schemaVersion」的错配。
77
- schemaVersion: 3,
76
+ // item.artifactHash 来自子报告(走 aggregateReport 的整树哈),故 fallback 与之一致标当前 eval
77
+ // report schemaVersion,避免「树哈 artifactHashes + 错位 schemaVersion」的错配。
78
+ schemaVersion: EVALUATION_REPORT_SCHEMA_VERSION,
78
79
  artifactHashes: item.artifactHash ? { [item.name]: item.artifactHash } : {},
79
80
  },
80
81
  summary: item.summary,
@@ -88,7 +89,7 @@ async function loadBatchChildReports(batch, reportsDir, lang) {
88
89
  const reports = [];
89
90
  for (const item of batch.items) {
90
91
  const loaded = await store.get(item.reportId);
91
- if (loaded?.reportKind === 'evaluation') {
92
+ if (loaded?.kind === 'evaluation') {
92
93
  reports.push(loaded);
93
94
  }
94
95
  else {
@@ -128,7 +129,7 @@ async function emitBatchVerdict(report, reportsDir, values, lang) {
128
129
  }
129
130
  async function announceSavedReport({ report, filePath, reportsDir, values, lang, }) {
130
131
  const tally = computeRunTally(report);
131
- process.stderr.write(tCli(report.reportKind === 'batch-evaluation' ? 'cli.run.batch_complete' : 'cli.run.eval_complete', lang));
132
+ process.stderr.write(tCli(report.kind === 'batch-evaluation' ? 'cli.run.batch_complete' : 'cli.run.eval_complete', lang));
132
133
  process.stderr.write(tCli('cli.run.tally', lang, tally));
133
134
  process.stderr.write(tCli('cli.run.report_saved', lang, { path: filePath }));
134
135
  if (!values['no-serve'] && process.stdout.isTTY) {
@@ -1,6 +1,17 @@
1
1
  import { BaseCommand } from '../oclif/base-command.js';
2
2
  import { type CliLang } from '../lib/i18n.js';
3
3
  import type { EvolveArgs, EvolveFlags } from '../lib/cmd-flags.js';
4
+ /** 路径处是否已存在「用例源」:按 statSync 判型 —— 文件(含无扩展名)直接算存在;
5
+ * 目录看是否含候选用例文件(排除 report/health/_ 前缀,对齐 sample.ts 的发现约定)。
6
+ * 用于区分「损坏文件(存在但解析失败 → 报错不覆盖)」与「确实没有用例(可生成)」。
7
+ * 不能用 extname 猜文件/目录:无扩展名的损坏样本文件会绕过守卫被覆盖,
8
+ * 带点的目录名(如 samples.v2/)会被误当文件。 */
9
+ export declare function sampleSourceExists(p: string): boolean;
10
+ /** 自动生成时的落盘目标:已存在的目录(含带点目录名,如 samples.v2/)→ 写进目录内的
11
+ * samples.json;已存在的文件 → 覆盖该文件;不存在 → 按扩展名(有扩展名当文件,无扩展名
12
+ * 当目录,落 samples.json)。与 sampleSourceExists 同用 statSync 判型,不被带点目录名
13
+ * 误当成文件(否则 writeFileSync 撞 EISDIR)。 */
14
+ export declare function resolveSampleOutFile(samplesAbs: string): string;
4
15
  export declare function runEvolve(args: EvolveArgs, flags: EvolveFlags, lang: CliLang): Promise<void>;
5
16
  export default class Evolve extends BaseCommand {
6
17
  static description: string;
@@ -1,5 +1,5 @@
1
- import { resolve, join } from 'node:path';
2
- import { existsSync } from 'node:fs';
1
+ import { resolve, join, dirname, extname } from 'node:path';
2
+ import { existsSync, readFileSync, readdirSync, mkdirSync, writeFileSync, statSync } from 'node:fs';
3
3
  import { Args, Flags } from '@oclif/core';
4
4
  import { LANG_FLAG, bilingual } from '../oclif/i18n.js';
5
5
  import { BaseCommand } from '../oclif/base-command.js';
@@ -18,6 +18,40 @@ function validateEvolveEffort(raw, lang) {
18
18
  }
19
19
  return raw;
20
20
  }
21
+ /** 路径处是否已存在「用例源」:按 statSync 判型 —— 文件(含无扩展名)直接算存在;
22
+ * 目录看是否含候选用例文件(排除 report/health/_ 前缀,对齐 sample.ts 的发现约定)。
23
+ * 用于区分「损坏文件(存在但解析失败 → 报错不覆盖)」与「确实没有用例(可生成)」。
24
+ * 不能用 extname 猜文件/目录:无扩展名的损坏样本文件会绕过守卫被覆盖,
25
+ * 带点的目录名(如 samples.v2/)会被误当文件。 */
26
+ export function sampleSourceExists(p) {
27
+ let st;
28
+ try {
29
+ st = statSync(p);
30
+ }
31
+ catch {
32
+ return false;
33
+ }
34
+ if (!st.isDirectory())
35
+ return true;
36
+ try {
37
+ return readdirSync(p).some((f) => /\.(json|ya?ml)$/i.test(f) && !/^(report|health|_)/i.test(f));
38
+ }
39
+ catch {
40
+ return false;
41
+ }
42
+ }
43
+ /** 自动生成时的落盘目标:已存在的目录(含带点目录名,如 samples.v2/)→ 写进目录内的
44
+ * samples.json;已存在的文件 → 覆盖该文件;不存在 → 按扩展名(有扩展名当文件,无扩展名
45
+ * 当目录,落 samples.json)。与 sampleSourceExists 同用 statSync 判型,不被带点目录名
46
+ * 误当成文件(否则 writeFileSync 撞 EISDIR)。 */
47
+ export function resolveSampleOutFile(samplesAbs) {
48
+ try {
49
+ return statSync(samplesAbs).isDirectory() ? join(samplesAbs, 'samples.json') : samplesAbs;
50
+ }
51
+ catch {
52
+ return extname(samplesAbs) ? samplesAbs : join(samplesAbs, 'samples.json');
53
+ }
54
+ }
21
55
  // runEvolve module-level helper:cli-exit.test 测「skillPath 空 throw CliExit(1)」走
22
56
  // in-process import 验证业务,Command.run() body 直接调它。
23
57
  export async function runEvolve(args, flags, lang) {
@@ -47,13 +81,79 @@ export async function runEvolve(args, flags, lang) {
47
81
  if (samplesFile === 'eval-samples.json' && !existsSync(resolve(samplesFile))) {
48
82
  samplesFile = resolvedInput.samplesPath;
49
83
  }
50
- const { evolveSkill } = await import('../../authoring/evolver.js');
84
+ // 参数校验必须早于任何昂贵副作用(自动生成用例 / LLM 调用)
51
85
  const { parseJudgeModelsArgOrExit } = await import('../lib/parse-run-config.js');
52
86
  const evolveJudges = parseJudgeModelsArgOrExit(flags['judge-models']);
53
87
  if (evolveJudges.length > 1) {
54
88
  console.error(tCli('cli.common.judge_models_single_only', lang, { cmd: 'omk evolve' }));
55
89
  throw new CliExit(2);
56
90
  }
91
+ // 无用例时自动生成 —— 让 omk evolve 成为「检测(doctor) → 生成用例 → 自迭代」一键命令。
92
+ // 已有用例(且非空)则原样使用;生成失败按普通错误退出。
93
+ const samplesAbs = resolve(samplesFile);
94
+ let hasSamples = false;
95
+ let loadErr = null;
96
+ try {
97
+ const { loadSamples } = await import('../../inputs/load-samples.js');
98
+ hasSamples = loadSamples(samplesAbs).samples.length > 0;
99
+ }
100
+ catch (err) {
101
+ loadErr = err;
102
+ }
103
+ const sourceExists = sampleSourceExists(samplesAbs);
104
+ // 用例源已存在却解析失败(JSON/YAML 语法错、duplicate id 等)= 损坏文件,
105
+ // 绝不用 LLM 生成内容覆盖它 —— 报错退出,让用户先修。只有真的没有用例源(文件
106
+ // 不存在 / 目录无候选用例文件)才进入自动生成。
107
+ if (loadErr && sourceExists) {
108
+ console.error(lang === 'zh'
109
+ ? `评测用例文件解析失败,evolve 不会覆盖它,请先修复:${samplesAbs}\n 原因:${loadErr.message}`
110
+ : `Failed to parse the samples source; evolve will not overwrite it. Fix it first: ${samplesAbs}\n reason: ${loadErr.message}`);
111
+ throw new CliExit(1);
112
+ }
113
+ if (!hasSamples) {
114
+ try {
115
+ const { generateSamples } = await import('../../authoring/generator.js');
116
+ const skillContent = readFileSync(resolve(skillPath), 'utf-8');
117
+ // outFile:已存在目录写进内部 samples.json,已存在文件覆盖,不存在按扩展名 —— statSync
118
+ // 判型,不被带点目录名(samples.v2/)骗。loadSamples 目录模式会自动发现生成的文件。
119
+ const outFile = resolveSampleOutFile(samplesAbs);
120
+ // sourceExists 为真 = 用例源存在但解析出 0 条(合法空 [])→ 明示"重新生成覆盖空文件",
121
+ // 不静默盖用户文件;为假 = 真没有用例源 → "未发现,生成"。
122
+ process.stderr.write(lang === 'zh'
123
+ ? (sourceExists
124
+ ? `评测用例为空,正在重新生成并覆盖 ${outFile} …\n`
125
+ : `未发现评测用例,正在自动生成到 ${outFile} …\n`)
126
+ : (sourceExists
127
+ ? `Samples are empty; regenerating (overwriting) ${outFile} …\n`
128
+ : `No samples found; auto-generating to ${outFile} …\n`));
129
+ const { samples, costUSD } = await generateSamples({
130
+ skillContent,
131
+ model: flags.model,
132
+ executorName: flags.executor,
133
+ });
134
+ // 模型可能保守返回 0 条 —— 不写空文件再空跑迭代,直接报错让用户改用
135
+ // `omk sample --focus` 引导生成或手写用例。
136
+ if (samples.length === 0) {
137
+ console.error(lang === 'zh'
138
+ ? '自动生成返回 0 条用例,已中止。请用 `omk sample <skill> --focus "…"` 引导生成,或手写后重试。'
139
+ : 'Auto-generation produced 0 samples; aborting. Use `omk sample <skill> --focus "…"` to guide generation, or write samples manually.');
140
+ throw new CliExit(1);
141
+ }
142
+ mkdirSync(dirname(outFile), { recursive: true });
143
+ writeFileSync(outFile, JSON.stringify(samples, null, 2));
144
+ const cost = costUSD > 0 ? ` $${costUSD.toFixed(4)}` : '';
145
+ process.stderr.write(lang === 'zh'
146
+ ? `已生成 ${samples.length} 条用例${cost},开始自迭代。\n`
147
+ : `Generated ${samples.length} samples${cost}; starting evolution.\n`);
148
+ }
149
+ catch (err) {
150
+ if (err instanceof CliExit)
151
+ throw err;
152
+ console.error(tCli('cli.common.error_prefix', lang, { message: err.message }));
153
+ throw new CliExit(1);
154
+ }
155
+ }
156
+ const { evolveSkill } = await import('../../authoring/evolver.js');
57
157
  process.stderr.write(tCli('cli.evolve.section_header', lang, { path: skillPath }));
58
158
  try {
59
159
  const result = await evolveSkill({
@@ -208,8 +308,8 @@ export default class Evolve extends BaseCommand {
208
308
  }),
209
309
  model: Flags.string({
210
310
  description: bilingual({
211
- zh: '被评测的 LLM,默认 sonnet',
212
- en: 'Evaluated LLM, default sonnet',
311
+ zh: '被评测的 LLM,默认 sonnet。无用例时也用作自动生成用例的出题模型。',
312
+ en: 'Evaluated LLM, default sonnet. Also used as the sample-generation model when no samples exist.',
213
313
  }),
214
314
  default: 'sonnet',
215
315
  }),
@@ -0,0 +1,25 @@
1
+ import { BaseCommand } from '../oclif/base-command.js';
2
+ import { probeSourceState } from '../lib/source-probe.js';
3
+ import { sanitizeCell } from '../lib/cell-format.js';
4
+ import { type ManagedListRow } from '../../managed/index.js';
5
+ import type { CliLang } from '../lib/i18n.js';
6
+ export { probeSourceState };
7
+ /** CJK 全角字符按 2 列计宽,使含中文表头的列也能对齐。 */
8
+ export declare function dispWidth(s: string): number;
9
+ /** 按**显示宽度**截断(不是 code unit):逐码点累加 dispWidth,绝不切断 surrogate 对、CJK 也不溢出列。 */
10
+ export declare function truncate(s: string, max: number): string;
11
+ export { sanitizeCell };
12
+ export declare function renderTable(rows: ManagedListRow[], lang: CliLang): string;
13
+ export default class List extends BaseCommand {
14
+ static description: string;
15
+ static examples: {
16
+ description: string;
17
+ command: string;
18
+ }[];
19
+ static flags: {
20
+ lang: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
21
+ global: import("@oclif/core/interfaces").BooleanFlag<boolean>;
22
+ json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
23
+ };
24
+ run(): Promise<void>;
25
+ }