oh-my-knowledge 0.36.0 → 0.38.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 (135) hide show
  1. package/README.md +6 -4
  2. package/README.zh.md +6 -4
  3. package/dist/assets/agent-skills/omk/SKILL.md +5 -3
  4. package/dist/assets/agent-skills/omk/references/commands.md +107 -15
  5. package/dist/authoring/evolver.d.ts +4 -1
  6. package/dist/authoring/evolver.js +13 -8
  7. package/dist/cli/commands/doctor.d.ts +1 -0
  8. package/dist/cli/commands/doctor.js +45 -10
  9. package/dist/cli/commands/eval/gold/compare.js +7 -4
  10. package/dist/cli/commands/eval/index.d.ts +1 -0
  11. package/dist/cli/commands/eval/index.js +14 -3
  12. package/dist/cli/commands/evolve.d.ts +12 -0
  13. package/dist/cli/commands/evolve.js +150 -5
  14. package/dist/cli/commands/init.js +18 -6
  15. package/dist/cli/commands/list.d.ts +3 -26
  16. package/dist/cli/commands/list.js +14 -144
  17. package/dist/cli/commands/observe/inbox.d.ts +1 -0
  18. package/dist/cli/commands/observe/inbox.js +14 -4
  19. package/dist/cli/commands/observe/index.d.ts +11 -0
  20. package/dist/cli/commands/observe/index.js +35 -12
  21. package/dist/cli/commands/observe/ingest.d.ts +1 -0
  22. package/dist/cli/commands/observe/ingest.js +13 -4
  23. package/dist/cli/commands/observe/show.d.ts +1 -0
  24. package/dist/cli/commands/observe/show.js +12 -2
  25. package/dist/cli/commands/promote.d.ts +24 -0
  26. package/dist/cli/commands/promote.js +157 -0
  27. package/dist/cli/commands/rollback.d.ts +20 -0
  28. package/dist/cli/commands/rollback.js +95 -0
  29. package/dist/cli/commands/sample.d.ts +1 -0
  30. package/dist/cli/commands/sample.js +25 -13
  31. package/dist/cli/commands/studio.d.ts +2 -0
  32. package/dist/cli/commands/studio.js +62 -12
  33. package/dist/cli/lib/cell-format.d.ts +17 -0
  34. package/dist/cli/lib/cell-format.js +19 -0
  35. package/dist/cli/lib/cmd-flags.d.ts +9 -0
  36. package/dist/cli/lib/i18n-dict/common.d.ts +1 -1
  37. package/dist/cli/lib/i18n-dict/common.js +9 -1
  38. package/dist/cli/lib/i18n-dict/evolve.d.ts +1 -1
  39. package/dist/cli/lib/i18n-dict/evolve.js +8 -0
  40. package/dist/cli/lib/i18n-dict/help.js +27 -45
  41. package/dist/cli/lib/i18n-dict/init.js +2 -2
  42. package/dist/cli/lib/i18n-dict/list.d.ts +1 -1
  43. package/dist/cli/lib/i18n-dict/list.js +4 -0
  44. package/dist/cli/lib/i18n-dict/promote.d.ts +3 -0
  45. package/dist/cli/lib/i18n-dict/promote.js +54 -0
  46. package/dist/cli/lib/i18n-dict/rollback.d.ts +3 -0
  47. package/dist/cli/lib/i18n-dict/rollback.js +22 -0
  48. package/dist/cli/lib/i18n-dict.d.ts +3 -1
  49. package/dist/cli/lib/i18n-dict.js +4 -0
  50. package/dist/cli/lib/parse-run-config.d.ts +0 -1
  51. package/dist/cli/lib/parse-run-config.js +6 -4
  52. package/dist/cli/lib/progress.d.ts +3 -0
  53. package/dist/cli/lib/progress.js +22 -0
  54. package/dist/cli/lib/record-evolve-outcome.d.ts +31 -0
  55. package/dist/cli/lib/record-evolve-outcome.js +110 -0
  56. package/dist/cli/lib/resolve-skill-input.d.ts +6 -0
  57. package/dist/cli/lib/resolve-skill-input.js +4 -2
  58. package/dist/cli/lib/source-probe.d.ts +15 -0
  59. package/dist/cli/lib/source-probe.js +129 -0
  60. package/dist/diagnosis/types.js +1 -1
  61. package/dist/doctor/endpoint-rule.d.ts +60 -0
  62. package/dist/doctor/endpoint-rule.js +381 -0
  63. package/dist/doctor/health/load-custom-dimensions.d.ts +6 -0
  64. package/dist/doctor/health/load-custom-dimensions.js +43 -3
  65. package/dist/doctor/index.js +12 -2
  66. package/dist/eval-core/artifact-index.d.ts +106 -0
  67. package/dist/eval-core/artifact-index.js +277 -0
  68. package/dist/eval-core/default-dirs.d.ts +34 -0
  69. package/dist/eval-core/default-dirs.js +36 -0
  70. package/dist/eval-core/evaluation-execution.js +3 -3
  71. package/dist/eval-core/evaluation-reporting.d.ts +7 -0
  72. package/dist/eval-core/evaluation-reporting.js +20 -5
  73. package/dist/eval-core/execution-strategy.js +3 -3
  74. package/dist/eval-core/measurement-dirs.d.ts +18 -0
  75. package/dist/eval-core/measurement-dirs.js +76 -0
  76. package/dist/eval-workflows/batch-evaluation-workflow.js +2 -1
  77. package/dist/eval-workflows/evaluation-pipeline/run-state.js +2 -1
  78. package/dist/eval-workflows/run-evaluation.js +5 -2
  79. package/dist/executors/codex-cli.js +7 -3
  80. package/dist/executors/script.js +43 -4
  81. package/dist/inputs/materialize-copy.d.ts +1 -1
  82. package/dist/inputs/materialize-copy.js +3 -3
  83. package/dist/inputs/skill-loader.d.ts +1 -1
  84. package/dist/inputs/skill-loader.js +1 -1
  85. package/dist/managed/index.d.ts +3 -0
  86. package/dist/managed/index.js +3 -0
  87. package/dist/managed/list-query.d.ts +7 -0
  88. package/dist/managed/list-query.js +11 -0
  89. package/dist/managed/list-view.d.ts +3 -0
  90. package/dist/managed/list-view.js +8 -4
  91. package/dist/managed/promote-gate.d.ts +40 -0
  92. package/dist/managed/promote-gate.js +37 -0
  93. package/dist/managed/source-probe.d.ts +15 -0
  94. package/dist/managed/source-probe.js +129 -0
  95. package/dist/managed/store.d.ts +35 -2
  96. package/dist/managed/store.js +117 -5
  97. package/dist/observability/inbox.js +6 -3
  98. package/dist/observability/skill-health-analyzer.d.ts +3 -0
  99. package/dist/observability/skill-health-analyzer.js +2 -0
  100. package/dist/renderer/doctor-detail-renderer.d.ts +9 -0
  101. package/dist/renderer/doctor-detail-renderer.js +114 -0
  102. package/dist/renderer/html-renderer.d.ts +3 -2
  103. package/dist/renderer/html-renderer.js +103 -101
  104. package/dist/renderer/icons.d.ts +29 -0
  105. package/dist/renderer/icons.js +66 -0
  106. package/dist/renderer/layout.js +81 -68
  107. package/dist/renderer/managed-history-renderer.d.ts +5 -0
  108. package/dist/renderer/managed-history-renderer.js +255 -0
  109. package/dist/renderer/observation-inbox/styles.d.ts +1 -1
  110. package/dist/renderer/observation-inbox/styles.js +53 -53
  111. package/dist/renderer/observation-inbox-renderer.js +19 -19
  112. package/dist/renderer/report-shell.d.ts +77 -0
  113. package/dist/renderer/report-shell.js +223 -0
  114. package/dist/renderer/skill-detail-renderer.js +164 -360
  115. package/dist/renderer/skill-health-renderer.js +50 -73
  116. package/dist/renderer/skill-list-renderer.js +359 -306
  117. package/dist/renderer/summary.js +272 -162
  118. package/dist/renderer/test-view.d.ts +4 -3
  119. package/dist/renderer/test-view.js +386 -135
  120. package/dist/server/indexed-report-store.d.ts +8 -0
  121. package/dist/server/indexed-report-store.js +130 -0
  122. package/dist/server/job-store.d.ts +0 -1
  123. package/dist/server/job-store.js +0 -2
  124. package/dist/server/report-server.d.ts +17 -3
  125. package/dist/server/report-server.js +357 -97
  126. package/dist/server/report-store.d.ts +14 -8
  127. package/dist/server/report-store.js +46 -0
  128. package/dist/server/skill-index.d.ts +7 -1
  129. package/dist/server/skill-index.js +48 -12
  130. package/dist/types/doctor.d.ts +22 -0
  131. package/dist/types/executor.d.ts +4 -2
  132. package/dist/types/managed.d.ts +17 -3
  133. package/dist/types/report.d.ts +15 -0
  134. package/dist/types/skill-index.d.ts +3 -0
  135. package/package.json +1 -1
