oh-my-knowledge 0.51.1 → 0.52.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 (75) hide show
  1. package/README.md +27 -6
  2. package/README.zh.md +27 -6
  3. package/dist/assets/agent-skills/omk/SKILL.md +10 -8
  4. package/dist/assets/agent-skills/omk/references/commands.md +2 -2
  5. package/dist/authoring/generator.d.ts +15 -5
  6. package/dist/authoring/generator.js +181 -18
  7. package/dist/authoring/sample-fixer.d.ts +2 -0
  8. package/dist/authoring/sample-fixer.js +19 -5
  9. package/dist/cli/commands/sample.js +11 -4
  10. package/dist/eval-core/evaluation-execution.js +11 -2
  11. package/dist/eval-core/fact-checker.d.ts +15 -2
  12. package/dist/eval-core/fact-checker.js +75 -20
  13. package/dist/eval-core/mock-hook.cjs +40 -1
  14. package/dist/eval-core/mocks-runtime.js +39 -2
  15. package/dist/eval-core/task-planner.d.ts +2 -2
  16. package/dist/eval-core/task-planner.js +7 -7
  17. package/dist/eval-workflows/evaluation-pipeline.js +2 -0
  18. package/dist/eval-workflows/run-evaluation.js +2 -1
  19. package/dist/executors/capabilities.d.ts +15 -0
  20. package/dist/executors/capabilities.js +64 -0
  21. package/dist/executors/index.d.ts +1 -0
  22. package/dist/executors/index.js +4 -1
  23. package/dist/observability/codex-conversation-index.d.ts +88 -0
  24. package/dist/observability/codex-conversation-index.js +566 -0
  25. package/dist/observability/codex-protocol.d.ts +12 -0
  26. package/dist/observability/codex-protocol.js +78 -0
  27. package/dist/observability/codex-tool-status.d.ts +16 -0
  28. package/dist/observability/codex-tool-status.js +114 -0
  29. package/dist/observability/codex-trace-adapter.js +342 -119
  30. package/dist/observability/conversation-catalog.d.ts +38 -0
  31. package/dist/observability/conversation-catalog.js +573 -0
  32. package/dist/observability/conversation-index-process.d.ts +1 -0
  33. package/dist/observability/conversation-index-process.js +31 -0
  34. package/dist/observability/conversation-view-model.d.ts +5 -0
  35. package/dist/observability/conversation-view-model.js +100 -0
  36. package/dist/observability/experience.d.ts +3 -1
  37. package/dist/observability/experience.js +248 -7
  38. package/dist/observability/inbox.js +40 -2
  39. package/dist/observability/knowledge-debugger.d.ts +4 -0
  40. package/dist/observability/knowledge-debugger.js +364 -0
  41. package/dist/observability/polling-subscription-hub.d.ts +27 -0
  42. package/dist/observability/polling-subscription-hub.js +149 -0
  43. package/dist/observability/source-record-archive.d.ts +13 -0
  44. package/dist/observability/source-record-archive.js +324 -0
  45. package/dist/observability/task-window.d.ts +8 -0
  46. package/dist/observability/task-window.js +36 -0
  47. package/dist/observability/trace-ir.d.ts +66 -1
  48. package/dist/observability/trace-source.d.ts +1 -0
  49. package/dist/observability/trace-source.js +12 -5
  50. package/dist/observability/turn-index.d.ts +3 -0
  51. package/dist/observability/turn-index.js +251 -0
  52. package/dist/renderer/conversation-renderer.d.ts +9 -0
  53. package/dist/renderer/conversation-renderer.js +410 -0
  54. package/dist/renderer/icons.js +1 -0
  55. package/dist/renderer/inline-markdown.d.ts +6 -0
  56. package/dist/renderer/inline-markdown.js +158 -0
  57. package/dist/renderer/knowledge-debugger-renderer.d.ts +9 -0
  58. package/dist/renderer/knowledge-debugger-renderer.js +2280 -0
  59. package/dist/renderer/observation-inbox-renderer.js +9 -0
  60. package/dist/renderer/skill-list-renderer.js +2 -1
  61. package/dist/renderer/trajectory-live.d.ts +42 -0
  62. package/dist/renderer/trajectory-live.js +258 -0
  63. package/dist/renderer/trajectory-routing.d.ts +60 -0
  64. package/dist/renderer/trajectory-routing.js +351 -0
  65. package/dist/server/report-server.d.ts +4 -1
  66. package/dist/server/report-server.js +274 -6
  67. package/dist/server/skill-insights.js +1 -1
  68. package/dist/shared/sample-contract.d.ts +1 -0
  69. package/dist/shared/sample-contract.js +35 -0
  70. package/dist/shared/tool-identity.d.ts +8 -0
  71. package/dist/shared/tool-identity.js +13 -0
  72. package/dist/types/eval.d.ts +7 -6
  73. package/dist/types/executor.d.ts +3 -2
  74. package/dist/types/observability.d.ts +225 -1
  75. package/package.json +10 -7
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # oh-my-knowledge
1
+ # OMK
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/oh-my-knowledge.svg)](https://www.npmjs.com/package/oh-my-knowledge)
4
4
  [![npm weekly downloads](https://img.shields.io/npm/dw/oh-my-knowledge.svg)](https://www.npmjs.com/package/oh-my-knowledge)
@@ -8,14 +8,19 @@
8
8
 
9
9
  **English** | [简体中文](./README.zh.md)
10
10
 
11
- **Stop editing LLM knowledge inputs by gut feel.**
12
- `oh-my-knowledge` (omk) is a measurement workflow for prompts, RAG context, skills, agents, and workflows. It fixes the executor model and the evaluation samples, changes only the knowledge artifact, then answers the release question that matters: **can v2 ship, and where is it better?**
11
+ **Observe. Measure. Know.**
12
+
13
+ **OMK makes every knowledge change in your AI application evidence-backed.**
14
+
15
+ Observe real-world performance, measure version differences, and determine whether the change is effective and the version is ready to ship.
16
+
17
+ **Same model. Same evaluation samples. Only the knowledge artifact changes.**
13
18
 
14
19
  ![omk knowledge artifact evaluation flow: doctor / eval / observe / sample / evolve loop](./docs/public/omk-knowledge-flow-en-animated.gif)
15
20
 
16
21
  📖 **Full documentation: [oh-my-knowledge.pages.dev](https://oh-my-knowledge.pages.dev)** (searchable, English / 简体中文)
17
22
 
18
- ## What omk makes measurable
23
+ ## What OMK helps you know
19
24
 
20
25
  | Decision | Command | Evidence you get |
21
26
  |---|---|---|
@@ -23,6 +28,7 @@
23
28
  | Is v2 actually better than v1? | `omk eval` | one-line verdict, confidence interval, failed samples, cost |
24
29
  | Why did it pass or fail? | `omk studio` | report view with scores, diagnostics, and examples |
25
30
  | Should this version become the accepted one? | `omk promote` / `omk evolve` | evidence-gated accept or generate a better candidate |
31
+ | What happened during one real AI task? | `omk observe` / Studio Task Trajectory | a trace-backed view of the request, visible Knowledge, tool calls, results, response, and user correction |
26
32
  | What did real usage expose? | `omk observe` / `omk sample --from-traces` | production gaps drafted for review; reviewed drafts can become eval samples |
27
33
 
28
34
  ![omk report — verdict pill "v2 is clearly better than v1 — ready to ship"](./assets/screenshots/report-overview.png)
@@ -57,9 +63,23 @@ Walkthrough: [5-minute quickstart guide](docs/quickstart-skill-eval.md) (recomme
57
63
 
58
64
  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)
59
65
 
60
- ## The omk loop
66
+ ## Inspect one Codex task
67
+
68
+ If you only want to see what happened behind one Codex conversation, you do not need to run `observe ingest` first:
69
+
70
+ ```bash
71
+ omk studio
72
+ ```
73
+
74
+ Studio opens the local Codex conversation overview at `http://127.0.0.1:7799` by default. Select a conversation, then a task, to open **Task Trajectory**. Its four lanes — **Conversation, Actions, Results, and Knowledge** — show the request, AI responses, tool calls, tool returns, and observable context, with drill-downs into normalized events and raw logs.
75
+
76
+ Running tasks are prioritized and update live. While **Following**, the trajectory advances smoothly as events arrive; after you inspect an earlier point, Studio keeps your position and offers **View updates**. Old logs without a terminal event are marked **End status not recorded** instead of remaining live forever.
77
+
78
+ Task Trajectory only reconstructs facts observable in the log. It does not reveal or infer hidden reasoning. See [Observe production traces](docs/guides/observe-production.md#inspect-one-task) for the full model.
79
+
80
+ ## The OMK loop
61
81
 
62
- omk is for authors and maintainers of LLM knowledge artifacts who need a release decision, not for passive end-users of a skill. The main loop is deliberately controlled:
82
+ OMK is for authors and maintainers of LLM knowledge artifacts who need a release decision, not for passive end-users of a skill. The main loop is deliberately controlled:
63
83
 
64
84
  ```text
65
85
  change a prompt / RAG / skill / agent artifact
@@ -166,6 +186,7 @@ The full docs are published at **[oh-my-knowledge.pages.dev](https://oh-my-knowl
166
186
  - **[CLI reference](docs/reference/cli.md)** — all top-level commands with bash examples and flag tables
167
187
  - **[Executors](docs/reference/executors.md)** & **[artifact layout](docs/reference/artifact-layout.md)** — built-in / custom executors; how `variant` resolves to an artifact + runtime context
168
188
  - **[How-to guides](docs/guides/agent-eval.md)** — [evaluate an agent](docs/guides/agent-eval.md) (project runtime context) and [use non-Claude models](docs/guides/non-claude-models.md) (GLM / Qwen / DeepSeek / Moonshot / Ollama)
189
+ - **[Observe & inspect task trajectories](docs/guides/observe-production.md)** — browse local Codex conversations, drill into one task, and follow its observable execution live
169
190
  - **[Quickstart](docs/quickstart-skill-eval.md)** — first-time five-minute walkthrough
170
191
  - **[Example gallery](https://github.com/lizhiyao/oh-my-knowledge/tree/main/examples)** — a set of runnable examples in the repo, arranged simplest-to-richest
171
192
  - **[Sample design spec](docs/specs/sample-design-spec.md)** — capability / construct / provenance metadata; industry-gap mapping
package/README.zh.md CHANGED
@@ -1,4 +1,4 @@
1
- # oh-my-knowledge
1
+ # OMK
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/oh-my-knowledge.svg)](https://www.npmjs.com/package/oh-my-knowledge)
4
4
  [![npm weekly downloads](https://img.shields.io/npm/dw/oh-my-knowledge.svg)](https://www.npmjs.com/package/oh-my-knowledge)
@@ -8,14 +8,19 @@
8
8
 
9
9
  [English](./README.md) | **简体中文**
10
10
 
11
- **别再凭感觉改 LLM 知识输入。**
12
- `oh-my-knowledge`(omk)是一套面向 prompt、RAG、skill、agent、workflow 的测量工作流。它固定执行模型和评测用例,只改变知识载体,然后回答发布时真正要追问的问题:**v2 能不能发,好在哪里?**
11
+ **Observe. Measure. Know.**
12
+
13
+ **OMK,让 AI 应用的知识改动有据可依。**
14
+
15
+ 观测真实表现,量出版本差异,判断改动是否有效、版本能否发布。
16
+
17
+ **相同模型,相同评测用例,只改变知识载体。**
13
18
 
14
19
  ![omk 知识载体评测流程:doctor / eval / observe / sample / evolve 闭环](./docs/public/omk-knowledge-flow-animated.gif)
15
20
 
16
21
  📖 **完整文档:[oh-my-knowledge.pages.dev/zh](https://oh-my-knowledge.pages.dev/zh/)**(可搜索,可切换英文)
17
22
 
18
- ## omk 让什么变得可测量
23
+ ## OMK 让你知道什么
19
24
 
20
25
  | 决策问题 | 命令 | 你会得到的证据 |
21
26
  |------|------|------|
@@ -23,6 +28,7 @@
23
28
  | v2 是否真的优于 v1? | `omk eval` | 一行 verdict、置信区间、失败样本、成本 |
24
29
  | 它为什么通过或失败? | `omk studio` | 分数、诊断、样本证据的报告视图 |
25
30
  | 这个版本是否应成为接受版本? | `omk promote` / `omk evolve` | 基于证据接受,或生成更好的候选版 |
31
+ | 一次真实 AI 任务中发生了什么? | `omk observe` / Studio 任务轨迹 | 请求、可见知识、工具调用与结果、回答和用户纠正的可核验轨迹 |
26
32
  | 真实使用暴露了哪些知识缺口? | `omk observe` / `omk sample --from-traces` | 将线上缺口生成待复核草稿,复核后再沉淀为评测样本 |
27
33
 
28
34
  ![omk 报告 — verdict pill「v2 明显优于 v1,可以发布」](./assets/screenshots/report-overview-zh.png)
@@ -57,9 +63,23 @@ export OMK_EXECUTOR=codex
57
63
 
58
64
  深入:[为谁、解决什么](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) · [知识载体布局](docs/zh/reference/artifact-layout.md)
59
65
 
60
- ## omk 的闭环
66
+ ## 先看清一次 Codex 任务
67
+
68
+ 只想知道一次 Codex 对话背后发生了什么,不需要先运行 `observe ingest`:
69
+
70
+ ```bash
71
+ omk studio
72
+ ```
73
+
74
+ Studio 默认在 `http://127.0.0.1:7799` 打开本机 Codex 对话总览。先选择一段对话,再选择其中一次任务,即可进入「任务轨迹」:按**对话、执行、结果、知识**四条泳道查看请求、AI 回答、工具调用、工具返回和可见上下文,并可下钻到规范化事件与原始日志。
75
+
76
+ 进行中的任务会优先显示并实时更新。保持「跟随中」时,轨迹会随新事件平滑前进;手动查看历史位置后,页面保留当前位置并提示「查看更新」。旧日志如果没有记录结束事件,会标记为「未记录结束状态」,不会一直冒充进行中。
77
+
78
+ 任务轨迹只还原日志中可观测的事实,不展示或推断隐藏思维。完整说明见[观测与任务轨迹](docs/zh/guides/observe-production.md#查看一次任务)。
79
+
80
+ ## OMK 的闭环
61
81
 
62
- omk 主要给 LLM 知识载体的作者 / 维护者用,帮他们做发布判断;它不是给被动安装 skill 的普通使用者用的。主流程刻意保持受控:
82
+ OMK 主要给 LLM 知识载体的作者 / 维护者用,帮他们做发布判断;它不是给被动安装 skill 的普通使用者用的。主流程刻意保持受控:
63
83
 
64
84
  ```text
65
85
  改了一份 prompt / RAG / skill / agent 知识载体
@@ -166,6 +186,7 @@ RAG 专项评测请看 RAGAS(独立 niche,跟 omk 互补)。完整对比
166
186
  - **[CLI 参考](docs/zh/reference/cli.md)** —— 顶层命令的 bash 示例和 flag 表
167
187
  - **[执行器](docs/zh/reference/executors.md)** & **[知识载体布局](docs/zh/reference/artifact-layout.md)** —— 内置 / 自定义执行器;variant 如何解析为 artifact + runtime context
168
188
  - **[操作指南](docs/zh/guides/agent-eval.md)** —— [评测 agent](docs/zh/guides/agent-eval.md)(项目 runtime context)与[使用非 Claude 模型](docs/zh/guides/non-claude-models.md)(GLM / 通义 / DeepSeek / Moonshot / Ollama)
189
+ - **[观测与任务轨迹](docs/zh/guides/observe-production.md)** —— 浏览本机 Codex 对话,下钻一次任务,并实时跟随可观测执行过程
169
190
  - **[快速上手](docs/zh/quickstart-skill-eval.md)** —— 第一次跑评测的 5 分钟教程
170
191
  - **[示例画廊](https://github.com/lizhiyao/oh-my-knowledge/tree/main/examples)** —— 仓库里一组可直接跑的示例,按由简到全排成上手路径
171
192
  - **[用例设计规范](docs/zh/specs/sample-design-spec.md)** —— capability / construct / provenance 元数据;行业 gap 映射
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  name: omk
3
3
  description: |
4
- oh-my-knowledge 知识载体评测工具的智能代理。评测 skill(系统提示词)质量,对比不同版本效果,自动迭代改进。
4
+ OMK(Observe. Measure. Know.)让 AI 应用的知识改动有据可依。观测真实表现,受控测量 prompt / RAG / skill / agent / workflow 的版本差异,判断改动是否有效、版本能否发布,并支持自动迭代改进。
5
5
  Use when: 用户提到"评测"、"测评"、"eval"、"benchmark"、"对比 skill"、"改进 skill"、"evolve"、"生成测试用例"、"gen-samples"、"omk"。
6
6
  user-invocable: true
7
7
  argument-hint: "<doctor|eval|evolve|init|install|list|observe|promote|rollback|sample|studio> [options]"
8
8
  ---
9
9
 
10
- # OMK — 知识载体评测
10
+ # OMK — Observe. Measure. Know.
11
11
 
12
- 你是 oh-my-knowledge(omk)的智能代理。帮助用户评测、对比、改进 AI skill(系统提示词),用数据说话。
12
+ 你是 OMK 的智能代理。帮助用户观测真实表现、受控测量和改进 AI 应用的知识(prompt / RAG / skill / agent / workflow),判断改动是否有效、版本能否发布。
13
13
 
14
14
  ## 第一步:检查环境
15
15
 
@@ -37,7 +37,7 @@ Codex 是 omk 的一等 runtime。运行在 Codex 任务中时,`omk eval` / `d
37
37
  | 改进 / 优化 skill | → `omk evolve`(自动多轮迭代) |
38
38
  | 生成测试用例 | → `omk sample` |
39
39
  | 体检 skill 写法 | → `omk doctor` |
40
- | 查看 / 浏览报告 | → `omk studio`(启动本地报告浏览器) |
40
+ | 浏览对话、任务轨迹与报告 | → `omk studio`(启动本地知识工作台) |
41
41
  | 看真实使用 trace | → `omk observe` |
42
42
  | 查看受管 skill 状态 | → `omk list` |
43
43
  | 按证据接受 / 回退某版本 | → `omk promote` / `omk rollback` |
@@ -123,6 +123,8 @@ omk sample skills/my-skill/SKILL.md --focus "重点覆盖搜索失败 / 权限
123
123
  omk sample --batch
124
124
  ```
125
125
 
126
+ 目标执行器不支持工具拦截时,`omk sample` 会自动生成无 mock 用例。当前 `codex` / `codex-sdk` 属于这种情况;不要手工补 `mocks` 或 `mock_hit`。已有 mocks 用例会被 `omk eval` 在模型调用前拒绝,避免把执行器能力缺口误判成模型失败。`environment.files_available` 只提供题设上下文,不会在 `cwd` 物化文件。
127
+
126
128
  输出位置:目录 skill(`<skill>/SKILL.md`)→ `<skill>/.omk/samples.json`(标准);扁平 `.md` 单次生成 → 当前目录 `eval-samples.json`(项目级兜底);扁平 `.md` 的 `--batch` 兼容生成 `<skill-dir>/<name>.eval-samples.json`。
127
129
 
128
130
  ### 观测真实使用
@@ -136,7 +138,7 @@ omk observe ingest ~/.codex/sessions
136
138
  omk observe ~/.claude/projects/<project> --last 7d
137
139
  ```
138
140
 
139
- Codex rollout 会保留 `sourceKind=codex`、模型、父子任务、tool call 和 token 证据,并从实际读取的 `skills/<name>/SKILL.md` 归因 skill。`observe ingest` 生成待复核 observation;确认真实知识缺口后,再用 `omk sample --from-traces` 草拟评测用例。
141
+ Codex rollout 会保留 `sourceKind=codex`、模型、父子任务、tool call 和 token 证据,并从实际读取的 `skills/<name>/SKILL.md` 归因 skill。`omk studio` 无需先 ingest,即可从本机 Codex 对话总览进入某次任务的实时轨迹;`observe ingest` 只在需要生成待复核 observation 时运行。任务轨迹按对话、执行、结果和知识呈现结构化事实,并联动检查配对后的工具调用与结果、AI 回答和用户纠正。该页面只呈现 trace 中可观测的执行过程,不推断隐藏思维或失败根因。确认真实知识缺口后,再用 `omk sample --from-traces` 草拟评测用例。
140
142
 
141
143
  ### 体检 skill 写法
142
144
 
@@ -153,16 +155,16 @@ omk doctor skills/my-skill.md
153
155
 
154
156
  `omk eval` 默认会先跑一次 doctor 当 preflight 门禁,所以一般不用单独跑;想在 eval 之前先把结构问题先扫一遍再跑评测,就单独跑 `omk doctor`。
155
157
 
156
- ### 查看 / 浏览报告
158
+ ### 浏览对话、任务轨迹与报告
157
159
 
158
160
  ```bash
159
- omk studio # 启动本地 web 报告浏览器
161
+ omk studio # 启动本地知识工作台(默认端口 7799)
160
162
  omk studio --port 8080 # 改端口
161
163
  omk studio --host 0.0.0.0 # 局域网访问(默认 127.0.0.1)
162
164
  omk studio --no-open # 不自动开浏览器
163
165
  ```
164
166
 
165
- Studio skill-centric:列表页(`/`)按 skill 卡片展示健康等级 / 0-100 参考分 / 待优化数 / 趋势;详情页(`/skills/<name>`)左栏列关键问题清单,右栏画健康趋势 + 三档阶段卡(doctor / eval / observe)。访问 `/observe-inbox` 查看 observe inbox 看板。
167
+ Studio 首页直接索引本机 Codex 对话。先选择对话,再选择任务查看四泳道任务轨迹;进行中的任务支持实时跟随。顶部「知识载体」入口用于浏览 doctor / eval / observe 报告,`/observe-inbox` 用于复核 observation。无需为了浏览本机 Codex 对话而先运行 `omk observe ingest`。
166
168
 
167
169
  ## 第五步:解读结果
168
170
 
@@ -602,7 +602,7 @@ omk sample [skillPath] [flags]
602
602
  - `--from-traces` `boolean`:from-traces 模式:从 observe inbox 的失败信号回流生成评测用例草稿(provenance: production-trace),落草稿待人工 review。
603
603
  - `--lang` `option` (默认 `zh`):输出语言 zh|en,优先级 CLI > OMK_LANG env > zh。
604
604
  - `--model` `option`:生成 LLM model 名。Codex 自动读取本机配置;也可用 OMK_MODEL 设置环境偏好。
605
- - `--no-mock` `boolean`:不生成 mocks,eval 时所有工具调用真实执行。
605
+ - `--no-mock` `boolean`:不生成 mocks。执行器不支持工具拦截时会自动启用,避免产生必然失败的 mock_hit。
606
606
  - `--observations-dir` `option`:observe inbox 目录(from-traces 模式用),默认项目 .omk/observe-inbox。
607
607
  - `--reports-dir` `option`:报告目录(fix 模式用),默认 ~/.oh-my-knowledge/reports。
608
608
  - `--skill` `option`:仅从指定 skill 的 observe inbox 信号生成草稿(仅 from-traces 模式用)。
@@ -689,7 +689,7 @@ omk studio --port 8080 --no-open
689
689
  | `construct` | 否 | 测的是什么构念 |
690
690
  | `provenance` | 否 | 用例来源(`omk sample` 自动打) |
691
691
  | `mocks` | 否 | 工具调用 mock 返回(sandbox 评测) |
692
- | `environment` | 否 | 评测环境前置「已就绪」声明 |
692
+ | `environment` | 否 | 题设环境声明(仅注入 prompt,不物化) |
693
693
  | `tripwire` | 否 | 标记为「故意诱错」样本,failed 时 diagnostic 不建议改 skill |
694
694
 
695
695
  完整 schema 见 [docs/specs/sample-design-spec.md](https://github.com/lizhiyao/oh-my-knowledge/blob/main/docs/specs/sample-design-spec.md)。
@@ -1,6 +1,7 @@
1
- import type { Sample, ExecutorFn } from '../types/index.js';
1
+ import type { ExecutorFn, Sample } from '../types/index.js';
2
2
  import type { ObservationInboxItem } from '../types/observability.js';
3
- interface GenerateSamplesOptions {
3
+ export declare function sampleGenerationUsesMocks(executorName: string | undefined, noMock?: boolean): boolean;
4
+ export interface GenerateSamplesOptions {
4
5
  skillContent: string;
5
6
  count?: number;
6
7
  model: string;
@@ -12,6 +13,8 @@ interface GenerateSamplesOptions {
12
13
  focus?: string;
13
14
  /** 不生成 mocks/mocksStrict,eval 时真实执行所有工具调用。 */
14
15
  noMock?: boolean;
16
+ /** Injectable executor for tests. Defaults to createExecutor(executorName). */
17
+ executor?: ExecutorFn;
15
18
  }
16
19
  /**
17
20
  * 拼出送给 LLM 的 user prompt。抽出来便于单测验证 focus 是否真的注入了。
@@ -26,7 +29,7 @@ export declare function buildSamplesPrompt({ skillContent, count, focus, noMock
26
29
  focus?: string;
27
30
  noMock?: boolean;
28
31
  }): string;
29
- export declare function generateSamples({ skillContent, count, model, executorName, focus, noMock }: GenerateSamplesOptions): Promise<{
32
+ export declare function generateSamples({ skillContent, count, model, executorName, focus, noMock, executor: injectedExecutor, }: GenerateSamplesOptions): Promise<{
30
33
  samples: Sample[];
31
34
  costUSD: number;
32
35
  }>;
@@ -62,12 +65,16 @@ export declare function stratifyTraceSignals(items: TraceSignalItem[]): Stratifi
62
65
  * production failures. The trace text feeds the *generator* only — never the
63
66
  * judge prompt — so judge-prompt isolation is unaffected.
64
67
  */
65
- export declare function buildSamplesFromTracesPrompt(items: TraceSignalItem[], count?: number): string;
68
+ export declare function buildSamplesFromTracesPrompt(items: TraceSignalItem[], count?: number, options?: {
69
+ noMock?: boolean;
70
+ }): string;
66
71
  export interface GenerateSamplesFromTracesOptions {
67
72
  items: TraceSignalItem[];
68
73
  count?: number;
69
74
  model: string;
70
75
  executorName?: string;
76
+ /** 不生成 mocks。目标 executor 不支持时会自动启用。 */
77
+ noMock?: boolean;
71
78
  /** Injectable executor (tests). Defaults to createExecutor(executorName). */
72
79
  executor?: ExecutorFn;
73
80
  }
@@ -77,12 +84,15 @@ export interface GenerateSamplesFromTracesOptions {
77
84
  * stamps `provenance: 'production-trace'`. Output is meant to land in a review draft,
78
85
  * not the live dataset (the CLI enforces that).
79
86
  */
80
- export declare function generateSamplesFromTraces({ items, count, model, executorName, executor: injectedExecutor, }: GenerateSamplesFromTracesOptions): Promise<{
87
+ export declare function generateSamplesFromTraces({ items, count, model, executorName, noMock, executor: injectedExecutor, }: GenerateSamplesFromTracesOptions): Promise<{
81
88
  samples: Sample[];
82
89
  costUSD: number;
83
90
  }>;
84
91
  export declare function sanitizeGeneratedSamples(samples: Sample[], opts?: {
85
92
  skillContent?: string;
93
+ mockless?: boolean;
94
+ migrateMocklessEnvironment?: boolean;
95
+ preserveMocklessEnvironment?: boolean;
86
96
  }): {
87
97
  stripped: string[];
88
98
  };
@@ -1,5 +1,7 @@
1
1
  import { createExecutor } from '../executors/index.js';
2
+ import { executorSupportsSampleMocks } from '../executors/capabilities.js';
2
3
  import { DEFAULT_GATE_THRESHOLD } from '../eval-core/verdict.js';
4
+ import { sampleMockReferenceKeys } from '../shared/sample-contract.js';
3
5
  const SYSTEM_PROMPT = `你是一个评测用例生成器。你的任务是根据用户提供的 skill(系统提示词)内容,生成高质量的评测用例。
4
6
 
5
7
  样本结构决策(必须先做):先扫一遍 skill 内容判断它属于哪一类,按对应配比和数量生成。
@@ -195,14 +197,14 @@ const SYSTEM_PROMPT = `你是一个评测用例生成器。你的任务是根据
195
197
  tool_input_not_contains 或 tools_not_called。
196
198
  - { "type": "regex", "pattern": "...", "weight": 1 }
197
199
  ↑ 同 contains 限制:只用在固定格式字面量(如 SHA / UUID / 路径模板)。
198
- - environment: 可选,对象。**评测环境的"已就绪"声明**,LLM 看到后跳过环境探测直接进工作流。
200
+ - environment: 可选,对象。**仅作 prompt 上下文的题设环境声明**,不会修改 PATH、创建文件或物化 fixture。
199
201
  字段:
200
202
  - cli_available: string[],已在 PATH 上的 CLI(如 ["node", "git", "code-host"])
201
203
  - files_available: string[],已存在的文件/脚本(如 ["~/.req-tool-api.json", "$SKILL_DIR/scripts/x.js"])
202
204
  - notes: string,自由文本兜底(如"DevAPI 凭证有效,工号 testuser001")
203
205
  原则:
204
- 凡是 skill 跑起来需要的环境(凭证文件 / 业务 CLI / 自带脚本 / API token 等),
205
- 都写到这里,而不是在 mock mock 它们的探测命令。这让 mock 只关注业务调用本身。
206
+ 只有用例明确把某项环境能力作为题设前提时才写。需要读取真实内容的文件不能放在
207
+ files_available 里冒充 fixture:应放进 sample.cwd 下的真实 fixture,或由 mock 返回内容。
206
208
  - mocksStrict: **必填且必须设为 true**(只要 sample 配了 mocks)。
207
209
  原因:mocksStrict=false 时,LLM 调到没匹配 mock 的命令会**透传到真 shell**,
208
210
  既可能真调外部接口产生副作用,也可能因二进制不存在(如 mcporter)报噪声错误污染评测信号。
@@ -223,8 +225,8 @@ const SYSTEM_PROMPT = `你是一个评测用例生成器。你的任务是根据
223
225
  - 对(✅):写一条宽 mock:\`{tool:"Bash", match:{command_glob:"ls *"},
224
226
  return:{stdout:"<模拟目录列表>", exit:0}}\` — \`command_glob\` 用 \`*\` 兜底各种
225
227
  ls 参数变体(\`ls\` / \`ls -la\` / \`ls -d\` / \`ls /xx\` 全命中)
226
- (c) 单纯"已就绪"声明(凭证文件 / 业务 CLI 是否安装)还是走 \`environment\` 字段,
227
- 不需要 LLM 真调命令检查 — environment 字段就是告诉 LLM "这些不用检查"
228
+ (c) 题设明确声明可用的凭证 / CLI 可写进 \`environment\`,让 LLM 不做可用性探测。
229
+ 该字段只进 prompt,不会真的安装 CLI、创建凭证文件或改变 runtime
228
230
  (d) **intent-level mock(文件搜索/读取类操作)** — LLM 搜代码时会自由选择 Bash grep、
229
231
  Grep 工具、Glob+Read 组合、甚至 Agent 子代理,逐个枚举工具写 mock 不可持续。
230
232
  正确做法:用 \`tool: "*"\` + \`input_contains: "关键词"\` 按意图匹配:
@@ -367,6 +369,37 @@ const SYSTEM_PROMPT = `你是一个评测用例生成器。你的任务是根据
367
369
  - 字符串字段(prompt / rubric / capability 等)内部如需引号,**必须用全角「」**而不是半角 \`""\`,避免漏转义破坏 JSON 解析
368
370
  - 例:错 → \`"prompt": "查询"Daily"标签..."\`(内部 \`"\` 未转义,JSON 解析失败)
369
371
  对 → \`"prompt": "查询「Daily」标签..."\`(全角引号,无转义压力)`;
372
+ const MOCKLESS_SYSTEM_OVERRIDE = `
373
+
374
+ ## 目标执行器能力约束(最高优先级)
375
+
376
+ 目标执行器不支持可靠的工具调用拦截。本节覆盖上文所有要求 mocks、mock_hit、
377
+ files_available 或正向工具调用断言的规则:
378
+
379
+ - 不要输出 mocks、mocksStrict、environment 字段。
380
+ - 不要输出 mock_hit、tools_called、tools_count_min、tool_input_contains、tool_output_contains。
381
+ - 可以保留 tools_not_called、tools_count_max、tool_input_not_contains 这类负向安全约束。
382
+ - 把必要的输入事实直接写进 context,把可判分结果写进 rubric 或输出内容断言。
383
+ - 不要假装某个文件、CLI 或工具调用已经由 fixture 物化。`;
384
+ function generationSystemPrompt(mockless) {
385
+ return mockless ? `${SYSTEM_PROMPT}${MOCKLESS_SYSTEM_OVERRIDE}` : SYSTEM_PROMPT;
386
+ }
387
+ const warnedAutoMocklessExecutors = new Set();
388
+ function warnAutoMockless(executorName, noMock) {
389
+ if (noMock
390
+ || !executorName
391
+ || executorSupportsSampleMocks(executorName)
392
+ || warnedAutoMocklessExecutors.has(executorName))
393
+ return;
394
+ warnedAutoMocklessExecutors.add(executorName);
395
+ process.stderr.write(`[omk sample] 执行器「${executorName}」不支持工具调用拦截,`
396
+ + '已自动切换为无 mocks 用例。\n');
397
+ }
398
+ export function sampleGenerationUsesMocks(executorName, noMock = false) {
399
+ if (noMock)
400
+ return false;
401
+ return executorName ? executorSupportsSampleMocks(executorName) : true;
402
+ }
370
403
  /**
371
404
  * 拼出送给 LLM 的 user prompt。抽出来便于单测验证 focus 是否真的注入了。
372
405
  *
@@ -390,9 +423,12 @@ ${skillContent}
390
423
 
391
424
  ${countLine}直接输出 JSON 数组。${focusBlock}${noMockBlock}`;
392
425
  }
393
- export async function generateSamples({ skillContent, count, model, executorName, focus, noMock }) {
394
- const executor = createExecutor(executorName);
395
- const prompt = buildSamplesPrompt({ skillContent, count, focus, noMock });
426
+ export async function generateSamples({ skillContent, count, model, executorName, focus, noMock, executor: injectedExecutor, }) {
427
+ const executor = injectedExecutor ?? createExecutor(executorName);
428
+ const mockless = !sampleGenerationUsesMocks(executorName, noMock);
429
+ warnAutoMockless(executorName, noMock);
430
+ const prompt = buildSamplesPrompt({ skillContent, count, focus, noMock: mockless });
431
+ const system = generationSystemPrompt(mockless);
396
432
  // 生成场景比单次 eval 调用更重(LLM 要思考结构 + 输出大段 JSON),
397
433
  // 默认 120s 对长 skill + count >= 8 经常不够,这里用 5 分钟兜底。
398
434
  // lean=true 关掉 agent 工具循环 / skill 发现 — 生成只需要纯文本,不需要 Bash / Read 等工具。
@@ -405,7 +441,7 @@ export async function generateSamples({ skillContent, count, model, executorName
405
441
  const attemptPrompt = attempt === 1
406
442
  ? prompt
407
443
  : `${prompt}\n\n上一次输出解析失败:${lastErr}\n请严格按 JSON 规范输出(字符串内部用「」全角引号),只输出数组,不要包含其他文字。`;
408
- const result = await executor({ model, system: SYSTEM_PROMPT, prompt: attemptPrompt, timeoutMs: 300_000, lean: true });
444
+ const result = await executor({ model, system, prompt: attemptPrompt, timeoutMs: 300_000, lean: true });
409
445
  totalCost += result.costUSD || 0;
410
446
  if (!result.ok) {
411
447
  lastErr = result.error || 'unknown error';
@@ -435,12 +471,15 @@ export async function generateSamples({ skillContent, count, model, executorName
435
471
  continue;
436
472
  }
437
473
  // 通过校验,跳出循环继续后续 sanitize
438
- return await finalizeSamples(samples, totalCost, skillContent);
474
+ return await finalizeSamples(samples, totalCost, {
475
+ skillContent,
476
+ mockless,
477
+ });
439
478
  }
440
479
  // 不可达 (循环里所有出口都 throw 或 return),保留是为了 TS 类型推断
441
480
  throw new Error('unreachable');
442
481
  }
443
- async function finalizeSamples(samples, costUSD, skillContent) {
482
+ async function finalizeSamples(samples, costUSD, options) {
444
483
  // Validate required fields + sanitize metadata enums *at generator boundary*
445
484
  // (see sanitizeGeneratedSamples). skillContent is passed so the function can
446
485
  // strip "脑补"-style fact assertions whose tool name has no literal mention
@@ -448,7 +487,10 @@ async function finalizeSamples(samples, costUSD, skillContent) {
448
487
  // the data-security-review v1-v5 regen series (generator kept producing
449
488
  // tool_input_contains "WebFetch:语雀URL" even after 7 prompt iterations,
450
489
  // because LLM's "URL → fetch" training prior overrides instructional text).
451
- const { stripped } = sanitizeGeneratedSamples(samples, { skillContent });
490
+ const { stripped } = sanitizeGeneratedSamples(samples, {
491
+ ...options,
492
+ migrateMocklessEnvironment: true,
493
+ });
452
494
  if (stripped.length > 0) {
453
495
  process.stderr.write(`[omk sample] LLM-output 含 ${stripped.length} 个非法元数据/断言字段,已剥离避免污染:\n - ${stripped.join('\n - ')}\n`);
454
496
  }
@@ -492,7 +534,7 @@ export function stratifyTraceSignals(items) {
492
534
  * production failures. The trace text feeds the *generator* only — never the
493
535
  * judge prompt — so judge-prompt isolation is unaffected.
494
536
  */
495
- export function buildSamplesFromTracesPrompt(items, count) {
537
+ export function buildSamplesFromTracesPrompt(items, count, options = {}) {
496
538
  // 先按频次分层(合并重复 + 算占比 + 降序),让模型按「占比」分配配额,而非每信号一刀切。
497
539
  const stratified = stratifyTraceSignals(items);
498
540
  const sections = stratified.map((it, i) => {
@@ -515,13 +557,16 @@ export function buildSamplesFromTracesPrompt(items, count) {
515
557
  const countLine = typeof count === 'number'
516
558
  ? `共生成约 ${count} 条评测用例,按各信号的「占比」分配配额(高频多、低频少),覆盖整体失败分布。`
517
559
  : '按各信号「占比」分配:高频信号多生成、低频少生成,覆盖整体失败分布。';
560
+ const mocklessBlock = options.noMock
561
+ ? '\n\n目标执行器不支持工具调用拦截:不要生成 mocks、mocksStrict、environment 或正向工具调用断言;把 trace 证据写入 context 和 rubric。'
562
+ : '';
518
563
  return `${TRACE_GEN_INSTRUCTIONS}
519
564
 
520
565
  ## 观测到的失败信号(共 ${stratified.length} 个,已按出现频次降序)
521
566
 
522
567
  ${sections}
523
568
 
524
- ${countLine}直接输出 JSON 数组。`;
569
+ ${countLine}直接输出 JSON 数组。${mocklessBlock}`;
525
570
  }
526
571
  /** Build a sanitize context string from trace evidence so finalizeSamples keeps
527
572
  * tool-name assertions that reference tools actually seen in the traces (an empty
@@ -539,14 +584,17 @@ function traceSanitizeContext(items) {
539
584
  * stamps `provenance: 'production-trace'`. Output is meant to land in a review draft,
540
585
  * not the live dataset (the CLI enforces that).
541
586
  */
542
- export async function generateSamplesFromTraces({ items, count, model, executorName, executor: injectedExecutor, }) {
587
+ export async function generateSamplesFromTraces({ items, count, model, executorName, noMock, executor: injectedExecutor, }) {
543
588
  if (items.length === 0)
544
589
  return { samples: [], costUSD: 0 };
545
590
  if (!injectedExecutor && !executorName) {
546
591
  throw new Error('executorName is required when no executor is injected');
547
592
  }
548
593
  const executor = injectedExecutor ?? createExecutor(executorName);
549
- const prompt = buildSamplesFromTracesPrompt(items, count);
594
+ const mockless = !sampleGenerationUsesMocks(executorName, noMock);
595
+ warnAutoMockless(executorName, noMock);
596
+ const prompt = buildSamplesFromTracesPrompt(items, count, { noMock: mockless });
597
+ const system = generationSystemPrompt(mockless);
550
598
  const sanitizeContext = traceSanitizeContext(items);
551
599
  const PROVENANCE = 'production-trace';
552
600
  const MAX_ATTEMPTS = 3;
@@ -556,7 +604,7 @@ export async function generateSamplesFromTraces({ items, count, model, executorN
556
604
  const attemptPrompt = attempt === 1
557
605
  ? prompt
558
606
  : `${prompt}\n\n上一次输出解析失败:${lastErr}\n请严格按 JSON 规范输出(字符串内部用「」全角引号),只输出数组,不要包含其他文字。`;
559
- const result = await executor({ model, system: SYSTEM_PROMPT, prompt: attemptPrompt, timeoutMs: 300_000, lean: true });
607
+ const result = await executor({ model, system, prompt: attemptPrompt, timeoutMs: 300_000, lean: true });
560
608
  totalCost += result.costUSD || 0;
561
609
  if (!result.ok) {
562
610
  lastErr = result.error || 'unknown error';
@@ -593,7 +641,10 @@ export async function generateSamplesFromTraces({ items, count, model, executorN
593
641
  // Stamp provenance before sanitize so it survives (it's a valid enum value).
594
642
  for (const s of samples)
595
643
  s.provenance = PROVENANCE;
596
- return await finalizeSamples(samples, totalCost, sanitizeContext);
644
+ return await finalizeSamples(samples, totalCost, {
645
+ skillContent: sanitizeContext,
646
+ mockless,
647
+ });
597
648
  }
598
649
  throw new Error('unreachable');
599
650
  }
@@ -656,6 +707,78 @@ const TEXT_VALUE_TYPES = new Set([
656
707
  const TOOL_POSITIVE_TYPES = new Set([
657
708
  'tool_input_contains', 'tool_output_contains', 'mock_hit',
658
709
  ]);
710
+ const MOCKLESS_POSITIVE_TOOL_TYPES = new Set([
711
+ 'mock_hit',
712
+ 'tools_called',
713
+ 'tools_count_min',
714
+ 'tool_input_contains',
715
+ 'tool_output_contains',
716
+ ]);
717
+ function stripMocklessPositiveToolAssertions(assertions, label, stripped) {
718
+ const kept = [];
719
+ for (const [index, assertion] of assertions.entries()) {
720
+ const assertionLabel = `${label}[${index}]`;
721
+ if (assertion?.type === 'assert-set' && Array.isArray(assertion.children)) {
722
+ const children = stripMocklessPositiveToolAssertions(assertion.children, `${assertionLabel}.children`, stripped);
723
+ if (children.length === 0) {
724
+ stripped.push(`${assertionLabel}.assert-set(无 mock 模式下没有可执行子断言)`);
725
+ continue;
726
+ }
727
+ assertion.children = children;
728
+ kept.push(assertion);
729
+ continue;
730
+ }
731
+ if (MOCKLESS_POSITIVE_TOOL_TYPES.has(assertion?.type)) {
732
+ stripped.push(`${assertionLabel}.${assertion.type}(目标执行器不支持 mocks,正向工具证据不可复现)`);
733
+ continue;
734
+ }
735
+ kept.push(assertion);
736
+ }
737
+ return kept;
738
+ }
739
+ function stripInvalidMockHitAssertions(assertions, mockKeys, label, stripped) {
740
+ const kept = [];
741
+ for (const [index, assertion] of assertions.entries()) {
742
+ const assertionLabel = `${label}[${index}]`;
743
+ if (assertion?.type === 'assert-set' && Array.isArray(assertion.children)) {
744
+ const children = stripInvalidMockHitAssertions(assertion.children, mockKeys, `${assertionLabel}.children`, stripped);
745
+ if (children.length === 0) {
746
+ stripped.push(`${assertionLabel}.assert-set(没有可执行子断言)`);
747
+ continue;
748
+ }
749
+ assertion.children = children;
750
+ kept.push(assertion);
751
+ continue;
752
+ }
753
+ if (assertion?.type === 'mock_hit'
754
+ && typeof assertion.value === 'string'
755
+ && !mockKeys.has(assertion.value)) {
756
+ stripped.push(`${assertionLabel}.mock_hit(未引用实际 mock:${assertion.value})`);
757
+ continue;
758
+ }
759
+ kept.push(assertion);
760
+ }
761
+ return kept;
762
+ }
763
+ function environmentAsPromptContext(environment) {
764
+ if (!environment || typeof environment !== 'object' || Array.isArray(environment)) {
765
+ return null;
766
+ }
767
+ const env = environment;
768
+ const lines = ['题设环境声明(仅作上下文,不会在 cwd 物化):'];
769
+ if (Array.isArray(env.cli_available)
770
+ && env.cli_available.every((item) => typeof item === 'string' && item.length > 0)) {
771
+ lines.push(`- 可用 CLI:${env.cli_available.join('、')}`);
772
+ }
773
+ if (Array.isArray(env.files_available)
774
+ && env.files_available.every((item) => typeof item === 'string' && item.length > 0)) {
775
+ lines.push(`- 题设引用路径(未物化):${env.files_available.join('、')}`);
776
+ }
777
+ if (typeof env.notes === 'string' && env.notes.trim()) {
778
+ lines.push(`- 说明:${env.notes.trim()}`);
779
+ }
780
+ return lines.length > 1 ? lines.join('\n') : null;
781
+ }
659
782
  function isAsciiTokenLike(v) {
660
783
  if (typeof v !== 'string')
661
784
  return false;
@@ -719,6 +842,40 @@ export function sanitizeGeneratedSamples(samples, opts = {}) {
719
842
  stripped.push(`samples[${i}].tripwire (${typeof s.tripwire})`);
720
843
  delete s.tripwire;
721
844
  }
845
+ if (opts.mockless) {
846
+ if (Array.isArray(s.mocks) && s.mocks.length > 0) {
847
+ stripped.push(`samples[${i}].mocks(目标执行器不支持工具调用拦截)`);
848
+ }
849
+ delete s.mocks;
850
+ if (s.mocksStrict !== undefined) {
851
+ stripped.push(`samples[${i}].mocksStrict(无 mocks)`);
852
+ }
853
+ delete s.mocksStrict;
854
+ if (s.environment !== undefined && !opts.preserveMocklessEnvironment) {
855
+ const environmentContext = opts.migrateMocklessEnvironment
856
+ ? environmentAsPromptContext(s.environment)
857
+ : null;
858
+ if (environmentContext) {
859
+ const existingContext = typeof s.context === 'string' ? s.context.trim() : '';
860
+ s.context = existingContext
861
+ ? `${existingContext}\n\n${environmentContext}`
862
+ : environmentContext;
863
+ stripped.push(`samples[${i}].environment(已迁移到题设 context,未物化)`);
864
+ }
865
+ else {
866
+ stripped.push(`samples[${i}].environment(未物化的环境声明不能充当 fixture)`);
867
+ }
868
+ }
869
+ if (!opts.preserveMocklessEnvironment) {
870
+ delete s.environment;
871
+ }
872
+ if (Array.isArray(s.assertions)) {
873
+ s.assertions = stripMocklessPositiveToolAssertions(s.assertions, `samples[${i}].assertions`, stripped);
874
+ if (s.assertions.length === 0) {
875
+ delete s.assertions;
876
+ }
877
+ }
878
+ }
722
879
  // assertions 校验:loader 会拒掉两类无效断言 — 在 generator boundary 提前 strip,
723
880
  // 避免落盘的 sample 跑不动:
724
881
  // 1. tools_called / tools_not_called 的 values 必须非空
@@ -843,6 +1000,12 @@ export function sanitizeGeneratedSamples(samples, opts = {}) {
843
1000
  delete s.mocks;
844
1001
  }
845
1002
  }
1003
+ if (Array.isArray(s.assertions)) {
1004
+ s.assertions = stripInvalidMockHitAssertions(s.assertions, sampleMockReferenceKeys(s.mocks), `samples[${i}].assertions`, stripped);
1005
+ if (s.assertions.length === 0) {
1006
+ delete s.assertions;
1007
+ }
1008
+ }
846
1009
  // mocksStrict 兜底:有 mocks 时强制 true。
847
1010
  // SYSTEM_PROMPT 已要求 LLM 必填,但偶尔 LLM 漏填 — 在 generator boundary 修掉,
848
1011
  // 避免运行时 mock 未命中透传到真 shell(报 mcporter not found 等噪声错误)。
@@ -34,6 +34,8 @@ export interface FixSamplesOptions {
34
34
  }>;
35
35
  model: string;
36
36
  maxAttemptsPerSample?: number;
37
+ /** Target executor cannot intercept tool calls; strip mocks and dependent positive evidence. */
38
+ mockless?: boolean;
37
39
  }
38
40
  export interface FixSamplesResult {
39
41
  samples: Record<string, unknown>[];