oh-my-knowledge 0.36.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.
- package/README.md +5 -3
- package/README.zh.md +5 -3
- package/dist/assets/agent-skills/omk/SKILL.md +4 -2
- package/dist/assets/agent-skills/omk/references/commands.md +86 -3
- package/dist/cli/commands/doctor.js +19 -4
- package/dist/cli/commands/evolve.d.ts +11 -0
- package/dist/cli/commands/evolve.js +105 -5
- package/dist/cli/commands/list.d.ts +5 -26
- package/dist/cli/commands/list.js +17 -142
- package/dist/cli/commands/promote.d.ts +24 -0
- package/dist/cli/commands/promote.js +158 -0
- package/dist/cli/commands/rollback.d.ts +20 -0
- package/dist/cli/commands/rollback.js +95 -0
- package/dist/cli/commands/sample.d.ts +1 -0
- package/dist/cli/commands/sample.js +10 -4
- package/dist/cli/lib/cell-format.d.ts +17 -0
- package/dist/cli/lib/cell-format.js +19 -0
- package/dist/cli/lib/cmd-flags.d.ts +1 -0
- package/dist/cli/lib/i18n-dict/common.d.ts +1 -1
- package/dist/cli/lib/i18n-dict/common.js +8 -0
- package/dist/cli/lib/i18n-dict/list.d.ts +1 -1
- package/dist/cli/lib/i18n-dict/list.js +4 -0
- package/dist/cli/lib/i18n-dict/promote.d.ts +3 -0
- package/dist/cli/lib/i18n-dict/promote.js +54 -0
- package/dist/cli/lib/i18n-dict/rollback.d.ts +3 -0
- package/dist/cli/lib/i18n-dict/rollback.js +22 -0
- package/dist/cli/lib/i18n-dict.d.ts +3 -1
- package/dist/cli/lib/i18n-dict.js +4 -0
- package/dist/cli/lib/progress.d.ts +3 -0
- package/dist/cli/lib/progress.js +22 -0
- package/dist/cli/lib/source-probe.d.ts +15 -0
- package/dist/cli/lib/source-probe.js +129 -0
- package/dist/doctor/endpoint-rule.d.ts +60 -0
- package/dist/doctor/endpoint-rule.js +381 -0
- package/dist/doctor/health/load-custom-dimensions.d.ts +6 -0
- package/dist/doctor/health/load-custom-dimensions.js +43 -3
- package/dist/doctor/index.js +8 -1
- package/dist/executors/codex-cli.js +5 -1
- package/dist/managed/index.d.ts +1 -0
- package/dist/managed/index.js +1 -0
- package/dist/managed/list-view.d.ts +3 -0
- package/dist/managed/list-view.js +8 -4
- package/dist/managed/promote-gate.d.ts +40 -0
- package/dist/managed/promote-gate.js +37 -0
- package/dist/managed/store.d.ts +25 -2
- package/dist/managed/store.js +92 -3
- package/dist/renderer/doctor-detail-renderer.d.ts +9 -0
- package/dist/renderer/doctor-detail-renderer.js +114 -0
- package/dist/renderer/html-renderer.d.ts +3 -2
- package/dist/renderer/html-renderer.js +99 -99
- package/dist/renderer/icons.d.ts +29 -0
- package/dist/renderer/icons.js +66 -0
- package/dist/renderer/layout.js +81 -68
- package/dist/renderer/observation-inbox/styles.d.ts +1 -1
- package/dist/renderer/observation-inbox/styles.js +53 -53
- package/dist/renderer/report-shell.d.ts +77 -0
- package/dist/renderer/report-shell.js +223 -0
- package/dist/renderer/skill-detail-renderer.js +147 -352
- package/dist/renderer/skill-health-renderer.js +50 -73
- package/dist/renderer/skill-list-renderer.js +358 -306
- package/dist/renderer/summary.js +272 -162
- package/dist/renderer/test-view.d.ts +4 -3
- package/dist/renderer/test-view.js +386 -135
- package/dist/server/report-server.js +142 -28
- package/dist/types/doctor.d.ts +22 -0
- package/dist/types/managed.d.ts +17 -3
- 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`; `omk list` surfaces each managed skill's status (installed / measurable / stale)
|
|
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`;`omk list` 查看各受管 skill 的状态(installed / measurable / stale
|
|
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|list|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|list|observe|sample|studio> [op
|
|
|
19
19
|
npm i oh-my-knowledge -g
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
omk CLI 顶层命令包括:`init` / `install` / `list` / `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` / `list` / `doctor` / `eval` / `
|
|
|
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)
|
|
@@ -350,7 +350,7 @@ omk install --git-url https://github.com/org/repo.git --git-ref v1.0.0 skills/re
|
|
|
350
350
|
|
|
351
351
|
## omk list
|
|
352
352
|
|
|
353
|
-
列出受管 skill 及其证据状态:生命周期(installed / measurable / stale)、最新 verdict、证据数、源。
|
|
353
|
+
列出受管 skill 及其证据状态:生命周期(installed / measurable / promoted / stale)、最新 verdict、证据数、源。
|
|
354
354
|
|
|
355
355
|
**用法:**
|
|
356
356
|
|
|
@@ -479,6 +479,88 @@ omk observe show <inboxId> [flags]
|
|
|
479
479
|
- `--input-dir` `option`:inbox 数据目录
|
|
480
480
|
- `--lang` `option` (默认 `zh`):输出语言 zh|en,优先级 CLI > OMK_LANG env > zh。
|
|
481
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
|
+
|
|
482
564
|
## omk sample
|
|
483
565
|
|
|
484
566
|
为指定 skill 生成评测用例(eval-samples),支持 batch / single / fix / from-traces 四种模式。
|
|
@@ -497,6 +579,7 @@ omk sample [skillPath] [flags]
|
|
|
497
579
|
|
|
498
580
|
- `--batch` `boolean`:批量模式:扫 --skill-dir 下所有缺 samples 的 skill,逐个生成。
|
|
499
581
|
- `--count` `option`:生成用例条数。不传由 LLM 按 skill 类型自动决定。
|
|
582
|
+
- `--executor` `option`:执行器名,默认 claude(同 omk eval / doctor / evolve)。指定 codex 等其它执行器时,记得连带传一个该执行器能识别的 --model。
|
|
500
583
|
- `--fix` `boolean`:fix 模式:基于最近评测报告自动修复 sample_design 类型失败。
|
|
501
584
|
- `--focus` `option`:生成焦点(自然语言提示)。控制 LLM 偏向哪类用例。
|
|
502
585
|
- `--from-traces` `boolean`:from-traces 模式:从 observe inbox 的失败信号回流生成回归用例草稿(provenance: production-trace),落草稿待人工 review。
|
|
@@ -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
|
|
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) => !
|
|
210
|
-
: getRegisteredRules().filter(
|
|
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,
|
|
@@ -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
|
-
|
|
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
|
}),
|
|
@@ -1,35 +1,14 @@
|
|
|
1
1
|
import { BaseCommand } from '../oclif/base-command.js';
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
2
|
+
import { probeSourceState } from '../lib/source-probe.js';
|
|
3
|
+
import { sanitizeCell } from '../lib/cell-format.js';
|
|
4
|
+
import { type ManagedListRow } from '../../managed/index.js';
|
|
4
5
|
import type { CliLang } from '../lib/i18n.js';
|
|
5
|
-
|
|
6
|
-
* 探测一条受管记录**当前源**的状态(三态,喂 buildManagedListRow 判 drift / 生命周期)。
|
|
7
|
-
* - 远端 git(带 url):源身份钉不可变 SHA(install 时 pin),内容恒定 → 直接取 record.contentHash,
|
|
8
|
-
* 不联网(list 是快读命令,不为 drift 检查发网络请求)。reachable。
|
|
9
|
-
* - 本地 git:locator `git:<ref>:<spec>` 复用 resolveInstallSource 在**仓库对象库**内重物化重哈
|
|
10
|
-
* (读取受 git 边界约束,无任意文件读 DoS)。解析不到(常因 `omk list` 的 cwd 与 install 时不同、
|
|
11
|
-
* spec 随 cwd 漂)→ 抛错 → **reachable:false(未核,不当 stale)**,避免对未改动的 skill 误报漂移。
|
|
12
|
-
* - 本地 file:locator 是绝对路径,但受管 JSON **用户可手改 / 随仓库分发**(无 install、无 opt-in 即被
|
|
13
|
-
* loadAllManagedRecords 读到)。只读命令绝不盲读任意路径:**拒软链、只读常规文件 / 真目录、单文件
|
|
14
|
-
* 设 size cap**(挡 `evil.md → /dev/zero` 这类 readFileSync 无界 DoS 与项目外任意读)。守卫不过 →
|
|
15
|
-
* reachable:false。目录-skill 整树哈本就跳软链(hashArtifactSource 用 isFile() 过滤)。
|
|
16
|
-
*/
|
|
17
|
-
export declare function probeSourceState(record: ManagedArtifactRecord): SourceProbe;
|
|
6
|
+
export { probeSourceState };
|
|
18
7
|
/** CJK 全角字符按 2 列计宽,使含中文表头的列也能对齐。 */
|
|
19
8
|
export declare function dispWidth(s: string): number;
|
|
20
9
|
/** 按**显示宽度**截断(不是 code unit):逐码点累加 dispWidth,绝不切断 surrogate 对、CJK 也不溢出列。 */
|
|
21
10
|
export declare function truncate(s: string, max: number): string;
|
|
22
|
-
|
|
23
|
-
* 否则会破坏表格甚至伪造终端输出。一律映射到可见 U+FFFD(--json 路径保留原值给脚本)。
|
|
24
|
-
* 用 Unicode **属性类**而非手列码点 —— 手列清单天然有缺口(BiDi、U+2028 / 2029、Tags 块都曾漏一轮补一轮),
|
|
25
|
-
* 属性类一次覆盖整类、新码点自动纳入:
|
|
26
|
-
* - `\p{Cc}` 控制符(C0 / C1 / DEL,含 ESC / 换行 / 回车 / TAB)→ 杀 ANSI / OSC 转义与终端控制;
|
|
27
|
-
* - `\p{Cf}` 格式符(BiDi 重排 / 隔离、零宽、joiners、BOM、Tags、interlinear)→ 防 Trojan-Source 视觉伪造与零宽隐藏 / 分割;
|
|
28
|
-
* - `\p{Zl}` / `\p{Zp}` 行 / 段分隔(U+2028 / 2029)→ LF 的 Unicode 孪生,防换行伪造表格行;
|
|
29
|
-
* - `\p{Mn}` / `\p{Me}` 非间距 / 封闭组合附加符 → 变可见,避免零前进宽度令 dispWidth 与终端列错位
|
|
30
|
-
* (间距组合符 `\p{Mc}` 合法占 1 列,保留);
|
|
31
|
-
* - Hangul filler(U+115F / U+1160 / U+3164,属 Lo 不在上述任何类)→ 零宽显示诡计,补列。 */
|
|
32
|
-
export declare function sanitizeCell(s: string): string;
|
|
11
|
+
export { sanitizeCell };
|
|
33
12
|
export declare function renderTable(rows: ManagedListRow[], lang: CliLang): string;
|
|
34
13
|
export default class List extends BaseCommand {
|
|
35
14
|
static description: string;
|