package/README.md CHANGED
@@ -29,7 +29,7 @@ That's it — no editing required. `omk init` scaffolds two skill variants and t
29
29
 
30
30
  Walkthrough: [5-minute quickstart guide](docs/quickstart-skill-eval.md) (recommended for first-time users).
31
31
 
32
- Deeper: [CLI reference](docs/reference/cli.md) · [how it works](docs/explanation/architecture.md) · [eval sample format](docs/reference/eval-sample-format.md) · [executors](docs/reference/executors.md) · [artifact layout](docs/reference/artifact-layout.md)
32
+ Deeper: [who omk is for](docs/explanation/who-omk-is-for.md) · [CLI reference](docs/reference/cli.md) · [how it works](docs/explanation/architecture.md) · [eval sample format](docs/reference/eval-sample-format.md) · [executors](docs/reference/executors.md) · [artifact layout](docs/reference/artifact-layout.md)
33
33
 
34
34
  ## Use inside AI Coding Agents
35
35
 
@@ -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). [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
@@ -29,7 +29,7 @@ omk eval --control code-review-v1 --treatment code-review-v2
29
29
 
30
30
  手把手教程:[5 分钟快速上手](docs/zh/quickstart-skill-eval.md)(推荐第一次跑评测的用户)。
31
31
 
32
- 深入:[CLI 参考](docs/zh/reference/cli.md) · [工作原理](docs/zh/explanation/architecture.md) · [评测用例格式](docs/zh/reference/eval-sample-format.md) · [执行器](docs/zh/reference/executors.md) · [artifact 布局](docs/zh/reference/artifact-layout.md)
32
+ 深入:[为谁、解决什么](docs/zh/explanation/who-omk-is-for.md) · [CLI 参考](docs/zh/reference/cli.md) · [工作原理](docs/zh/explanation/architecture.md) · [评测用例格式](docs/zh/reference/eval-sample-format.md) · [执行器](docs/zh/reference/executors.md) · [artifact 布局](docs/zh/reference/artifact-layout.md)
33
33
 
34
34
  ## 在 AI Coding Agent 中使用
35
35
 
@@ -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)。[规范 →](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|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
 
@@ -140,7 +142,7 @@ omk studio --host 0.0.0.0 # 局域网访问(默认 127.0.0.1
140
142
  omk studio --no-open # 不自动开浏览器
141
143
  ```
142
144
 
143
- Studio 是 skill-centric:列表页(`/`)按 skill 卡片展示健康等级 / 0-100 参考分 / 待优化数 / 趋势;详情页(`/skills/<name>`)左栏列关键问题清单,右栏画健康趋势 + 三档阶段卡(doctor / eval / observe)。访问 `/observations/inbox` 查看 observe inbox 看板。
145
+ Studio 是 skill-centric:列表页(`/`)按 skill 卡片展示健康等级 / 0-100 参考分 / 待优化数 / 趋势;详情页(`/skills/<name>`)左栏列关键问题清单,右栏画健康趋势 + 三档阶段卡(doctor / eval / observe)。访问 `/observe-inbox` 查看 observe inbox 看板。
144
146
 
145
147
  ## 第五步:解读结果
146
148
 
@@ -20,15 +20,16 @@ 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。
27
27
  - `--gate` `boolean`:静默模式,只在 fail 时输出 stderr 摘要,exit code 标识结果。
28
+ - `--global` `boolean`:写全局 ~/.oh-my-knowledge/doctors,而非项目 .omk/doctors
28
29
  - `--json` `boolean`:JSON 输出到 stdout,适合 CI / 外部脚本消费。
29
30
  - `--lang` `option` (默认 `zh`):输出语言 zh|en,优先级 CLI > OMK_LANG env > zh。
30
31
  - `--model` `option`:LLM model 名,默认 sonnet。
31
- - `--output-dir` `option`:报告输出目录,默认 ~/.oh-my-knowledge/doctors
32
+ - `--output-dir` `option`:报告输出目录,默认项目级 .omk/doctors(--global 写全局)。
32
33
  - `--samples` `option`:用例文件路径(.json/.yaml)。不传则按 target / cwd 顺序自动发现。
33
34
  - `--static-only` `boolean`:离线静态模式,只跑 4 条静态 rule(skill_readable / skill_metadata / dependencies_present / samples_contract_aligned),不调 LLM。
34
35
  - `--timeout` `option`:单次 LLM 会话超时秒数,默认 600(10 分钟)。
@@ -79,6 +80,7 @@ omk eval [flags]
79
80
  - `--dry-run` `boolean`:只 plan 不实跑
80
81
  - `--effort` `option`:被测 LLM 扩展思考预算 low/medium/high/xhigh/max(默认 low;跨 effort 报告不严格可比)。
81
82
  - `--executor` `option`:执行器:claude / claude-sdk / codex / codex-sdk / openai-api / gemini / 自定义命令(默认 claude)。
83
+ - `--global` `boolean`:报告写全局 ~/.oh-my-knowledge/reports,而非项目 .omk/
82
84
  - `--gold-dir` `option`:gold dataset 目录
83
85
  - `--judge-models` `option`:评委配置,格式 executor:model[,...],例 claude:haiku 或 claude:opus,openai:gpt-4o(≥ 2 个 = ensemble)。默认 <executor>:haiku。
84
86
  - `--judge-repeat` `option`:每个 dim 评 N 次
@@ -94,7 +96,7 @@ omk eval [flags]
94
96
  - `--no-judge` `boolean`:跳过 LLM judge
95
97
  - `--no-serve` `boolean`:不启 report server
96
98
  - `--no-strict-baseline` `boolean`:关闭 baseline 隔离
97
- - `--output-dir` `option`:报告输出目录
99
+ - `--output-dir` `option`:报告输出目录(默认项目级 .omk/reports)
98
100
  - `--repeat` `option`:每个 sample 重复跑 N 次
99
101
  - `--report-only` `boolean`:生成报告并打印 verdict,但始终 exit 0(不参与 CI gate)。
100
102
  - `--resume` `option`:从某次失败 run 续跑
@@ -222,7 +224,7 @@ omk evolve <skillPath> [flags]
222
224
  - `--improve-model` `option` (默认 `sonnet`):负责重写 skill 的 LLM,默认 sonnet
223
225
  - `--judge-models` `option` (默认 `claude:haiku`):评委 model(单评委约束),格式 executor:model。默认 claude:haiku
224
226
  - `--lang` `option` (默认 `zh`):输出语言 zh|en,优先级 CLI > OMK_LANG env > zh。
225
- - `--model` `option` (默认 `sonnet`):被评测的 LLM,默认 sonnet
227
+ - `--model` `option` (默认 `sonnet`):被评测的 LLM,默认 sonnet。无用例时也用作自动生成用例的出题模型。
226
228
  - `--no-diagnostic` `boolean`:关 LLM diagnostic 调用
227
229
  - `--no-edit-budget` `boolean`:关掉 edit budget 约束(允许任意大小的单轮改动)
228
230
  - `--no-reject-memory` `boolean`:关掉 rejected-edit 记忆(不把被拒改法回灌下一轮 prompt)
@@ -234,6 +236,7 @@ omk evolve <skillPath> [flags]
234
236
  - `--significance-alpha` `option` (默认 `0.05`):显著性门的 diff CI 显著性水平(默认 0.05 = 95% CI)
235
237
  - `--skip-connectivity` `boolean`:跳过 LLM 连通性预检
236
238
  - `--skip-doctor` `boolean`:跳过 doctor 门禁(escape hatch,自负 garbage-in 风险)
239
+ - `--snapshot-only` `boolean`:只产候选、不写回 source:胜出版本留在 evolve/<skillName>.r{N}.md 供你挑选,再 omk promote 接受。受管 skill 默认会写回 source 并记证据(measurable)。
237
240
  - `--stop-on-assertions-pass` `boolean`:普通用例断言全过时提前停止
238
241
  - `--target` `option`:目标 composite 分数,达到即停。不传则跑满 rounds
239
242
  - `--test-ratio` `option` (默认 `0`):锁定 test 集比例(0..1,默认 0=关),需配 --holdout-ratio。全程不参与选择,收尾读一次给无偏泛化分
@@ -255,7 +258,7 @@ omk evolve skills/my-skill/SKILL.md --target 4.5 --model opus --improve-model op
255
258
 
256
259
  ## omk init
257
260
 
258
- 初始化 omk 项目脚手架(skills/ + eval-samples.json 模板)。
261
+ 初始化一个 omk 项目:在目标目录铺好待测知识载体(skills/)与评测用例(eval-samples.json),供 omk eval / doctor / evolve / observe / list 操作。默认是两版 code-review skill 的 A/B 起步模板。
259
262
 
260
263
  **用法:**
261
264
 
@@ -273,13 +276,13 @@ omk init [targetDir] [flags]
273
276
 
274
277
  **示例:**
275
278
 
276
- > 在当前目录初始化
279
+ > 在当前目录初始化一个 omk 项目
277
280
 
278
281
  ```bash
279
282
  omk init
280
283
  ```
281
284
 
282
- > 在指定目录初始化
285
+ > 在指定目录初始化一个 omk 项目
283
286
 
284
287
  ```bash
285
288
  omk init my-project
@@ -350,7 +353,7 @@ omk install --git-url https://github.com/org/repo.git --git-ref v1.0.0 skills/re
350
353
 
351
354
  ## omk list
352
355
 
353
- 列出受管 skill 及其证据状态:生命周期(installed / measurable / stale)、最新 verdict、证据数、源。
356
+ 列出受管 skill 及其证据状态:生命周期(installed / measurable / promoted / stale)、最新 verdict、证据数、源。
354
357
 
355
358
  **用法:**
356
359
 
@@ -401,10 +404,11 @@ omk observe [sessionsDir] [flags]
401
404
  **Flags:**
402
405
 
403
406
  - `--from` `option`:起始时间 ISO,优先级高于 --last
407
+ - `--global` `boolean`:写全局 ~/.oh-my-knowledge/observe-health,而非项目 .omk/observe-health
404
408
  - `--kb` `option`:知识库 root,启用 KB-aware 分析
405
409
  - `--lang` `option` (默认 `zh`):输出语言 zh|en,优先级 CLI > OMK_LANG env > zh。
406
410
  - `--last` `option`:时间窗(7d / 24h / 30m)
407
- - `--output-dir` `option`:分析结果输出目录
411
+ - `--output-dir` `option`:健康报告输出目录,默认项目级 .omk/observe-health(--global 写全局)
408
412
  - `--skills` `option`:只看指定 skill,逗号分隔
409
413
  - `--to` `option`:结束时间 ISO
410
414
 
@@ -431,8 +435,9 @@ omk observe inbox [flags]
431
435
  - `--by-skill` `boolean`:按 skill 聚合输出
432
436
  - `--executor` `option`:LLM 增强复盘使用的执行器
433
437
  - `--explore` `option`:抽样 N 条 medium/low 长尾(replaces limit)
438
+ - `--global` `boolean`:直接读取全局 ~/.oh-my-knowledge/observe-inbox(跳过项目级与兜底)。
434
439
  - `--include-noise` `boolean`:explore 时也包含 noise 桶
435
- - `--input-dir` `option`:inbox 数据目录,默认 .omk/observations(项目级,相对于 cwd);目录不存在时兜底读 ~/.oh-my-knowledge/observations
440
+ - `--input-dir` `option`:inbox 数据目录,默认 .omk/observe-inbox(项目级,相对于 cwd);目录不存在时兜底读 ~/.oh-my-knowledge/observe-inbox
436
441
  - `--json` `boolean`:JSON 格式输出
437
442
  - `--lang` `option` (默认 `zh`):输出语言 zh|en,优先级 CLI > OMK_LANG env > zh。
438
443
  - `--limit` `option`:限制条数,默认 20
@@ -457,8 +462,9 @@ omk observe ingest <traceDir> [flags]
457
462
 
458
463
  **Flags:**
459
464
 
465
+ - `--global` `boolean`:写入全局 ~/.oh-my-knowledge/observe-inbox,而非项目 .omk/observe-inbox。
460
466
  - `--lang` `option` (默认 `zh`):输出语言 zh|en,优先级 CLI > OMK_LANG env > zh。
461
- - `--output-dir` `option`:输出目录,默认 .omk/observations(项目级,相对于 cwd)。
467
+ - `--output-dir` `option`:输出目录,默认 .omk/observe-inbox(项目级,相对于 cwd;--global 写全局)。
462
468
 
463
469
  ## omk observe show
464
470
 
@@ -476,9 +482,92 @@ omk observe show <inboxId> [flags]
476
482
 
477
483
  **Flags:**
478
484
 
485
+ - `--global` `boolean`:直接读取全局 ~/.oh-my-knowledge/observe-inbox(跳过项目级与兜底)。
479
486
  - `--input-dir` `option`:inbox 数据目录
480
487
  - `--lang` `option` (默认 `zh`):输出语言 zh|en,优先级 CLI > OMK_LANG env > zh。
481
488
 
489
+ ## omk promote
490
+
491
+ 把受管 skill 的当前版本按证据门禁「接受」为 promoted:默认仅放行 verdict=PROGRESS,在记录里追加一条带证据指针的人工决定。
492
+
493
+ **用法:**
494
+
495
+ ```bash
496
+ omk promote <name> [flags]
497
+ ```
498
+
499
+ **参数:**
500
+
501
+ - `name`(必填):受管 skill 名(omk list 里的 NAME)
502
+
503
+ **Flags:**
504
+
505
+ - `--accept-cautious` `boolean`:把 CAUTIOUS 也算可接受(默认仅 PROGRESS)
506
+ - `--actor` `option`:决定的 actor(默认取 git config user.name)
507
+ - `--force` `boolean`:越过可越门拦截强制 promote,记为人工 override 决定(无当前证据或源 hash 已变时仍拒)
508
+ - `--global` `boolean`:操作全局受管目录而非项目 .omk/managed
509
+ - `--json` `boolean`:输出 JSON(版本化信封)供脚本消费
510
+ - `--kind` `option` (默认 `skill`):artifact 类型(当前仅 skill)
511
+ - `--lang` `option` (默认 `zh`):输出语言 zh|en,优先级 CLI > OMK_LANG env > zh。
512
+ - `--reason` `option`:promote / 越门的理由(写入决定)
513
+
514
+ **示例:**
515
+
516
+ > promote 一个证据达标的 skill
517
+
518
+ ```bash
519
+ omk promote review
520
+ ```
521
+
522
+ > 接受 CAUTIOUS 结果(显式放宽门禁)
523
+
524
+ ```bash
525
+ omk promote review --accept-cautious
526
+ ```
527
+
528
+ > 越门 promote 并记录理由(人工 override)
529
+
530
+ ```bash
531
+ omk promote review --force --reason "已人工复核"
532
+ ```
533
+
534
+ ## omk rollback
535
+
536
+ 回退受管 skill 当前版本的 promoted 接受:撤销最近一次 promote,在记录里追加一条 rollback 决定(源未漂移则状态回到 measurable,源已漂移则仍 stale)。
537
+
538
+ **用法:**
539
+
540
+ ```bash
541
+ omk rollback <name> [flags]
542
+ ```
543
+
544
+ **参数:**
545
+
546
+ - `name`(必填):受管 skill 名(omk list 里的 NAME)
547
+
548
+ **Flags:**
549
+
550
+ - `--actor` `option`:决定的 actor(默认取 git config user.name)
551
+ - `--global` `boolean`:操作全局受管目录而非项目 .omk/managed
552
+ - `--json` `boolean`:输出 JSON(版本化信封)供脚本消费
553
+ - `--kind` `option` (默认 `skill`):artifact 类型(当前仅 skill)
554
+ - `--lang` `option` (默认 `zh`):输出语言 zh|en,优先级 CLI > OMK_LANG env > zh。
555
+ - `--reason` `option`:回退的理由(写入决定)
556
+
557
+ **示例:**
558
+
559
+ > 回退一个已 promoted 的 skill
560
+
561
+ ```bash
562
+ omk rollback review
563
+ ```
564
+
565
+ > 回退并记录理由
566
+
567
+ ```bash
568
+ omk rollback review --reason "线上发现回归"
569
+ ```
570
+
482
571
  ## omk sample
483
572
 
484
573
  为指定 skill 生成评测用例(eval-samples),支持 batch / single / fix / from-traces 四种模式。
@@ -497,13 +586,14 @@ omk sample [skillPath] [flags]
497
586
 
498
587
  - `--batch` `boolean`:批量模式:扫 --skill-dir 下所有缺 samples 的 skill,逐个生成。
499
588
  - `--count` `option`:生成用例条数。不传由 LLM 按 skill 类型自动决定。
589
+ - `--executor` `option`:执行器名,默认 claude(同 omk eval / doctor / evolve)。指定 codex 等其它执行器时,记得连带传一个该执行器能识别的 --model。
500
590
  - `--fix` `boolean`:fix 模式:基于最近评测报告自动修复 sample_design 类型失败。
501
591
  - `--focus` `option`:生成焦点(自然语言提示)。控制 LLM 偏向哪类用例。
502
592
  - `--from-traces` `boolean`:from-traces 模式:从 observe inbox 的失败信号回流生成回归用例草稿(provenance: production-trace),落草稿待人工 review。
503
593
  - `--lang` `option` (默认 `zh`):输出语言 zh|en,优先级 CLI > OMK_LANG env > zh。
504
594
  - `--model` `option` (默认 `sonnet`):生成 LLM model 名,默认 sonnet。
505
595
  - `--no-mock` `boolean`:不生成 mocks,eval 时所有工具调用真实执行。
506
- - `--observations-dir` `option`:observe inbox 目录(from-traces 模式用),默认项目 .omk/observations
596
+ - `--observations-dir` `option`:observe inbox 目录(from-traces 模式用),默认项目 .omk/observe-inbox
507
597
  - `--reports-dir` `option`:报告目录(fix 模式用),默认 ~/.oh-my-knowledge/reports。
508
598
  - `--skill-dir` `option` (默认 `skills`):skill 根目录,默认 skills。batch 模式扫此目录。
509
599
  - `--treatment` `option`:指定 treatment 名(fix 模式用),默认推断自 skill 路径。
@@ -546,14 +636,16 @@ omk studio [flags]
546
636
 
547
637
  **Flags:**
548
638
 
549
- - `--analyses-dir` `option`:分析数据目录(可选)
639
+ - `--analyses-dir` `option`:观测健康报告目录(可选,默认项目级 .omk/observe-health,空则全局兜底)
550
640
  - `--dev` `boolean`:dev 模式:子进程启动 + 热更新
641
+ - `--doctors-dir` `option`:体检报告目录(可选,默认项目级 .omk/doctors,空则全局兜底)
642
+ - `--global` `boolean`:只看全局 reports / observe-health / doctors / observe-inbox 目录(~/.oh-my-knowledge/*),而非机器级聚合 / 项目优先;managed 不受影响
551
643
  - `--host` `option`:监听 host,默认 localhost。改为 0.0.0.0 暴露给局域网
552
644
  - `--lang` `option` (默认 `zh`):输出语言 zh|en,优先级 CLI > OMK_LANG env > zh。
553
645
  - `--no-open` `boolean`:不自动打开浏览器
554
- - `--observations-dir` `option`:观测数据目录(可选)
646
+ - `--observations-dir` `option`:观测收件箱数据目录(可选,默认 .omk/observe-inbox)
555
647
  - `--port` `option` (默认 `7799`):监听端口,默认 7799。传 0 让 OS 分配
556
- - `--reports-dir` `option`:报告目录,默认 ~/.oh-my-knowledge/reports
648
+ - `--reports-dir` `option`:只看指定报告目录(可选;默认机器级聚合:当前项目 + 全局 + 别项目索引)
557
649
 
558
650
  **示例:**
559
651
 
@@ -182,6 +182,9 @@ interface EvolveOptions {
182
182
  /** Feed rejected candidate edits back into the next round's improvement prompt
183
183
  * ("these were tried and did not help — don't repeat them"). Default true. */
184
184
  rejectMemory?: boolean;
185
+ /** 是否把胜出版本写回原 source 文件。默认 true(保「一键化」:evolve 跑完源即更新)。
186
+ * `--snapshot-only` 置 false → 不写 source,候选仍落在 `evolve/<skillName>.r{N}.md` 供人工挑选 / promote。 */
187
+ writeBackToSource?: boolean;
185
188
  onProgress?: ProgressCallback | null;
186
189
  onRoundProgress?: ((progress: EvolveRoundProgressInfo) => void) | null;
187
190
  }
@@ -263,5 +266,5 @@ export interface RoundReport {
263
266
  report: Report;
264
267
  }
265
268
  export declare function mergeEvolveReports(roundReports: RoundReport[], skillName: string, totalCostUSD: number, samples?: Sample[], skillPath?: string): Report;
266
- export declare function evolveSkill({ skillPath, samplesPath, rounds, target, stopOnAssertionsPass, autoFixSamples, sampleFixMaxAttempts, reuseLatestEval, model, judgeModels, improveModel, improveMode, executorName, concurrency, timeoutMs, skipConnectivity, effort, noDiagnostic, skipDoctor, holdoutRatio, significanceGate, significanceAlpha, testRatio, editBudget, rejectMemory, onProgress, onRoundProgress, }: EvolveOptions): Promise<EvolveResult>;
269
+ export declare function evolveSkill({ skillPath, samplesPath, rounds, target, stopOnAssertionsPass, autoFixSamples, sampleFixMaxAttempts, reuseLatestEval, model, judgeModels, improveModel, improveMode, executorName, concurrency, timeoutMs, skipConnectivity, effort, noDiagnostic, skipDoctor, holdoutRatio, significanceGate, significanceAlpha, testRatio, editBudget, rejectMemory, writeBackToSource, onProgress, onRoundProgress, }: EvolveOptions): Promise<EvolveResult>;
267
270
  export {};
@@ -2,8 +2,9 @@ import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
2
2
  import { resolve, join, dirname, basename } from 'node:path';
3
3
  import { runEvaluation } from '../eval-workflows/run-evaluation.js';
4
4
  import { createExecutor, DEFAULT_MODEL, JUDGE_MODEL } from '../executors/index.js';
5
- import { persistReport, DEFAULT_OUTPUT_DIR, generateRunId, hashString } from '../eval-core/evaluation-reporting.js';
6
- import { createFileStore } from '../server/report-store.js';
5
+ import { persistReport, DEFAULT_OUTPUT_DIR, runIdSuffix, hashString } from '../eval-core/evaluation-reporting.js';
6
+ import { createOverlayReportStore } from '../server/report-store.js';
7
+ import { projectReportsDir, globalReportsDir } from '../eval-core/measurement-dirs.js';
7
8
  import { analyzeResults } from '../analysis/report-diagnostics.js';
8
9
  import { loadSamples } from '../inputs/load-samples.js';
9
10
  import { hashArtifactSource } from '../inputs/content-hash.js';
@@ -87,7 +88,9 @@ function singleVariantReport(report, variantKey) {
87
88
  };
88
89
  }
