oh-my-knowledge 0.41.0 → 0.42.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 +7 -2
- package/README.zh.md +7 -2
- package/dist/analysis/report-diagnostics.d.ts +8 -1
- package/dist/analysis/report-diagnostics.js +82 -1
- package/dist/assets/agent-skills/omk/references/commands.md +1 -0
- package/dist/authoring/evolver.d.ts +3 -14
- package/dist/authoring/evolver.js +1 -52
- package/dist/authoring/generator.d.ts +24 -0
- package/dist/authoring/generator.js +33 -6
- package/dist/cli/commands/eval/index.d.ts +1 -0
- package/dist/cli/commands/eval/index.js +40 -5
- package/dist/cli/commands/init.js +10 -7
- package/dist/cli/lib/i18n-dict/init.d.ts +1 -1
- package/dist/cli/lib/i18n-dict/init.js +14 -11
- package/dist/cli/lib/i18n-dict/run.d.ts +1 -1
- package/dist/cli/lib/i18n-dict/run.js +4 -0
- package/dist/cli/lib/parse-run-config.d.ts +3 -0
- package/dist/eval-core/evaluation-job.d.ts +2 -1
- package/dist/eval-core/evaluation-job.js +2 -1
- package/dist/eval-core/evaluation-reporting.js +7 -3
- package/dist/eval-core/holdout.d.ts +66 -0
- package/dist/eval-core/holdout.js +118 -0
- package/dist/eval-core/verdict.d.ts +44 -1
- package/dist/eval-core/verdict.js +175 -13
- package/dist/eval-workflows/evaluation-pipeline/report-finalize.js +19 -1
- package/dist/eval-workflows/evaluation-pipeline/run-state.d.ts +2 -1
- package/dist/eval-workflows/evaluation-pipeline/run-state.js +2 -1
- package/dist/eval-workflows/evaluation-pipeline.d.ts +3 -1
- package/dist/eval-workflows/evaluation-pipeline.js +2 -1
- package/dist/eval-workflows/run-evaluation.d.ts +5 -2
- package/dist/eval-workflows/run-evaluation.js +8 -5
- package/dist/inputs/eval-config.js +6 -0
- package/dist/renderer/summary.js +36 -3
- package/dist/types/eval.d.ts +7 -0
- package/dist/types/report.d.ts +49 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -23,11 +23,15 @@ omk init demo && cd demo
|
|
|
23
23
|
omk eval --control code-review-v1 --treatment code-review-v2
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
Runs out of the box — no edits needed first. `omk init` scaffolds two skill variants and three sample cases; `omk eval` runs the controlled A/B and opens an HTML report with a one-line verdict in about five minutes. Once it runs, swap in your own skills and cases.
|
|
27
|
+
|
|
28
|
+
Prerequisite: the default executor and judge use the `claude` CLI — install and log in first (see [Requirements](#requirements)); to use another model or run offline (no API key) see [executors](docs/reference/executors.md).
|
|
29
|
+
|
|
30
|
+
> The first run has only 3 cases, so the verdict will usually be `UNDERPOWERED` (insufficient data) — that's a normal starting point, not an error; grow to ~20+ cases before trusting a ship/no-ship call.
|
|
27
31
|
|
|
28
32
|
> The CLI notifies you when a newer version is available (at most once per 20h); set `OMK_SKIP_UPDATE_CHECK=1` to silence it permanently.
|
|
29
33
|
|
|
30
|
-
Walkthrough: [5-minute quickstart guide](docs/quickstart-skill-eval.md) (recommended for first-time users).
|
|
34
|
+
Walkthrough: [5-minute quickstart guide](docs/quickstart-skill-eval.md) (recommended for first-time users). More runnable examples (A/B, offline executor, batch, evolve, agent, RAG) live in the repo's [example gallery](https://github.com/lizhiyao/oh-my-knowledge/tree/main/examples).
|
|
31
35
|
|
|
32
36
|
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
37
|
|
|
@@ -122,6 +126,7 @@ The full docs are published at **[oh-my-knowledge.pages.dev](https://oh-my-knowl
|
|
|
122
126
|
- **[Executors](docs/reference/executors.md)** & **[artifact layout](docs/reference/artifact-layout.md)** — built-in / custom executors; how `variant` resolves to an artifact + runtime context
|
|
123
127
|
- **[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)
|
|
124
128
|
- **[Quickstart](docs/quickstart-skill-eval.md)** — first-time five-minute walkthrough
|
|
129
|
+
- **[Example gallery](https://github.com/lizhiyao/oh-my-knowledge/tree/main/examples)** — a set of runnable examples in the repo, arranged simplest-to-richest
|
|
125
130
|
- **[Sample design spec](docs/specs/sample-design-spec.md)** — capability / construct / provenance metadata; industry-gap mapping
|
|
126
131
|
- **[Statistical rigor](docs/explanation/statistical-rigor.md)** — why bootstrap CI / α / length-debias / saturation matter
|
|
127
132
|
- **[Comparison with 7 tools](docs/reference/comparison.md)** — 25+ dimensions across promptfoo / DeepEval / RAGAS / OpenAI Evals / LangSmith / lm-eval-harness / inspect-ai
|
package/README.zh.md
CHANGED
|
@@ -23,11 +23,15 @@ omk init demo && cd demo
|
|
|
23
23
|
omk eval --control code-review-v1 --treatment code-review-v2
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
开箱即跑:`omk init` 脚手架好两版 skill 和三条评测用例,不用先改任何文件,`omk eval` 跑控制变量 A/B,约 5 分钟出 HTML 报告 + 一行 verdict;跑通后再把 skill 和用例换成你自己的。
|
|
27
|
+
|
|
28
|
+
前置:默认执行器与评委用 `claude` CLI,需先安装并登录(见[系统要求](#系统要求));想用别的模型或离线跑(无需 API key)见[执行器](docs/zh/reference/executors.md)。
|
|
29
|
+
|
|
30
|
+
> 首跑只有 3 条用例,verdict 多半是「数据不足(UNDERPOWERED)」——这是正常起点而非出错;把用例加到约 20 条以上,再看「可发布」结论。
|
|
27
31
|
|
|
28
32
|
> 命令行有新版本时会自动提示(每 20 小时最多一次);想永久关闭该提醒,设环境变量 `OMK_SKIP_UPDATE_CHECK=1` 即可。
|
|
29
33
|
|
|
30
|
-
手把手教程:[5 分钟快速上手](docs/zh/quickstart-skill-eval.md)
|
|
34
|
+
手把手教程:[5 分钟快速上手](docs/zh/quickstart-skill-eval.md)(推荐第一次跑评测的用户)。更多可跑示例(A/B、离线执行器、batch、evolve、agent、RAG)见仓库的[示例画廊](https://github.com/lizhiyao/oh-my-knowledge/tree/main/examples)。
|
|
31
35
|
|
|
32
36
|
深入:[为谁、解决什么](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
37
|
|
|
@@ -122,6 +126,7 @@ RAG 专项评测请看 RAGAS(独立 niche,跟 omk 互补)。完整对比
|
|
|
122
126
|
- **[执行器](docs/zh/reference/executors.md)** & **[artifact 布局](docs/zh/reference/artifact-layout.md)** —— 内置 / 自定义执行器;variant 如何解析为 artifact + runtime context
|
|
123
127
|
- **[操作指南](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)
|
|
124
128
|
- **[快速上手](docs/zh/quickstart-skill-eval.md)** —— 第一次跑评测的 5 分钟教程
|
|
129
|
+
- **[示例画廊](https://github.com/lizhiyao/oh-my-knowledge/tree/main/examples)** —— 仓库里一组可直接跑的示例,按由简到全排成上手路径
|
|
125
130
|
- **[用例设计规范](docs/zh/specs/sample-design-spec.md)** —— capability / construct / provenance 元数据;行业 gap 映射
|
|
126
131
|
- **[统计严谨性](docs/zh/explanation/statistical-rigor.md)** —— 为什么 Bootstrap CI / α / 长度去偏 / 饱和曲线重要
|
|
127
132
|
- **[7 工具对比](docs/zh/reference/comparison.md)** —— promptfoo / DeepEval / RAGAS / OpenAI Evals / LangSmith / lm-eval-harness / inspect-ai 等 25+ 维度横评
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Auto-analysis: detect patterns and generate insights from evaluation results.
|
|
3
3
|
*/
|
|
4
|
-
import type { Report, AnalysisResult, Sample, SampleQualityAggregate, Lang } from '../types/index.js';
|
|
4
|
+
import type { Report, AnalysisResult, Sample, SampleQualityAggregate, Representativeness, Lang } from '../types/index.js';
|
|
5
5
|
/** opts for `analyzeResults`. Optional because most older callers don't have
|
|
6
6
|
* samples in scope; new callers (evaluation-pipeline / evolver) pass them in to
|
|
7
7
|
* populate `analysis.sampleQuality`. */
|
|
@@ -30,4 +30,11 @@ export declare function analyzeResults(report: Report, opts?: AnalyzeResultsOpti
|
|
|
30
30
|
* participate in grading / judge / verdict. See docs/specs/sample-design-spec.md.
|
|
31
31
|
*/
|
|
32
32
|
export declare function buildSampleQualityAggregate(samples: Sample[]): SampleQualityAggregate;
|
|
33
|
+
/**
|
|
34
|
+
* Relative-balance / skew of the sample set. Pure function of the aggregate's
|
|
35
|
+
* distributions — no external "expected" denominator exists (capabilities are
|
|
36
|
+
* free-form), so it reports concentration (dominant bucket share) + the dominant
|
|
37
|
+
* label per dimension, not absolute coverage. Diagnostic only.
|
|
38
|
+
*/
|
|
39
|
+
export declare function buildRepresentativeness(aggregate: SampleQualityAggregate): Representativeness;
|
|
33
40
|
export declare function generateAnalysisSummary(report: Report, lang?: Lang): string | undefined;
|
|
@@ -144,7 +144,7 @@ export function buildSampleQualityAggregate(samples) {
|
|
|
144
144
|
rubricCount++;
|
|
145
145
|
}
|
|
146
146
|
}
|
|
147
|
-
|
|
147
|
+
const aggregate = {
|
|
148
148
|
capabilityCoverage,
|
|
149
149
|
difficultyDistribution,
|
|
150
150
|
constructDistribution,
|
|
@@ -155,6 +155,56 @@ export function buildSampleQualityAggregate(samples) {
|
|
|
155
155
|
sampleCountWithConstruct: withConstruct,
|
|
156
156
|
sampleCountWithProvenance: withProvenance,
|
|
157
157
|
};
|
|
158
|
+
aggregate.representativeness = buildRepresentativeness(aggregate);
|
|
159
|
+
return aggregate;
|
|
160
|
+
}
|
|
161
|
+
/** Largest entry of a `label → count` map, as `[label, share]` over a given total.
|
|
162
|
+
* Returns `[undefined, 0]` when the map is empty or total ≤ 0. */
|
|
163
|
+
function dominantShare(counts, total) {
|
|
164
|
+
let label;
|
|
165
|
+
let max = 0;
|
|
166
|
+
for (const [k, v] of Object.entries(counts)) {
|
|
167
|
+
if (v > max) {
|
|
168
|
+
max = v;
|
|
169
|
+
label = k;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return total > 0 ? [label, max / total] : [undefined, 0];
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Relative-balance / skew of the sample set. Pure function of the aggregate's
|
|
176
|
+
* distributions — no external "expected" denominator exists (capabilities are
|
|
177
|
+
* free-form), so it reports concentration (dominant bucket share) + the dominant
|
|
178
|
+
* label per dimension, not absolute coverage. Diagnostic only.
|
|
179
|
+
*/
|
|
180
|
+
export function buildRepresentativeness(aggregate) {
|
|
181
|
+
const capTotal = Object.values(aggregate.capabilityCoverage).reduce((a, b) => a + b, 0);
|
|
182
|
+
const [dominantCapability, capabilityConcentration] = dominantShare(aggregate.capabilityCoverage, capTotal);
|
|
183
|
+
// difficulty / construct concentration over *declared* samples only — `unspecified`
|
|
184
|
+
// is "didn't say", not a bucket the set is skewed toward.
|
|
185
|
+
const declaredDifficulty = {
|
|
186
|
+
easy: aggregate.difficultyDistribution.easy,
|
|
187
|
+
medium: aggregate.difficultyDistribution.medium,
|
|
188
|
+
hard: aggregate.difficultyDistribution.hard,
|
|
189
|
+
};
|
|
190
|
+
const diffTotal = declaredDifficulty.easy + declaredDifficulty.medium + declaredDifficulty.hard;
|
|
191
|
+
const [dominantDifficulty, difficultyConcentration] = dominantShare(declaredDifficulty, diffTotal);
|
|
192
|
+
const declaredConstruct = {};
|
|
193
|
+
for (const [k, v] of Object.entries(aggregate.constructDistribution)) {
|
|
194
|
+
if (k !== 'unspecified')
|
|
195
|
+
declaredConstruct[k] = v;
|
|
196
|
+
}
|
|
197
|
+
const consTotal = Object.values(declaredConstruct).reduce((a, b) => a + b, 0);
|
|
198
|
+
const [dominantConstruct, constructConcentration] = dominantShare(declaredConstruct, consTotal);
|
|
199
|
+
return {
|
|
200
|
+
capabilityCount: Object.keys(aggregate.capabilityCoverage).length,
|
|
201
|
+
capabilityConcentration,
|
|
202
|
+
...(dominantCapability ? { dominantCapability } : {}),
|
|
203
|
+
difficultyConcentration,
|
|
204
|
+
...(dominantDifficulty ? { dominantDifficulty: dominantDifficulty } : {}),
|
|
205
|
+
constructConcentration,
|
|
206
|
+
...(dominantConstruct ? { dominantConstruct } : {}),
|
|
207
|
+
};
|
|
158
208
|
}
|
|
159
209
|
export function generateAnalysisSummary(report, lang = 'zh') {
|
|
160
210
|
const variants = report.meta?.variants || [];
|
|
@@ -383,12 +433,43 @@ export function generateAnalysisSummary(report, lang = 'zh') {
|
|
|
383
433
|
? `【综合洞察】${synthesis.join(';')}。`
|
|
384
434
|
: `【Synthesis】${synthesis.join('; ')}.`);
|
|
385
435
|
}
|
|
436
|
+
// ── Sample-composition skew ── flags over-representation ("70% are easy") so the
|
|
437
|
+
// reader knows the set may not be representative of the real task distribution.
|
|
438
|
+
// Diagnostic only — never gates the verdict. N≥10 guard mirrors capability_thin.
|
|
439
|
+
const rep = report.analysis?.sampleQuality?.representativeness;
|
|
440
|
+
const sampleCount = report.meta?.sampleCount ?? 0;
|
|
441
|
+
if (rep && sampleCount >= 10) {
|
|
442
|
+
const skews = [];
|
|
443
|
+
if (rep.difficultyConcentration > SKEW_CONCENTRATION_BAND && rep.dominantDifficulty) {
|
|
444
|
+
skews.push(lang === 'zh'
|
|
445
|
+
? `难度 ${(rep.difficultyConcentration * 100).toFixed(0)}% 集中在 ${rep.dominantDifficulty}`
|
|
446
|
+
: `${(rep.difficultyConcentration * 100).toFixed(0)}% of declared difficulty is ${rep.dominantDifficulty}`);
|
|
447
|
+
}
|
|
448
|
+
if (rep.capabilityConcentration > SKEW_CONCENTRATION_BAND && rep.dominantCapability) {
|
|
449
|
+
skews.push(lang === 'zh'
|
|
450
|
+
? `能力标签 ${(rep.capabilityConcentration * 100).toFixed(0)}% 集中在 ${rep.dominantCapability}`
|
|
451
|
+
: `${(rep.capabilityConcentration * 100).toFixed(0)}% of capability tags are ${rep.dominantCapability}`);
|
|
452
|
+
}
|
|
453
|
+
if (rep.constructConcentration > SKEW_CONCENTRATION_BAND && rep.dominantConstruct) {
|
|
454
|
+
skews.push(lang === 'zh'
|
|
455
|
+
? `construct ${(rep.constructConcentration * 100).toFixed(0)}% 集中在 ${rep.dominantConstruct}`
|
|
456
|
+
: `${(rep.constructConcentration * 100).toFixed(0)}% of declared construct is ${rep.dominantConstruct}`);
|
|
457
|
+
}
|
|
458
|
+
if (skews.length > 0) {
|
|
459
|
+
lines.push(lang === 'zh'
|
|
460
|
+
? `【用例构成】偏斜:${skews.join(';')}——补充其它维度可提升代表性(构成提示,不影响 verdict)。`
|
|
461
|
+
: `【Sample composition】Skewed: ${skews.join('; ')} — adding other dimensions improves representativeness (informational; does not affect the verdict).`);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
386
464
|
// Caveats and recommendations are handled by the issues table below,
|
|
387
465
|
// so the summary focuses only on verdict + differentiators + synthesis.
|
|
388
466
|
if (lines.length === 0)
|
|
389
467
|
return undefined;
|
|
390
468
|
return lines.join('\n');
|
|
391
469
|
}
|
|
470
|
+
/** Dominant-bucket share above which the sample set is flagged as skewed. Pragmatic
|
|
471
|
+
* default — 60% of declared samples in one bucket is a clear over-representation. */
|
|
472
|
+
const SKEW_CONCENTRATION_BAND = 0.6;
|
|
392
473
|
const AGENT_ASSERTION_TYPES = new Set([
|
|
393
474
|
'tools_called',
|
|
394
475
|
'tools_not_called',
|
|
@@ -81,6 +81,7 @@ omk eval [flags]
|
|
|
81
81
|
- `--executor` `option`:执行器:claude / claude-sdk / codex / codex-sdk / openai-api / gemini / 自定义命令(默认 claude)。
|
|
82
82
|
- `--global` `boolean`:报告写全局 ~/.oh-my-knowledge/reports,而非项目 .omk/
|
|
83
83
|
- `--gold-dir` `option`:gold dataset 目录
|
|
84
|
+
- `--holdout-ratio` `option`:留出比例 0-1(如 0.3);切出 holdout 子集,对比 train/holdout 综合分检测过拟合
|
|
84
85
|
- `--judge-models` `option`:评委配置,格式 executor:model[,...],例 claude:haiku 或 claude:opus,openai-api:gpt-4o(≥ 2 个 = ensemble)。默认 <executor>:haiku。
|
|
85
86
|
- `--judge-repeat` `option`:每个 dim 评 N 次
|
|
86
87
|
- `--lang` `option` (默认 `zh`):输出语言 zh|en,优先级 CLI > OMK_LANG env > zh。
|
|
@@ -16,14 +16,11 @@ interface WeakSample {
|
|
|
16
16
|
};
|
|
17
17
|
}
|
|
18
18
|
export declare function extractWeakSamples(report: Report, variantKey: string, count?: number, sampleIdFilter?: Set<string>): WeakSample[];
|
|
19
|
-
/** A train / holdout partition of a sample set. */
|
|
20
|
-
interface HoldoutSplit {
|
|
21
|
-
trainIds: Set<string>;
|
|
22
|
-
holdoutIds: Set<string>;
|
|
23
|
-
}
|
|
24
19
|
/** A train / val / test partition. `val` drives the accept decision; `test` is
|
|
25
20
|
* locked — never seen during the loop, read once at the end for an unbiased
|
|
26
|
-
* generalization score.
|
|
21
|
+
* generalization score. Two-way holdout (`splitHoldout`), the stride picker
|
|
22
|
+
* (`pickByStride`), `MIN_HOLDOUT_SUBSET`, and `subsetCompositeScore` live in
|
|
23
|
+
* `src/eval-core/holdout.ts` so `omk eval --holdout-ratio` reuses them. */
|
|
27
24
|
interface TrainValTestSplit {
|
|
28
25
|
trainIds: Set<string>;
|
|
29
26
|
valIds: Set<string>;
|
|
@@ -34,14 +31,6 @@ interface TrainValTestSplit {
|
|
|
34
31
|
* every candidate. Under that floor evolve degrades to the point-estimate accept
|
|
35
32
|
* and flags `gate.underpowered`. */
|
|
36
33
|
export declare const MIN_GATE_SAMPLES = 8;
|
|
37
|
-
/**
|
|
38
|
-
* Deterministically split sample ids into train / holdout by `ratio` (fraction
|
|
39
|
-
* held out). Holdout members are picked at an even stride so the partition is
|
|
40
|
-
* representative of the ordering, and the split is stable across rounds and runs
|
|
41
|
-
* (no RNG). Returns null when ratio ≤ 0 or either side would drop below
|
|
42
|
-
* MIN_HOLDOUT_SUBSET — the caller then scores on the full set.
|
|
43
|
-
*/
|
|
44
|
-
export declare function splitHoldout(sampleIds: string[], ratio: number): HoldoutSplit | null;
|
|
45
34
|
/**
|
|
46
35
|
* Deterministically split sample ids into train / val / test. `val` is carved
|
|
47
36
|
* first at an even stride; `test` is carved at an even stride over what remains,
|
|
@@ -8,7 +8,7 @@ import { projectReportsDir, globalReportsDir } from '../eval-core/measurement-di
|
|
|
8
8
|
import { analyzeResults } from '../analysis/report-diagnostics.js';
|
|
9
9
|
import { loadSamples } from '../inputs/load-samples.js';
|
|
10
10
|
import { hashArtifactSource } from '../inputs/content-hash.js';
|
|
11
|
-
import {
|
|
11
|
+
import { MIN_HOLDOUT_SUBSET, pickByStride, splitHoldout, subsetCompositeScore } from '../eval-core/holdout.js';
|
|
12
12
|
import { bootstrapDiffCI, DEFAULT_BOOTSTRAP_ALPHA, DEFAULT_BOOTSTRAP_SAMPLES } from '../eval-core/bootstrap.js';
|
|
13
13
|
import { fixSamples } from './sample-fixer.js';
|
|
14
14
|
const IMPROVE_SYSTEM_PROMPT = `你是一个 AI 提示词改进专家。你的任务是分析评测结果中的薄弱环节,针对性地改进 skill(系统提示词),使其在评测中获得更高的分数。
|
|
@@ -165,43 +165,11 @@ export function extractWeakSamples(report, variantKey, count = 5, sampleIdFilter
|
|
|
165
165
|
.sort((a, b) => a.compositeScore - b.compositeScore)
|
|
166
166
|
.slice(0, count);
|
|
167
167
|
}
|
|
168
|
-
/** Below this many samples on any side, a split is too small to be meaningful —
|
|
169
|
-
* evolve falls back to full-set scoring and warns. */
|
|
170
|
-
const MIN_HOLDOUT_SUBSET = 3;
|
|
171
168
|
/** Below this many decision (val) samples the bootstrap diff CI almost never
|
|
172
169
|
* excludes 0 for realistic effect sizes, so the significance gate would reject
|
|
173
170
|
* every candidate. Under that floor evolve degrades to the point-estimate accept
|
|
174
171
|
* and flags `gate.underpowered`. */
|
|
175
172
|
export const MIN_GATE_SAMPLES = 8;
|
|
176
|
-
/** Pick `count` ids at an even stride across `ids` (deterministic, no RNG) so the
|
|
177
|
-
* picked subset is representative of the ordering and stable across rounds/runs. */
|
|
178
|
-
function pickByStride(ids, count) {
|
|
179
|
-
const picked = new Set();
|
|
180
|
-
if (count <= 0)
|
|
181
|
-
return picked;
|
|
182
|
-
const stride = ids.length / count;
|
|
183
|
-
for (let k = 0; k < count; k++)
|
|
184
|
-
picked.add(ids[Math.floor(k * stride)]);
|
|
185
|
-
return picked;
|
|
186
|
-
}
|
|
187
|
-
/**
|
|
188
|
-
* Deterministically split sample ids into train / holdout by `ratio` (fraction
|
|
189
|
-
* held out). Holdout members are picked at an even stride so the partition is
|
|
190
|
-
* representative of the ordering, and the split is stable across rounds and runs
|
|
191
|
-
* (no RNG). Returns null when ratio ≤ 0 or either side would drop below
|
|
192
|
-
* MIN_HOLDOUT_SUBSET — the caller then scores on the full set.
|
|
193
|
-
*/
|
|
194
|
-
export function splitHoldout(sampleIds, ratio) {
|
|
195
|
-
if (!(ratio > 0) || sampleIds.length === 0)
|
|
196
|
-
return null;
|
|
197
|
-
const holdoutCount = Math.round(sampleIds.length * ratio);
|
|
198
|
-
const trainCount = sampleIds.length - holdoutCount;
|
|
199
|
-
if (holdoutCount < MIN_HOLDOUT_SUBSET || trainCount < MIN_HOLDOUT_SUBSET)
|
|
200
|
-
return null;
|
|
201
|
-
const holdoutIds = pickByStride(sampleIds, holdoutCount);
|
|
202
|
-
const trainIds = new Set(sampleIds.filter((id) => !holdoutIds.has(id)));
|
|
203
|
-
return { trainIds, holdoutIds };
|
|
204
|
-
}
|
|
205
173
|
/**
|
|
206
174
|
* Deterministically split sample ids into train / val / test. `val` is carved
|
|
207
175
|
* first at an even stride; `test` is carved at an even stride over what remains,
|
|
@@ -223,25 +191,6 @@ export function splitTrainValTest(sampleIds, valRatio, testRatio) {
|
|
|
223
191
|
const trainIds = new Set(sampleIds.filter((id) => !valIds.has(id) && !testIds.has(id)));
|
|
224
192
|
return { trainIds, valIds, testIds };
|
|
225
193
|
}
|
|
226
|
-
/**
|
|
227
|
-
* Mean composite over the subset of a report's results whose sample_id is in
|
|
228
|
-
* `ids`, using the same aggregation as the full-run summary
|
|
229
|
-
* (`buildVariantSummary`) so train / holdout scores stay comparable to the
|
|
230
|
-
* headline composite. Returns 0 when the subset has no scorable entries.
|
|
231
|
-
*/
|
|
232
|
-
function subsetCompositeScore(report, variantKey, ids) {
|
|
233
|
-
const entries = [];
|
|
234
|
-
for (const r of report.results) {
|
|
235
|
-
if (!ids.has(r.sample_id))
|
|
236
|
-
continue;
|
|
237
|
-
const v = r.variants[variantKey];
|
|
238
|
-
if (v)
|
|
239
|
-
entries.push(v);
|
|
240
|
-
}
|
|
241
|
-
if (entries.length === 0)
|
|
242
|
-
return 0;
|
|
243
|
-
return buildVariantSummary(entries).avgCompositeScore ?? 0;
|
|
244
|
-
}
|
|
245
194
|
/**
|
|
246
195
|
* Per-sample composite scores over the subset of a report's results whose
|
|
247
196
|
* sample_id is in `ids`, in result order. Feeds `bootstrapDiffCI` for the
|
|
@@ -31,6 +31,30 @@ export declare function generateSamples({ skillContent, count, model, executorNa
|
|
|
31
31
|
costUSD: number;
|
|
32
32
|
}>;
|
|
33
33
|
type TraceSignalItem = Pick<ObservationInboxItem, 'skillName' | 'signalType' | 'signalSubtype' | 'severity' | 'evidence' | 'messageWindow' | 'occurrences'>;
|
|
34
|
+
export interface StratifiedTraceSignal extends TraceSignalItem {
|
|
35
|
+
/** Share of total occurrences across all signals (0-1) — drives proportional
|
|
36
|
+
* sample allocation in the prompt. */
|
|
37
|
+
weight: number;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Rank trace signals by frequency and annotate each with its share of the total
|
|
41
|
+
* occurrences. Lets `omk sample --from-traces` allocate samples *proportional to
|
|
42
|
+
* how often a failure actually happened* instead of a flat "1-2 per signal" — a
|
|
43
|
+
* failure seen 100× deserves more regression coverage than one seen twice.
|
|
44
|
+
*
|
|
45
|
+
* Deliberately does NOT re-merge signals here. The observation inbox is the only
|
|
46
|
+
* source of these items and already aggregates `occurrences` by the FULL identity
|
|
47
|
+
* — `skillName + cwd + sourceKind + signalType + signalSubtype + evidence`
|
|
48
|
+
* (`inbox.ts` `keyFor`). Re-merging on a narrower key (e.g. type+subtype+evidence)
|
|
49
|
+
* would fold *different skills / cwd* with the same failure shape into one entry,
|
|
50
|
+
* mis-attributing the summed occurrences to the first skill and skewing the
|
|
51
|
+
* regenerated distribution. So we trust the upstream dedup and only sort + weight.
|
|
52
|
+
*
|
|
53
|
+
* Does NOT fix the underlying selection bias (traces only capture *failures*),
|
|
54
|
+
* which is why the `omk sample --from-traces` draft warning still stands — this
|
|
55
|
+
* only makes the within-failure distribution representative of frequency.
|
|
56
|
+
*/
|
|
57
|
+
export declare function stratifyTraceSignals(items: TraceSignalItem[]): StratifiedTraceSignal[];
|
|
34
58
|
/**
|
|
35
59
|
* Build the generation prompt for `omk sample --from-traces`. Renders each
|
|
36
60
|
* observation-inbox signal (evidence + message window) into a section and asks
|
|
@@ -465,10 +465,34 @@ async function finalizeSamples(samples, costUSD, skillContent) {
|
|
|
465
465
|
const TRACE_GEN_INSTRUCTIONS = `下面给出的不是 skill,而是从生产会话 trace 中观测到的失败 / 异常信号。请为这些信号生成评测用例(eval samples),使评测能复现并守住这些失败模式——把线上真实发生过的问题沉淀成回归用例。
|
|
466
466
|
|
|
467
467
|
要求:
|
|
468
|
-
-
|
|
468
|
+
- 按各信号标注的「占比」分配用例数:高频信号多生成、低频少生成,让用例集覆盖线上失败的真实频次分布(高占比信号 2-3 条,低占比 1 条即可);信号若是噪声 / 证据不足 / 无法复现,跳过它,不要硬凑。
|
|
469
469
|
- prompt 要还原触发该信号的场景(自然语言任务),不要直接复述证据文本。
|
|
470
470
|
- 断言优先用 mock_hit / tools_called / tools_not_called / tool_input_contains 精确锚定失败步骤,再用 contains 兜底;按「原子型」配比处理(无需工作流编号步骤),除非证据明显是多步流程。
|
|
471
471
|
- 不要在输出里说明判断过程,直接输出 JSON 数组。`;
|
|
472
|
+
/**
|
|
473
|
+
* Rank trace signals by frequency and annotate each with its share of the total
|
|
474
|
+
* occurrences. Lets `omk sample --from-traces` allocate samples *proportional to
|
|
475
|
+
* how often a failure actually happened* instead of a flat "1-2 per signal" — a
|
|
476
|
+
* failure seen 100× deserves more regression coverage than one seen twice.
|
|
477
|
+
*
|
|
478
|
+
* Deliberately does NOT re-merge signals here. The observation inbox is the only
|
|
479
|
+
* source of these items and already aggregates `occurrences` by the FULL identity
|
|
480
|
+
* — `skillName + cwd + sourceKind + signalType + signalSubtype + evidence`
|
|
481
|
+
* (`inbox.ts` `keyFor`). Re-merging on a narrower key (e.g. type+subtype+evidence)
|
|
482
|
+
* would fold *different skills / cwd* with the same failure shape into one entry,
|
|
483
|
+
* mis-attributing the summed occurrences to the first skill and skewing the
|
|
484
|
+
* regenerated distribution. So we trust the upstream dedup and only sort + weight.
|
|
485
|
+
*
|
|
486
|
+
* Does NOT fix the underlying selection bias (traces only capture *failures*),
|
|
487
|
+
* which is why the `omk sample --from-traces` draft warning still stands — this
|
|
488
|
+
* only makes the within-failure distribution representative of frequency.
|
|
489
|
+
*/
|
|
490
|
+
export function stratifyTraceSignals(items) {
|
|
491
|
+
const total = items.reduce((sum, it) => sum + (it.occurrences ?? 0), 0) || 1;
|
|
492
|
+
return items
|
|
493
|
+
.map((it) => ({ ...it, occurrences: it.occurrences ?? 0, weight: (it.occurrences ?? 0) / total }))
|
|
494
|
+
.sort((a, b) => b.occurrences - a.occurrences);
|
|
495
|
+
}
|
|
472
496
|
/**
|
|
473
497
|
* Build the generation prompt for `omk sample --from-traces`. Renders each
|
|
474
498
|
* observation-inbox signal (evidence + message window) into a section and asks
|
|
@@ -477,7 +501,9 @@ const TRACE_GEN_INSTRUCTIONS = `下面给出的不是 skill,而是从生产会
|
|
|
477
501
|
* judge prompt — so judge-prompt isolation is unaffected.
|
|
478
502
|
*/
|
|
479
503
|
export function buildSamplesFromTracesPrompt(items, count) {
|
|
480
|
-
|
|
504
|
+
// 先按频次分层(合并重复 + 算占比 + 降序),让模型按「占比」分配配额,而非每信号一刀切。
|
|
505
|
+
const stratified = stratifyTraceSignals(items);
|
|
506
|
+
const sections = stratified.map((it, i) => {
|
|
481
507
|
const ev = it.evidence ?? {};
|
|
482
508
|
const evLines = [
|
|
483
509
|
ev.tool && `工具: ${ev.tool}`,
|
|
@@ -491,14 +517,15 @@ export function buildSamplesFromTracesPrompt(items, count) {
|
|
|
491
517
|
? '\n上下文消息:\n' + [...it.messageWindow.before, ...it.messageWindow.event, ...it.messageWindow.after]
|
|
492
518
|
.map((m) => ` [${m.role}] ${m.snippet}`).join('\n')
|
|
493
519
|
: '';
|
|
494
|
-
|
|
520
|
+
const pct = (it.weight * 100).toFixed(0);
|
|
521
|
+
return `### 信号 ${i + 1}:${it.signalType} / ${it.signalSubtype}(严重度 ${it.severity},出现 ${it.occurrences} 次 · 占比 ${pct}%,skill: ${it.skillName})\n${evLines}${win}`;
|
|
495
522
|
}).join('\n\n---\n\n');
|
|
496
523
|
const countLine = typeof count === 'number'
|
|
497
|
-
? `共生成约 ${count}
|
|
498
|
-
: '
|
|
524
|
+
? `共生成约 ${count} 条评测用例,按各信号的「占比」分配配额(高频多、低频少),覆盖整体失败分布。`
|
|
525
|
+
: '按各信号「占比」分配:高频信号多生成、低频少生成,覆盖整体失败分布。';
|
|
499
526
|
return `${TRACE_GEN_INSTRUCTIONS}
|
|
500
527
|
|
|
501
|
-
## 观测到的失败信号(共 ${
|
|
528
|
+
## 观测到的失败信号(共 ${stratified.length} 个,已按出现频次降序)
|
|
502
529
|
|
|
503
530
|
${sections}
|
|
504
531
|
|
|
@@ -38,6 +38,7 @@ export default class Eval extends BaseCommand {
|
|
|
38
38
|
effort: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
39
39
|
'no-diagnostic': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
40
40
|
repeat: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
41
|
+
'holdout-ratio': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
41
42
|
'judge-repeat': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
42
43
|
bootstrap: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
43
44
|
'bootstrap-samples': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
@@ -41,10 +41,30 @@ function applyGateExitCode(code, values, lang) {
|
|
|
41
41
|
process.stderr.write(tCli('cli.run.report_only_gate_skipped', lang));
|
|
42
42
|
return 0;
|
|
43
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* 完整 report JSON 是**机器输出**:重定向 / 管道(`omk eval > r.json`、`| jq`)时吐到 stdout 供下游消费。
|
|
46
|
+
* 交互式 TTY 下报告已存盘、(默认)还起了 report server,再刷上千行 JSON 只会把 verdict 淹没在屏幕外 ——
|
|
47
|
+
* 故只在非 TTY(stdout 被重定向 / 管道)时 dump。dry-run 的 JSON 是用户显式索取的产物,不走此门控。
|
|
48
|
+
*/
|
|
49
|
+
function emitReportJson(report) {
|
|
50
|
+
if (!process.stdout.isTTY) {
|
|
51
|
+
console.log(JSON.stringify(report, null, 2));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* 给人读的 verdict 文案:stdout 是 TTY 时进 stdout(交互终端没有 JSON,verdict 就是答案),
|
|
56
|
+
* 否则进 stderr —— 与 emitReportJson 配对,保证非 TTY 的 stdout 是**纯 report JSON**,
|
|
57
|
+
* `omk eval | jq` / `> report.json` 不会被末尾拼上的人类文案噎住(否则 JSON.parse 直接失败)。
|
|
58
|
+
*/
|
|
59
|
+
function emitVerdictText(text) {
|
|
60
|
+
// 与 console.log 等价(对单个字符串 = write(text + '\n')),只切换目标流,逐字节保留既有文案。
|
|
61
|
+
const stream = process.stdout.isTTY ? process.stdout : process.stderr;
|
|
62
|
+
stream.write(text + '\n');
|
|
63
|
+
}
|
|
44
64
|
async function emitEvaluationVerdict(report, values, lang) {
|
|
45
65
|
const { computeVerdict, formatVerdictText } = await import('../../../eval-core/verdict.js');
|
|
46
66
|
const result = computeVerdict(report, verdictOptions(values));
|
|
47
|
-
|
|
67
|
+
emitVerdictText(formatVerdictText(result, { verbose: true, lang }));
|
|
48
68
|
await recordEvidenceSafely(report, result.level, values, lang);
|
|
49
69
|
return verdictPasses(result.level, result.headline) ? 0 : 1;
|
|
50
70
|
}
|
|
@@ -123,13 +143,13 @@ async function emitBatchVerdict(report, reportsDir, values, lang) {
|
|
|
123
143
|
const status = lang === 'zh'
|
|
124
144
|
? (failed === 0 ? '通过' : '未通过')
|
|
125
145
|
: (failed === 0 ? 'PASS' : 'FAIL');
|
|
126
|
-
|
|
146
|
+
emitVerdictText(tCli('cli.run.batch_verdict_header', lang, {
|
|
127
147
|
status,
|
|
128
148
|
passed,
|
|
129
149
|
total: results.length,
|
|
130
150
|
}));
|
|
131
151
|
for (const result of results) {
|
|
132
|
-
|
|
152
|
+
emitVerdictText(` ${result.verdict.level}: ${result.treatment} — ${result.verdict.headline}`);
|
|
133
153
|
}
|
|
134
154
|
return failed === 0 ? 0 : 1;
|
|
135
155
|
}
|
|
@@ -168,6 +188,13 @@ async function runEval(_args, flags, lang) {
|
|
|
168
188
|
process.stderr.write(tCli('cli.run.invalid_repeat', lang, { value: repeatRaw }));
|
|
169
189
|
}
|
|
170
190
|
const repeatCount = Math.max(1, Math.floor(parsedRepeat) || 1);
|
|
191
|
+
const holdoutRatioRaw = values['holdout-ratio'];
|
|
192
|
+
const parsedHoldoutRatio = holdoutRatioRaw !== undefined ? Number(holdoutRatioRaw) : (evalConfig?.holdoutRatio ?? 0);
|
|
193
|
+
if (holdoutRatioRaw !== undefined && (!Number.isFinite(parsedHoldoutRatio) || parsedHoldoutRatio <= 0 || parsedHoldoutRatio >= 1)) {
|
|
194
|
+
process.stderr.write(tCli('cli.run.invalid_holdout_ratio', lang, { value: holdoutRatioRaw }));
|
|
195
|
+
}
|
|
196
|
+
if (parsedHoldoutRatio > 0 && parsedHoldoutRatio < 1)
|
|
197
|
+
config.holdoutRatio = parsedHoldoutRatio;
|
|
171
198
|
const judgeRepeatRaw = values['judge-repeat'];
|
|
172
199
|
const parsedJudgeRepeat = judgeRepeatRaw !== undefined ? Number(judgeRepeatRaw) : (evalConfig?.judgeRepeat ?? 1);
|
|
173
200
|
if (judgeRepeatRaw !== undefined && (!Number.isFinite(parsedJudgeRepeat) || parsedJudgeRepeat < 1)) {
|
|
@@ -224,11 +251,12 @@ async function runEval(_args, flags, lang) {
|
|
|
224
251
|
}
|
|
225
252
|
},
|
|
226
253
|
});
|
|
227
|
-
console.log(JSON.stringify(report, null, 2));
|
|
228
254
|
if (isDryRunBatchReport(report)) {
|
|
255
|
+
console.log(JSON.stringify(report, null, 2));
|
|
229
256
|
console.log(tCli('cli.run.dry_run_no_scores', lang));
|
|
230
257
|
throw new CliExit(0);
|
|
231
258
|
}
|
|
259
|
+
emitReportJson(report);
|
|
232
260
|
if (filePath) {
|
|
233
261
|
await announceSavedReport({ report, filePath, reportsDir: config.outputDir, values, lang });
|
|
234
262
|
}
|
|
@@ -285,7 +313,7 @@ async function runEval(_args, flags, lang) {
|
|
|
285
313
|
}
|
|
286
314
|
}
|
|
287
315
|
}
|
|
288
|
-
|
|
316
|
+
emitReportJson(report);
|
|
289
317
|
if (filePath) {
|
|
290
318
|
await announceSavedReport({ report, filePath, reportsDir: config.outputDir, values, lang });
|
|
291
319
|
}
|
|
@@ -457,6 +485,13 @@ export default class Eval extends BaseCommand {
|
|
|
457
485
|
description: bilingual({ zh: '每个 sample 重复跑 N 次', en: 'Repeat each sample N times' }),
|
|
458
486
|
parse: integerStringParser('--repeat', { min: 1 }),
|
|
459
487
|
}),
|
|
488
|
+
'holdout-ratio': Flags.string({
|
|
489
|
+
description: bilingual({
|
|
490
|
+
zh: '留出比例 0-1(如 0.3);切出 holdout 子集,对比 train/holdout 综合分检测过拟合',
|
|
491
|
+
en: 'Holdout fraction 0-1 (e.g. 0.3); splits a holdout subset, compares train/holdout composite to flag overfitting',
|
|
492
|
+
}),
|
|
493
|
+
parse: numberStringParser('--holdout-ratio', { min: 0, max: 1 }),
|
|
494
|
+
}),
|
|
460
495
|
'judge-repeat': Flags.string({
|
|
461
496
|
description: bilingual({ zh: '每个 dim 评 N 次', en: 'Judge each dim N times' }),
|
|
462
497
|
parse: integerStringParser('--judge-repeat', { min: 1 }),
|
|
@@ -12,17 +12,21 @@ const INIT_OMK_GITIGNORE = `# omk 测量 bulk + doctor --fix 备份(项目本地
|
|
|
12
12
|
/reports/
|
|
13
13
|
/backups/
|
|
14
14
|
`;
|
|
15
|
+
// 脚手架用例必须过 omk 自身的断言合规校验(load-samples.ts Rule A),否则新用户照
|
|
16
|
+
// 快速开始跑的第一条 omk eval 会直接硬报错。约束:contains / not_contains 的 value
|
|
17
|
+
// 只能是单个 ASCII token(长度 [2,40]、无内部空白、无 CJK);多词 / 中文语义匹配一律
|
|
18
|
+
// 走 rubric 交评委判;regex pattern 不能含 CJK。改这里前先跑 `omk eval --dry-run`
|
|
19
|
+
// (非 lenient 合规 oracle)与 test/cli/init-scaffold-conformance 回归测试。
|
|
15
20
|
const INIT_SAMPLES = `[
|
|
16
21
|
{
|
|
17
22
|
"sample_id": "s001",
|
|
18
23
|
"prompt": "审查以下代码",
|
|
19
24
|
"context": "function authenticate(username, password) {\\n const query = \`SELECT * FROM users WHERE name='\${username}' AND pass='\${password}'\`;\\n return db.execute(query);\\n}",
|
|
20
|
-
"rubric": "应识别 SQL
|
|
25
|
+
"rubric": "应识别 SQL 注入风险,建议使用参数化查询;不应把这段代码判为安全无问题。",
|
|
21
26
|
"assertions": [
|
|
22
27
|
{ "type": "contains", "value": "SQL", "weight": 1 },
|
|
23
28
|
{ "type": "contains", "value": "injection", "weight": 1 },
|
|
24
|
-
{ "type": "regex", "pattern": "parameterized|prepared|placeholder|bind", "flags": "i", "weight": 0.5 }
|
|
25
|
-
{ "type": "not_contains", "value": "looks good", "weight": 0.5 }
|
|
29
|
+
{ "type": "regex", "pattern": "parameterized|prepared|placeholder|bind", "flags": "i", "weight": 0.5 }
|
|
26
30
|
],
|
|
27
31
|
"dimensions": {
|
|
28
32
|
"security": "是否准确识别出 SQL 注入漏洞并说明其危害",
|
|
@@ -35,8 +39,7 @@ const INIT_SAMPLES = `[
|
|
|
35
39
|
"context": "async function fetchData(url) {\\n const res = await fetch(url);\\n const data = await res.json();\\n return data;\\n}",
|
|
36
40
|
"rubric": "应指出缺少错误处理(网络异常、非 JSON 响应、HTTP 错误状态码)",
|
|
37
41
|
"assertions": [
|
|
38
|
-
{ "type": "
|
|
39
|
-
{ "type": "regex", "pattern": "try[\\\\s\\\\S]*catch|exception|error", "flags": "i", "weight": 1 },
|
|
42
|
+
{ "type": "regex", "pattern": "try[\\\\s\\\\S]*catch|catch|exception|error", "flags": "i", "weight": 1 },
|
|
40
43
|
{ "type": "contains", "value": "status", "weight": 0.5 }
|
|
41
44
|
],
|
|
42
45
|
"dimensions": {
|
|
@@ -154,9 +157,9 @@ export default class Init extends BaseCommand {
|
|
|
154
157
|
console.log(tCli('cli.init.scaffolded', lang, { dir: targetDir }));
|
|
155
158
|
console.log('');
|
|
156
159
|
console.log(tCli('cli.init.next_steps_title', lang));
|
|
157
|
-
console.log(tCli('cli.init.next_step_edit_samples', lang));
|
|
158
|
-
console.log(tCli('cli.init.next_step_edit_skills', lang));
|
|
159
160
|
console.log(tCli('cli.init.next_step_run', lang));
|
|
161
|
+
console.log(tCli('cli.init.next_step_executor', lang));
|
|
162
|
+
console.log(tCli('cli.init.next_step_customize', lang));
|
|
160
163
|
console.log(tCli('cli.init.note_codex_executor', lang));
|
|
161
164
|
});
|
|
162
165
|
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { CliMessage } from './types.js';
|
|
2
|
-
export type InitMessageKey = 'cli.init.scaffolded' | 'cli.init.next_steps_title' | 'cli.init.
|
|
2
|
+
export type InitMessageKey = 'cli.init.scaffolded' | 'cli.init.next_steps_title' | 'cli.init.next_step_run' | 'cli.init.next_step_executor' | 'cli.init.next_step_customize' | 'cli.init.note_codex_executor';
|
|
3
3
|
export declare const initDict: Record<InitMessageKey, CliMessage>;
|
|
@@ -1,23 +1,26 @@
|
|
|
1
1
|
export const initDict = {
|
|
2
2
|
'cli.init.scaffolded': {
|
|
3
|
-
zh: '已初始化 omk
|
|
3
|
+
zh: '已初始化 omk 项目:{dir}',
|
|
4
4
|
en: 'omk project initialized at: {dir}',
|
|
5
5
|
},
|
|
6
6
|
'cli.init.next_steps_title': {
|
|
7
|
-
zh: '
|
|
7
|
+
zh: '下一步:',
|
|
8
8
|
en: 'Next steps:',
|
|
9
9
|
},
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
// 先让用户「无需改任何文件直接跑通」——脚手架的用例与 skill 本身可跑(已过合规校验),
|
|
11
|
+
// 跑出第一份报告是冷启动最该先发生的事;「换成你自己的」放到跑通之后。这也消除了
|
|
12
|
+
// 主 README「不用改任何文件」与旧 init「先编辑」的矛盾。
|
|
13
|
+
'cli.init.next_step_run': {
|
|
14
|
+
zh: ' 1. 直接跑通(无需先改任何文件):omk eval --control code-review-v1 --treatment code-review-v2',
|
|
15
|
+
en: ' 1. Run it as-is (no edits needed): omk eval --control code-review-v1 --treatment code-review-v2',
|
|
13
16
|
},
|
|
14
|
-
'cli.init.
|
|
15
|
-
zh: '
|
|
16
|
-
en: '
|
|
17
|
+
'cli.init.next_step_executor': {
|
|
18
|
+
zh: ' 默认执行器与评委用 claude CLI,需先装好并登录;想换别的模型或离线跑(无需 API key)见文档「执行器」。',
|
|
19
|
+
en: ' The default executor and judge use the claude CLI (install and log in first); to use another model or run offline (no API key) see the Executors docs.',
|
|
17
20
|
},
|
|
18
|
-
'cli.init.
|
|
19
|
-
zh: '
|
|
20
|
-
en: '
|
|
21
|
+
'cli.init.next_step_customize': {
|
|
22
|
+
zh: ' 2. 跑通后,把 skills/code-review-v1/SKILL.md 和 skills/code-review-v2/SKILL.md 与 eval-samples.json 换成你自己的 skill 和用例',
|
|
23
|
+
en: ' 2. Once it runs, replace skills/code-review-v1/SKILL.md and skills/code-review-v2/SKILL.md and eval-samples.json with your own skills and cases',
|
|
21
24
|
},
|
|
22
25
|
'cli.init.note_codex_executor': {
|
|
23
26
|
zh: '\n注: omk 评测时把 SKILL.md 整文(含 frontmatter)作为 system prompt 注入——跨 executor 一致(claude / codex / openai-api / gemini 都走同一条路径,不依赖任何 executor 的 native skill auto-discovery 或 Skill 工具机制)。frontmatter 在 prompt 头部对 model 行为无显著影响。\n模板带 Claude Code 兼容的 frontmatter(name + description)是为了让同一份 directory-skill 也能 deploy 到 Claude Code:把整个目录复制到 ~/.claude/skills/code-review-v1/(整目录,不是单个 SKILL.md),Claude SDK 才能识别。这是 omk 评测之外的 bonus,一份文件双向 dogfood。',
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { CliMessage } from './types.js';
|
|
2
|
-
export type RunMessageKey = 'cli.progress.preflight_starting' | 'cli.progress.sample_retry' | 'cli.progress.sample_error' | 'cli.progress.sample_executing' | 'cli.progress.sample_exec_done' | 'cli.progress.output_preview' | 'cli.progress.judging' | 'cli.progress.judged' | 'cli.progress.skipped' | 'cli.progress.sample_done' | 'cli.progress.sample_failed_done' | 'cli.run.invalid_repeat' | 'cli.run.invalid_judge_repeat' | 'cli.run.no_debias_length_active' | 'cli.run.invalid_bootstrap_samples' | 'cli.run.bootstrap_samples_too_large' | 'cli.run.dry_run_no_scores' | 'cli.run.skill_section' | 'cli.run.run_section' | 'cli.run.batch_complete' | 'cli.run.batch_verdict_header' | 'cli.run.batch_child_report_missing' | 'cli.run.eval_complete' | 'cli.run.tally' | 'cli.run.report_saved' | 'cli.run.evidence_recorded' | 'cli.run.evidence_recorded_unbound' | 'cli.run.report_only_gate_skipped' | 'cli.run.report_server_running' | 'cli.run.report_server_view' | 'cli.run.report_server_stop' | 'cli.run.no_serve_in_non_tty' | 'cli.run.no_serve_view_hint' | 'cli.run.gold_load_failed' | 'cli.run.gold_load_issue' | 'cli.run.contamination_warning' | 'cli.run.skip_connectivity_warning';
|
|
2
|
+
export type RunMessageKey = 'cli.progress.preflight_starting' | 'cli.progress.sample_retry' | 'cli.progress.sample_error' | 'cli.progress.sample_executing' | 'cli.progress.sample_exec_done' | 'cli.progress.output_preview' | 'cli.progress.judging' | 'cli.progress.judged' | 'cli.progress.skipped' | 'cli.progress.sample_done' | 'cli.progress.sample_failed_done' | 'cli.run.invalid_repeat' | 'cli.run.invalid_holdout_ratio' | 'cli.run.invalid_judge_repeat' | 'cli.run.no_debias_length_active' | 'cli.run.invalid_bootstrap_samples' | 'cli.run.bootstrap_samples_too_large' | 'cli.run.dry_run_no_scores' | 'cli.run.skill_section' | 'cli.run.run_section' | 'cli.run.batch_complete' | 'cli.run.batch_verdict_header' | 'cli.run.batch_child_report_missing' | 'cli.run.eval_complete' | 'cli.run.tally' | 'cli.run.report_saved' | 'cli.run.evidence_recorded' | 'cli.run.evidence_recorded_unbound' | 'cli.run.report_only_gate_skipped' | 'cli.run.report_server_running' | 'cli.run.report_server_view' | 'cli.run.report_server_stop' | 'cli.run.no_serve_in_non_tty' | 'cli.run.no_serve_view_hint' | 'cli.run.gold_load_failed' | 'cli.run.gold_load_issue' | 'cli.run.contamination_warning' | 'cli.run.skip_connectivity_warning';
|
|
3
3
|
export declare const runDict: Record<RunMessageKey, CliMessage>;
|