89
90
  async function findReusableBaselineReport(opts) {
90
- const store = createFileStore(DEFAULT_OUTPUT_DIR);
91
+ // baseline 复用读 overlay(项目 .omk/reports ∪ 全局):eval 写默认翻项目后,复用既能命中 eval 新写的项目
92
+ // baseline,又继续覆盖全局(含 evolve 自身写到全局的合并报告),复用命中率与报告数字不降。
93
+ const store = createOverlayReportStore(projectReportsDir(), globalReportsDir());
91
94
  const { samples } = loadSamples(opts.samplesPath);
92
95
  const artifactHash = opts.artifactHash;
93
96
  const reports = await store.findByArtifactHash(artifactHash);
@@ -544,7 +547,7 @@ export function mergeEvolveReports(roundReports, skillName, totalCostUSD, sample
544
547
  if (originalKey)
545
548
  artifactHashes[variantLabels[i]] = hashes[originalKey];
546
549
  }
547
- const runId = `evolve-${skillName}-${generateRunId([skillName]).split('-').slice(-2).join('-')}`;
550
+ const runId = `evolve-${skillName}-${runIdSuffix()}`;
548
551
  const report = {
549
552
  kind: 'evaluation',
550
553
  id: runId,
@@ -570,7 +573,7 @@ export function mergeEvolveReports(roundReports, skillName, totalCostUSD, sample
570
573
  report.analysis = analyzeResults(report, { samples });
571
574
  return report;
572
575
  }
573
- export async function evolveSkill({ skillPath, samplesPath, rounds = 5, target = null, stopOnAssertionsPass = false, autoFixSamples = false, sampleFixMaxAttempts = 2, reuseLatestEval = false, model = DEFAULT_MODEL, judgeModels, improveModel = DEFAULT_MODEL, improveMode = 'agent', executorName = 'claude', concurrency = 1, timeoutMs, skipConnectivity = false, effort, noDiagnostic, skipDoctor, holdoutRatio = 0, significanceGate = true, significanceAlpha = DEFAULT_BOOTSTRAP_ALPHA, testRatio = 0, editBudget = 0.2, rejectMemory = true, onProgress = null, onRoundProgress = null, }) {
576
+ export async function evolveSkill({ skillPath, samplesPath, rounds = 5, target = null, stopOnAssertionsPass = false, autoFixSamples = false, sampleFixMaxAttempts = 2, reuseLatestEval = false, model = DEFAULT_MODEL, judgeModels, improveModel = DEFAULT_MODEL, improveMode = 'agent', executorName = 'claude', concurrency = 1, timeoutMs, skipConnectivity = false, effort, noDiagnostic, skipDoctor, holdoutRatio = 0, significanceGate = true, significanceAlpha = DEFAULT_BOOTSTRAP_ALPHA, testRatio = 0, editBudget = 0.2, rejectMemory = true, writeBackToSource = true, onProgress = null, onRoundProgress = null, }) {
574
577
  if (judgeModels && judgeModels.length > 1) {
575
578
  throw new Error('evolveSkill does not support multi-judge ensemble (received '
576
579
  + `${judgeModels.length} judges). Pass a single-judge array, e.g. `
@@ -712,7 +715,8 @@ export async function evolveSkill({ skillPath, samplesPath, rounds = 5, target =
712
715
  onRoundProgress({ round: 0, totalRounds: rounds, phase: 'baseline', score: bestScore, costUSD: baselineCost, costReported: baselineCostReported, reused: baselineReused });
713
716
  if (stopOnAssertionsPass && allNonTripwireAssertionsPass(baselineReport, baselineVariantKey)) {
714
717
  stopReason = 'assertions-pass';
715
- writeFileSync(absSkillPath, currentBest);
718
+ if (writeBackToSource)
719
+ writeFileSync(absSkillPath, currentBest);
716
720
  const { samples } = loadSamples(absSamplesPath);
717
721
  const mergedReport = mergeEvolveReports(roundReports, skillName, totalCostUSD, samples, absSkillPath);
718
722
  persistReport(mergedReport, DEFAULT_OUTPUT_DIR);
@@ -908,8 +912,9 @@ export async function evolveSkill({ skillPath, samplesPath, rounds = 5, target =
908
912
  break;
909
913
  }
910
914
  }
911
- // Write best version back to original file only if an improvement was accepted
912
- if (bestRound > 0) {
915
+ // Write best version back to original file only if an improvement was accepted.
916
+ // `--snapshot-only`(writeBackToSource=false)跳过写回:候选仍在 evolve/<skillName>.r{N}.md,源不动。
917
+ if (bestRound > 0 && writeBackToSource) {
913
918
  writeFileSync(absSkillPath, currentBest);
914
919
  }
915
920
  // Merge all round reports into one and persist
@@ -17,6 +17,7 @@ export default class Doctor extends BaseCommand {
17
17
  samples: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
18
18
  timeout: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
19
19
  'output-dir': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
20
+ global: import("@oclif/core/interfaces").BooleanFlag<boolean>;
20
21
  dimensions: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
21
22
  'static-only': import("@oclif/core/interfaces").BooleanFlag<boolean>;
22
23
  fix: import("@oclif/core/interfaces").BooleanFlag<boolean>;
@@ -1,4 +1,3 @@
1
- import { homedir } from 'node:os';
2
1
  import { existsSync, statSync, mkdirSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from 'node:fs';
3
2
  import { dirname, join, resolve } from 'node:path';
4
3
  import { Args, Flags } from '@oclif/core';
@@ -7,6 +6,10 @@ import { BaseCommand } from '../oclif/base-command.js';
7
6
  import { numberStringParser } from '../oclif/parsers.js';
8
7
  import { CliExit } from '../lib/cli-exit.js';
9
8
  import { tCli } from '../lib/i18n.js';
9
+ import { makeDoctorProgress } from '../lib/progress.js';
10
+ import { DEFAULT_DOCTORS_DIR } from '../../eval-core/default-dirs.js';
11
+ import { indexDoctorWrite, removeDoctorCard } from '../../eval-core/artifact-index.js';
12
+ import { projectDoctorsDir, globalDoctorsDir } from '../../eval-core/measurement-dirs.js';
10
13
  const DEFAULT_SAMPLE_FILENAMES = ['eval-samples.json', 'eval-samples.yaml', 'eval-samples.yml'];
11
14
  function findSamplesInDir(dir) {
12
15
  for (const name of DEFAULT_SAMPLE_FILENAMES) {
@@ -130,14 +133,20 @@ export default class Doctor extends BaseCommand {
130
133
  }),
131
134
  'output-dir': Flags.string({
132
135
  description: bilingual({
133
- zh: '报告输出目录,默认 ~/.oh-my-knowledge/doctors',
134
- en: 'Report output dir, default ~/.oh-my-knowledge/doctors.',
136
+ zh: '报告输出目录,默认项目级 .omk/doctors(--global 写全局)。',
137
+ en: 'Report output dir, default project-level .omk/doctors (--global for global).',
138
+ }),
139
+ }),
140
+ global: Flags.boolean({
141
+ description: bilingual({
142
+ zh: '写全局 ~/.oh-my-knowledge/doctors,而非项目 .omk/doctors',
143
+ en: 'Write to global ~/.oh-my-knowledge/doctors instead of project .omk/doctors',
135
144
  }),
136
145
  }),
137
146
  dimensions: Flags.string({
138
147
  description: bilingual({
139
- zh: '自定义维度配置文件(YAML),追加到内置 7 维度之后。',
140
- en: 'Custom dimensions config file (YAML), appended after builtin 7 dimensions.',
148
+ zh: '自定义维度配置文件(YAML),追加到内置 7 维度之后。每条维度二选一:promptSection(走 LLM 体检)或 endpoint(POST skill 快照给接口判定)。注意:endpoint 会把 SKILL.md 全文 + 子文件发到该地址,仅对可信配置/可信地址启用。',
149
+ 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
150
  }),
142
151
  }),
143
152
  'static-only': Flags.boolean({
@@ -205,9 +214,15 @@ export default class Doctor extends BaseCommand {
205
214
  const { renderDoctorReportText, renderDoctorReportJson } = await import('../../doctor/renderer.js');
206
215
  const { getRegisteredRules } = await import('../../doctor/rules.js');
207
216
  const { isComposerRule } = await import('../../types/doctor.js');
217
+ // 在线检查(LLM health composer + endpoint 自定义维度)默认跑;--static-only
218
+ // 离线模式只跑纯静态的内置 rule(无网络 / LLM)。endpoint rule 标了 external=true。
219
+ const isOnline = (r) => isComposerRule(r) || r.external === true;
208
220
  const rulesOverride = staticOnly
209
- ? getRegisteredRules().filter((r) => !isComposerRule(r))
210
- : getRegisteredRules().filter(isComposerRule);
221
+ ? getRegisteredRules().filter((r) => !isOnline(r))
222
+ : getRegisteredRules().filter(isOnline);
223
+ // 批量体检进度(per-skill,写 stderr)。--gate 是静默模式,不报进度;
224
+ // --json 进度走 stderr 不污染 stdout 的 JSON。
225
+ const onProgress = flags.gate ? undefined : makeDoctorProgress(lang);
211
226
  let report;
212
227
  try {
213
228
  report = await runDoctor({
@@ -221,6 +236,7 @@ export default class Doctor extends BaseCommand {
221
236
  rules: rulesOverride,
222
237
  samples,
223
238
  requires,
239
+ onProgress,
224
240
  });
225
241
  }
226
242
  catch (err) {
@@ -252,7 +268,9 @@ export default class Doctor extends BaseCommand {
252
268
  }
253
269
  renderDoctorReportText(report, lang);
254
270
  }
255
- persistDoctorReport(report, flags['output-dir'] ? resolve(flags['output-dir']) : undefined);
271
+ persistDoctorReport(report, flags['output-dir']
272
+ ? resolve(flags['output-dir'])
273
+ : (flags.global ? globalDoctorsDir() : projectDoctorsDir()));
256
274
  if (flags.fix) {
257
275
  const existing = report;
258
276
  if (existing.outcome !== 'failed') {
@@ -271,13 +289,20 @@ export default class Doctor extends BaseCommand {
271
289
  // scanDoctorReports 扫盘成本)。50 = ~每天 1 跑撑 1.5 个月 sparkline,够用。
272
290
  const DOCTOR_HISTORY_MAX_PER_SKILL = 50;
273
291
  function persistDoctorReport(report, outputDir) {
274
- const dir = outputDir ?? join(homedir(), '.oh-my-knowledge', 'doctors');
292
+ const dir = outputDir ?? DEFAULT_DOCTORS_DIR;
275
293
  mkdirSync(dir, { recursive: true });
276
294
  const safeId = report.id.replace(/[/\\:*?"<>|]/g, '_');
277
295
  for (const skill of report.skills) {
296
+ const counts = { pass: 0, warn: 0, fail: 0, skipped: 0 };
297
+ for (const r of skill.results) {
298
+ const s = r.status;
299
+ if (s in counts)
300
+ counts[s]++;
301
+ }
278
302
  const perSkill = {
279
303
  ...report,
280
304
  skills: [skill],
305
+ ruleStats: { ...counts, total: skill.results.length },
281
306
  totals: {
282
307
  pass: skill.status === 'pass' ? 1 : 0,
283
308
  warn: skill.status === 'warn' ? 1 : 0,
@@ -286,7 +311,14 @@ function persistDoctorReport(report, outputDir) {
286
311
  outcome: skill.status === 'fail' ? 'failed' : skill.status === 'warn' ? 'warnings_only' : 'passed',
287
312
  };
288
313
  const safeName = skill.skillName.replace(/[/\\:*?"<>|]/g, '_');
289
- writeFileSync(join(dir, `${safeName}-${safeId}.json`), JSON.stringify(perSkill, null, 2), 'utf8');
314
+ const cardId = `${safeName}-${safeId}`;
315
+ const filePath = join(dir, `${cardId}.json`);
316
+ writeFileSync(filePath, JSON.stringify(perSkill, null, 2), 'utf8');
317
+ // 产物发现索引:per-skill 报告落项目本地后,best-effort 追加全局轻卡片,让 studio 跨项目聚合。
318
+ indexDoctorWrite({
319
+ id: cardId, path: filePath, skillName: skill.skillName, reportId: report.id, timestamp: report.timestamp,
320
+ status: skill.status, passCount: counts.pass, warnCount: counts.warn, failCount: counts.fail,
321
+ }, dir);
290
322
  pruneDoctorHistory(dir, skill.skillName, DOCTOR_HISTORY_MAX_PER_SKILL);
291
323
  }
292
324
  }
@@ -317,5 +349,8 @@ export function pruneDoctorHistory(dir, skillName, maxKeep) {
317
349
  unlinkSync(join(dir, file));
318
350
  }
319
351
  catch { /* ignore */ }
352
+ // 连带删卡片:否则被 prune 掉的报告会经 listDoctorCards 合并在本项目 studio「复活」(正文已删、卡片还在)。
353
+ // 卡片 id = 文件 stem(`{name}-{id}`),与 indexDoctorWrite 写入口径一致。
354
+ removeDoctorCard(file.replace(/\.json$/, ''));
320
355
  }
321
356
  }
@@ -4,7 +4,7 @@ import { BaseCommand } from '../../../oclif/base-command.js';
4
4
  import { LANG_FLAG, bilingual } from '../../../oclif/i18n.js';
5
5
  import { integerStringParser } from '../../../oclif/parsers.js';
6
6
  import { CliExit } from '../../../lib/cli-exit.js';
7
- import { DEFAULT_REPORTS_DIR } from '../../../lib/parse-run-config.js';
7
+ import { projectReportsDir, globalReportsDir } from '../../../../eval-core/measurement-dirs.js';
8
8
  import { requireEvaluationReport } from '../../../lib/shared.js';
9
9
  export default class EvalGoldCompare extends BaseCommand {
10
10
  static description = bilingual({
@@ -62,7 +62,7 @@ export default class EvalGoldCompare extends BaseCommand {
62
62
  }
63
63
  const { loadGoldDataset } = await import('../../../../grading/gold-dataset.js');
64
64
  const { compareGoldToReport, formatGoldCompare } = await import('../../../../grading/gold-cli.js');
65
- const { createFileStore } = await import('../../../../server/report-store.js');
65
+ const { createFileStore, createOverlayReportStore } = await import('../../../../server/report-store.js');
66
66
  const { dataset, issues } = loadGoldDataset(goldDir);
67
67
  if (!dataset) {
68
68
  console.error('Cannot load gold dataset:');
@@ -72,8 +72,11 @@ export default class EvalGoldCompare extends BaseCommand {
72
72
  }
73
73
  for (const i of issues)
74
74
  console.error(`warn: ${i.message}`);
75
- const reportsDir = flags['reports-dir'] ?? DEFAULT_REPORTS_DIR;
76
- const store = createFileStore(resolve(reportsDir));
75
+ // 显式 --reports-dir 固定该目录;默认 overlay(项目 .omk/reports 盖全局),get(reportId) 项目→全局兜底,
76
+ // 不因 eval 写默认翻项目而对比落空。
77
+ const store = flags['reports-dir']
78
+ ? createFileStore(resolve(flags['reports-dir']))
79
+ : createOverlayReportStore(projectReportsDir(), globalReportsDir());
77
80
  const report = requireEvaluationReport(await store.get(reportId), reportId, lang);
78
81
  const samples = Math.max(100, Number(flags['bootstrap-samples'] ?? 1000) || 1000);
79
82
  const seedVal = flags.seed != null ? Number(flags.seed) : undefined;
@@ -18,6 +18,7 @@ export default class Eval extends BaseCommand {
18
18
  executor: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
19
19
  'judge-models': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
20
20
  'output-dir': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
21
+ global: import("@oclif/core/interfaces").BooleanFlag<boolean>;
21
22
  'no-judge': import("@oclif/core/interfaces").BooleanFlag<boolean>;
22
23
  'no-cache': import("@oclif/core/interfaces").BooleanFlag<boolean>;
23
24
  'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;