oh-my-knowledge 0.48.0 → 0.49.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 (243) hide show
  1. package/README.md +50 -18
  2. package/README.zh.md +55 -23
  3. package/dist/analysis/coverage-analyzer.d.ts +1 -0
  4. package/dist/analysis/coverage-analyzer.js +125 -62
  5. package/dist/analysis/failure-clusterer.js +2 -1
  6. package/dist/analysis/gap-analyzer.d.ts +2 -2
  7. package/dist/analysis/gap-analyzer.js +13 -3
  8. package/dist/analysis/hedging-classifier.d.ts +2 -2
  9. package/dist/analysis/hedging-classifier.js +3 -4
  10. package/dist/analysis/report-diagnostics.js +9 -7
  11. package/dist/analysis/sample-diagnostics.js +6 -6
  12. package/dist/artifact-graph/doctor.js +15 -7
  13. package/dist/assets/agent-skills/omk/SKILL.md +27 -7
  14. package/dist/assets/agent-skills/omk/references/commands.md +18 -17
  15. package/dist/authoring/evolver.d.ts +10 -6
  16. package/dist/authoring/evolver.js +496 -83
  17. package/dist/authoring/generator.d.ts +3 -3
  18. package/dist/authoring/generator.js +5 -10
  19. package/dist/authoring/sample-fixer.d.ts +8 -6
  20. package/dist/authoring/sample-fixer.js +76 -5
  21. package/dist/cli/commands/doctor.js +31 -14
  22. package/dist/cli/commands/eval/index.d.ts +3 -0
  23. package/dist/cli/commands/eval/index.js +163 -17
  24. package/dist/cli/commands/evolve.d.ts +4 -4
  25. package/dist/cli/commands/evolve.js +27 -13
  26. package/dist/cli/commands/init.js +16 -3
  27. package/dist/cli/commands/observe/inbox.js +28 -21
  28. package/dist/cli/commands/observe/index.js +20 -11
  29. package/dist/cli/commands/observe/ingest.d.ts +3 -0
  30. package/dist/cli/commands/observe/ingest.js +30 -2
  31. package/dist/cli/commands/sample.d.ts +6 -3
  32. package/dist/cli/commands/sample.js +72 -68
  33. package/dist/cli/lib/codex-model-hint.d.ts +9 -0
  34. package/dist/cli/lib/codex-model-hint.js +45 -0
  35. package/dist/cli/lib/generation-failure-hint.d.ts +2 -0
  36. package/dist/cli/lib/generation-failure-hint.js +61 -0
  37. package/dist/cli/lib/i18n-dict/common.d.ts +1 -1
  38. package/dist/cli/lib/i18n-dict/common.js +4 -0
  39. package/dist/cli/lib/i18n-dict/gen.d.ts +1 -1
  40. package/dist/cli/lib/i18n-dict/gen.js +38 -6
  41. package/dist/cli/lib/i18n-dict/help.js +6 -6
  42. package/dist/cli/lib/i18n-dict/init.d.ts +1 -1
  43. package/dist/cli/lib/i18n-dict/init.js +13 -9
  44. package/dist/cli/lib/i18n-dict/run.d.ts +1 -1
  45. package/dist/cli/lib/i18n-dict/run.js +34 -2
  46. package/dist/cli/lib/llm-failure-classifier.d.ts +2 -0
  47. package/dist/cli/lib/llm-failure-classifier.js +8 -0
  48. package/dist/cli/lib/parse-run-config.d.ts +6 -5
  49. package/dist/cli/lib/parse-run-config.js +16 -9
  50. package/dist/cli/lib/runtime-defaults.d.ts +21 -0
  51. package/dist/cli/lib/runtime-defaults.js +79 -0
  52. package/dist/diagnosis/observe-mapper.js +14 -15
  53. package/dist/diagnosis/observe-producer.js +3 -1
  54. package/dist/diagnosis/studio-projection.js +14 -7
  55. package/dist/diagnosis/types.d.ts +2 -0
  56. package/dist/diagnosis/types.js +12 -0
  57. package/dist/doctor/endpoint-rule.js +2 -1
  58. package/dist/eval-core/artifact-file-names.js +18 -1
  59. package/dist/eval-core/artifact-index.d.ts +7 -11
  60. package/dist/eval-core/artifact-index.js +139 -80
  61. package/dist/eval-core/cache.d.ts +12 -3
  62. package/dist/eval-core/cache.js +89 -29
  63. package/dist/eval-core/comparability.js +10 -6
  64. package/dist/eval-core/evaluation-execution.d.ts +2 -1
  65. package/dist/eval-core/evaluation-execution.js +122 -37
  66. package/dist/eval-core/evaluation-job.d.ts +4 -1
  67. package/dist/eval-core/evaluation-job.js +4 -1
  68. package/dist/eval-core/evaluation-reporting.d.ts +15 -13
  69. package/dist/eval-core/evaluation-reporting.js +54 -52
  70. package/dist/eval-core/execution-strategy.d.ts +2 -0
  71. package/dist/eval-core/execution-strategy.js +11 -9
  72. package/dist/eval-core/fact-checker.js +15 -7
  73. package/dist/eval-core/holdout.js +3 -2
  74. package/dist/eval-core/judge-independence.d.ts +2 -2
  75. package/dist/eval-core/mock-hook.cjs +23 -6
  76. package/dist/eval-core/mocks-runtime.js +30 -8
  77. package/dist/eval-core/report-document.d.ts +12 -0
  78. package/dist/eval-core/report-document.js +1151 -0
  79. package/dist/eval-core/report-extensions.d.ts +4 -0
  80. package/dist/eval-core/report-extensions.js +500 -0
  81. package/dist/eval-core/report-file-migration.js +7 -2
  82. package/dist/eval-core/resume-compatibility.d.ts +31 -0
  83. package/dist/eval-core/resume-compatibility.js +141 -0
  84. package/dist/eval-core/sample-fingerprint.d.ts +12 -0
  85. package/dist/eval-core/sample-fingerprint.js +193 -0
  86. package/dist/eval-core/schema.js +86 -31
  87. package/dist/eval-core/verdict.d.ts +8 -4
  88. package/dist/eval-core/verdict.js +24 -10
  89. package/dist/eval-workflows/batch-evaluation-workflow.d.ts +2 -1
  90. package/dist/eval-workflows/batch-evaluation-workflow.js +25 -12
  91. package/dist/eval-workflows/evaluation-pipeline/preflight-warnings.d.ts +10 -5
  92. package/dist/eval-workflows/evaluation-pipeline/preflight-warnings.js +58 -21
  93. package/dist/eval-workflows/evaluation-pipeline/report-finalize.js +3 -1
  94. package/dist/eval-workflows/evaluation-pipeline/run-state.d.ts +4 -1
  95. package/dist/eval-workflows/evaluation-pipeline/run-state.js +4 -1
  96. package/dist/eval-workflows/evaluation-pipeline/test-set-hash.d.ts +6 -5
  97. package/dist/eval-workflows/evaluation-pipeline/test-set-hash.js +17 -10
  98. package/dist/eval-workflows/evaluation-pipeline.js +12 -7
  99. package/dist/eval-workflows/run-evaluation.d.ts +9 -7
  100. package/dist/eval-workflows/run-evaluation.js +79 -51
  101. package/dist/executors/anthropic-api.js +65 -9
  102. package/dist/executors/claude-cli.js +16 -79
  103. package/dist/executors/claude-protocol.d.ts +28 -0
  104. package/dist/executors/claude-protocol.js +180 -0
  105. package/dist/executors/claude-sdk-trace.js +56 -28
  106. package/dist/executors/claude-sdk.d.ts +1 -0
  107. package/dist/executors/claude-sdk.js +39 -93
  108. package/dist/executors/codex-cli-trace.js +166 -31
  109. package/dist/executors/codex-cli.d.ts +6 -8
  110. package/dist/executors/codex-cli.js +49 -151
  111. package/dist/executors/codex-protocol.d.ts +24 -0
  112. package/dist/executors/codex-protocol.js +234 -0
  113. package/dist/executors/codex-sdk.js +68 -120
  114. package/dist/executors/gemini.js +88 -13
  115. package/dist/executors/index.d.ts +2 -3
  116. package/dist/executors/index.js +5 -3
  117. package/dist/executors/openai-api.js +70 -9
  118. package/dist/executors/runtime-fingerprint.js +88 -11
  119. package/dist/executors/script-command.d.ts +8 -0
  120. package/dist/executors/script-command.js +87 -0
  121. package/dist/executors/script.js +202 -29
  122. package/dist/executors/shared.d.ts +35 -3
  123. package/dist/executors/shared.js +113 -15
  124. package/dist/grading/assertions.d.ts +1 -1
  125. package/dist/grading/assertions.js +19 -9
  126. package/dist/grading/diagnostic.d.ts +9 -2
  127. package/dist/grading/diagnostic.js +25 -2
  128. package/dist/grading/index.js +10 -4
  129. package/dist/grading/judge.js +19 -6
  130. package/dist/grading/layered-scores.d.ts +2 -3
  131. package/dist/grading/layered-scores.js +2 -3
  132. package/dist/inputs/load-samples.d.ts +1 -2
  133. package/dist/inputs/load-samples.js +23 -1
  134. package/dist/inputs/mcp-resolver.js +6 -3
  135. package/dist/inputs/sample-document.d.ts +11 -0
  136. package/dist/inputs/sample-document.js +96 -0
  137. package/dist/managed/evidence.d.ts +1 -0
  138. package/dist/managed/evidence.js +1 -1
  139. package/dist/managed/store.js +200 -91
  140. package/dist/observability/codex-trace-adapter.d.ts +5 -0
  141. package/dist/observability/codex-trace-adapter.js +850 -0
  142. package/dist/observability/experience.d.ts +32 -6
  143. package/dist/observability/experience.js +2695 -459
  144. package/dist/observability/feedback-matchers.js +16 -1
  145. package/dist/observability/inbox-view-model.d.ts +1 -1
  146. package/dist/observability/inbox-view-model.js +19 -14
  147. package/dist/observability/inbox.d.ts +7 -1
  148. package/dist/observability/inbox.js +632 -124
  149. package/dist/observability/problem-patterns.js +2 -0
  150. package/dist/observability/review-state.d.ts +6 -0
  151. package/dist/observability/review-state.js +235 -63
  152. package/dist/observability/skill-chain-advisories.js +1 -1
  153. package/dist/observability/skill-chain.js +17 -4
  154. package/dist/observability/skill-health-analyzer.d.ts +32 -7
  155. package/dist/observability/skill-health-analyzer.js +194 -121
  156. package/dist/observability/skill-health-report.d.ts +10 -0
  157. package/dist/observability/skill-health-report.js +620 -0
  158. package/dist/observability/soft-standards/constants.d.ts +0 -1
  159. package/dist/observability/soft-standards/constants.js +0 -1
  160. package/dist/observability/soft-standards/index.d.ts +1 -1
  161. package/dist/observability/soft-standards/index.js +1 -1
  162. package/dist/observability/soft-standards/llm-extractor.js +8 -10
  163. package/dist/observability/soft-standards/skill-standards-store.d.ts +2 -1
  164. package/dist/observability/soft-standards/skill-standards-store.js +59 -18
  165. package/dist/observability/soft-standards/types.d.ts +2 -2
  166. package/dist/observability/trace-adapter.d.ts +12 -7
  167. package/dist/observability/trace-adapter.js +11 -9
  168. package/dist/observability/trace-attribution.d.ts +13 -5
  169. package/dist/observability/trace-attribution.js +315 -21
  170. package/dist/observability/trace-ingestion.d.ts +9 -0
  171. package/dist/observability/trace-ingestion.js +80 -0
  172. package/dist/observability/trace-ir.d.ts +113 -0
  173. package/dist/observability/trace-ir.js +87 -0
  174. package/dist/observability/trace-segmenter.d.ts +19 -6
  175. package/dist/observability/trace-segmenter.js +377 -196
  176. package/dist/observability/trace-session-index.d.ts +19 -0
  177. package/dist/observability/trace-session-index.js +68 -0
  178. package/dist/observability/trace-source.d.ts +12 -4
  179. package/dist/observability/trace-source.js +939 -215
  180. package/dist/renderer/html-renderer.js +37 -6
  181. package/dist/renderer/icons.js +3 -0
  182. package/dist/renderer/observation-inbox-renderer.js +208 -90
  183. package/dist/renderer/skill-detail-renderer.js +452 -109
  184. package/dist/renderer/skill-health-renderer.js +69 -12
  185. package/dist/renderer/summary.js +28 -7
  186. package/dist/renderer/table.js +21 -4
  187. package/dist/renderer/test-view.d.ts +1 -0
  188. package/dist/renderer/test-view.js +44 -9
  189. package/dist/server/indexed-report-store.js +14 -18
  190. package/dist/server/job-store.js +64 -26
  191. package/dist/server/report-server.js +190 -78
  192. package/dist/server/report-store.js +57 -80
  193. package/dist/server/skill-index.js +143 -49
  194. package/dist/server/skill-insights.js +44 -5
  195. package/dist/shared/artifact-graph.d.ts +3 -0
  196. package/dist/shared/artifact-graph.js +224 -0
  197. package/dist/shared/assertion-types.d.ts +8 -0
  198. package/dist/shared/assertion-types.js +46 -0
  199. package/dist/shared/atomic-json.d.ts +8 -0
  200. package/dist/shared/atomic-json.js +33 -0
  201. package/dist/shared/diagnosis-schema.d.ts +9 -0
  202. package/dist/shared/diagnosis-schema.js +181 -0
  203. package/dist/shared/doctor-report.d.ts +3 -0
  204. package/dist/shared/doctor-report.js +103 -0
  205. package/dist/shared/evaluation-job.d.ts +6 -0
  206. package/dist/shared/evaluation-job.js +217 -0
  207. package/dist/shared/executor-result.d.ts +17 -0
  208. package/dist/shared/executor-result.js +221 -0
  209. package/dist/shared/file-lock.d.ts +12 -0
  210. package/dist/shared/file-lock.js +129 -0
  211. package/dist/shared/json-value.d.ts +5 -0
  212. package/dist/shared/json-value.js +36 -0
  213. package/dist/shared/keyed-mutex.d.ts +7 -0
  214. package/dist/shared/keyed-mutex.js +24 -0
  215. package/dist/shared/record-count.d.ts +8 -0
  216. package/dist/shared/record-count.js +43 -0
  217. package/dist/shared/sample-contract.d.ts +3 -0
  218. package/dist/shared/sample-contract.js +332 -0
  219. package/dist/shared/timestamp.d.ts +6 -0
  220. package/dist/shared/timestamp.js +64 -0
  221. package/dist/shared/token-usage.d.ts +19 -0
  222. package/dist/shared/token-usage.js +50 -0
  223. package/dist/shared/tool-call-status.d.ts +8 -0
  224. package/dist/shared/tool-call-status.js +28 -0
  225. package/dist/shared/tool-identity.d.ts +21 -0
  226. package/dist/shared/tool-identity.js +84 -0
  227. package/dist/shared/tool-search.js +73 -16
  228. package/dist/shared/trace-projection.d.ts +5 -0
  229. package/dist/shared/trace-projection.js +20 -0
  230. package/dist/shared/trace-source-kind.d.ts +3 -0
  231. package/dist/shared/trace-source-kind.js +12 -0
  232. package/dist/types/diagnosis.d.ts +2 -0
  233. package/dist/types/eval.d.ts +4 -0
  234. package/dist/types/executor.d.ts +32 -5
  235. package/dist/types/index.d.ts +1 -0
  236. package/dist/types/index.js +1 -0
  237. package/dist/types/judge.d.ts +2 -0
  238. package/dist/types/observability.d.ts +116 -9
  239. package/dist/types/report.d.ts +58 -6
  240. package/dist/types/skill-index.d.ts +7 -0
  241. package/dist/types/trace.d.ts +2 -0
  242. package/dist/types/trace.js +1 -0
  243. package/package.json +9 -5
@@ -0,0 +1,141 @@
1
+ import { buildVariantConfig } from './execution-strategy.js';
2
+ import { buildExecutorRuntimesByVariant, EVALUATION_REPORT_SCHEMA_VERSION, getCliVersion, } from './evaluation-reporting.js';
3
+ import { hashSample } from './sample-fingerprint.js';
4
+ import { getJudgePromptHash } from '../grading/judge.js';
5
+ import { getDiagnosticPromptHash, resolveDiagnosticTarget, } from '../grading/diagnostic.js';
6
+ import { getExecutorRuntimeFingerprint } from '../executors/runtime-fingerprint.js';
7
+ function canonicalStringify(value) {
8
+ if (value === undefined)
9
+ return 'undefined';
10
+ if (value === null || typeof value !== 'object')
11
+ return JSON.stringify(value) ?? 'null';
12
+ if (Array.isArray(value))
13
+ return `[${value.map(canonicalStringify).join(',')}]`;
14
+ const record = value;
15
+ const keys = Object.keys(record)
16
+ .filter((key) => record[key] !== undefined)
17
+ .sort();
18
+ return `{${keys.map((key) => `${JSON.stringify(key)}:${canonicalStringify(record[key])}`).join(',')}}`;
19
+ }
20
+ function same(left, right) {
21
+ return canonicalStringify(left) === canonicalStringify(right);
22
+ }
23
+ function runtimeFingerprints(runtimes) {
24
+ if (!runtimes)
25
+ return undefined;
26
+ return Object.fromEntries(Object.entries(runtimes).map(([variant, runtime]) => [variant, runtime.fingerprint]));
27
+ }
28
+ /**
29
+ * Decide whether successful entries from an earlier report can be inserted
30
+ * into the current measurement without mixing protocols. Every value compared
31
+ * here is either the report's public measurement evidence or a request option
32
+ * that can alter a persisted VariantResult.
33
+ */
34
+ export function checkResumeCompatibility(report, current) {
35
+ const mismatches = [];
36
+ const check = (field, actual, expected) => {
37
+ if (!same(actual, expected))
38
+ mismatches.push(field);
39
+ };
40
+ check('meta.schemaVersion', report.meta.schemaVersion, EVALUATION_REPORT_SCHEMA_VERSION);
41
+ check('meta.cliVersion', report.meta.cliVersion, getCliVersion());
42
+ check('meta.nodeVersion', report.meta.nodeVersion, process.version);
43
+ check('meta.variants', report.meta.variants, current.variants);
44
+ check('meta.model', report.meta.model, current.model);
45
+ check('meta.executor', report.meta.executor, current.executorName);
46
+ check('meta.effort', report.meta.effort, current.effort);
47
+ check('meta.noJudge', report.meta.noJudge === true, current.noJudge);
48
+ check('meta.judgeRepeat', report.meta.judgeRepeat ?? 1, current.judgeRepeat ?? 1);
49
+ check('meta.sampleCount', report.meta.sampleCount, current.samples.length);
50
+ check('meta.taskCount', report.meta.taskCount, current.tasks.length);
51
+ const expectedArtifactHashes = Object.fromEntries(current.artifacts.map((artifact) => [
52
+ artifact.name,
53
+ artifact.contentHash ?? 'no-skill',
54
+ ]));
55
+ check('meta.artifactHashes', report.meta.artifactHashes, expectedArtifactHashes);
56
+ const expectedSampleHashes = Object.fromEntries(current.samples.map((sample) => [
57
+ sample.sample_id,
58
+ hashSample(sample, current.samplesBaseDir),
59
+ ]));
60
+ check('meta.sampleHashes', report.meta.sampleHashes, expectedSampleHashes);
61
+ check('meta.variantConfigs', report.meta.variantConfigs, current.artifacts.map(buildVariantConfig));
62
+ check('meta.skillIsolation', report.meta.skillIsolation, Object.fromEntries(current.artifacts.map((artifact) => [
63
+ artifact.name,
64
+ artifact.allowedSkills ?? null,
65
+ ])));
66
+ const expectedExecutorRuntimes = buildExecutorRuntimesByVariant({
67
+ variants: current.variants,
68
+ model: current.model,
69
+ executorName: current.executorName,
70
+ tasks: current.tasks,
71
+ artifacts: current.artifacts,
72
+ request: {
73
+ skillDir: current.skillDir,
74
+ timeoutMs: current.timeoutMs,
75
+ },
76
+ });
77
+ check('meta.executorRuntimes', runtimeFingerprints(report.meta.executorRuntimes), runtimeFingerprints(expectedExecutorRuntimes));
78
+ const actualJudges = report.meta.judgeModels.map((judge) => ({
79
+ executor: judge.executor,
80
+ model: judge.model,
81
+ runtime: judge.runtime?.fingerprint,
82
+ }));
83
+ const expectedJudges = current.judgeModels.map((judge) => ({
84
+ executor: judge.executor,
85
+ model: judge.model,
86
+ ...(!current.noJudge
87
+ ? {
88
+ runtime: getExecutorRuntimeFingerprint(judge.executor, judge.model, {
89
+ skillDir: current.skillDir,
90
+ }).fingerprint,
91
+ }
92
+ : {}),
93
+ }));
94
+ check('meta.judgeModels', actualJudges, expectedJudges);
95
+ if (!current.noJudge) {
96
+ check('meta.judgePromptHash', report.meta.judgePromptHash, getJudgePromptHash(current.lengthDebias !== false));
97
+ }
98
+ const diagnosticEnabled = current.noDiagnostic !== true;
99
+ const diagnosticTarget = resolveDiagnosticTarget(current.judgeModels, current.executorName, current.model);
100
+ const expectedDiagnostic = diagnosticEnabled
101
+ ? {
102
+ enabled: true,
103
+ executor: diagnosticTarget.executor,
104
+ model: diagnosticTarget.model,
105
+ runtime: getExecutorRuntimeFingerprint(diagnosticTarget.executor, diagnosticTarget.model).fingerprint,
106
+ promptHash: getDiagnosticPromptHash(),
107
+ }
108
+ : { enabled: false };
109
+ const actualDiagnostic = report.meta.diagnostic
110
+ ? {
111
+ enabled: report.meta.diagnostic.enabled,
112
+ ...(report.meta.diagnostic.executor
113
+ ? { executor: report.meta.diagnostic.executor }
114
+ : {}),
115
+ ...(report.meta.diagnostic.model ? { model: report.meta.diagnostic.model } : {}),
116
+ ...(report.meta.diagnostic.runtime
117
+ ? { runtime: report.meta.diagnostic.runtime.fingerprint }
118
+ : {}),
119
+ ...(report.meta.diagnostic.promptHash
120
+ ? { promptHash: report.meta.diagnostic.promptHash }
121
+ : {}),
122
+ }
123
+ : undefined;
124
+ check('meta.diagnostic', actualDiagnostic, expectedDiagnostic);
125
+ const request = report.meta.request;
126
+ if (!request) {
127
+ mismatches.push('meta.request');
128
+ }
129
+ else {
130
+ check('meta.request.timeoutMs', request.timeoutMs, current.timeoutMs);
131
+ check('meta.request.budget', request.budget, current.budget);
132
+ check('meta.request.retry', request.retry ?? 0, current.retry ?? 0);
133
+ check('meta.request.noDiagnostic', request.noDiagnostic === true, current.noDiagnostic === true);
134
+ }
135
+ if (report.meta.budgetExhausted)
136
+ mismatches.push('meta.budgetExhausted');
137
+ return {
138
+ compatible: mismatches.length === 0,
139
+ mismatches: [...new Set(mismatches)],
140
+ };
141
+ }
@@ -0,0 +1,12 @@
1
+ import type { Sample } from '../types/index.js';
2
+ /**
3
+ * Content fingerprint for files that can change executor output while the
4
+ * inline sample contract remains byte-for-byte identical.
5
+ */
6
+ export declare function hashSampleExecutionDependencies(sample: Sample, baseDir?: string): string | undefined;
7
+ /**
8
+ * Stable hash of the complete sample measurement contract, including external
9
+ * mock fixtures and the statically resolvable ESM import graph of custom
10
+ * assertion modules when a sample base directory is available.
11
+ */
12
+ export declare function hashSample(sample: Sample, baseDir?: string): string;
@@ -0,0 +1,193 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { existsSync, readFileSync, statSync } from 'node:fs';
3
+ import { builtinModules, createRequire } from 'node:module';
4
+ import { dirname, isAbsolute, relative, resolve, } from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { initSync, parse } from 'es-module-lexer';
7
+ const MAX_ASSERTION_MODULE_FILES = 512;
8
+ const MAX_ASSERTION_MODULE_BYTES = 32 * 1024 * 1024;
9
+ const BUILTIN_MODULES = new Set([
10
+ ...builtinModules,
11
+ ...builtinModules.map((name) => `node:${name}`),
12
+ ]);
13
+ initSync();
14
+ function canonicalStringify(value) {
15
+ if (value === null || typeof value !== 'object')
16
+ return JSON.stringify(value) ?? 'null';
17
+ if (Array.isArray(value)) {
18
+ return `[${value.map((entry) => entry === undefined ? 'null' : canonicalStringify(entry)).join(',')}]`;
19
+ }
20
+ const record = value;
21
+ const entries = Object.keys(record)
22
+ .filter((key) => record[key] !== undefined)
23
+ .sort();
24
+ return `{${entries.map((key) => `${JSON.stringify(key)}:${canonicalStringify(record[key])}`).join(',')}}`;
25
+ }
26
+ function hashString(value) {
27
+ return createHash('sha256').update(value).digest('hex');
28
+ }
29
+ function dependencyContentFingerprint(path, baseDir) {
30
+ if (!baseDir && !isAbsolute(path))
31
+ return 'base-dir-unavailable';
32
+ const resolvedPath = isAbsolute(path) ? path : resolve(baseDir, path);
33
+ try {
34
+ return `sha256:${hashString(readFileSync(resolvedPath))}`;
35
+ }
36
+ catch (error) {
37
+ const code = error.code;
38
+ return `unavailable:${code ?? 'unknown'}`;
39
+ }
40
+ }
41
+ function customAssertionPaths(assertions) {
42
+ if (!assertions)
43
+ return [];
44
+ return assertions.flatMap((assertion) => {
45
+ const record = assertion;
46
+ const current = assertion.type === 'custom' && typeof record.fn === 'string'
47
+ ? [record.fn]
48
+ : [];
49
+ const children = assertion.type === 'assert-set' && Array.isArray(assertion.children)
50
+ ? customAssertionPaths(assertion.children)
51
+ : [];
52
+ return [...current, ...children];
53
+ });
54
+ }
55
+ function displayDependencyPath(path, baseDir) {
56
+ if (!baseDir)
57
+ return path;
58
+ const rel = relative(baseDir, path);
59
+ return rel || '.';
60
+ }
61
+ function resolveImportedModule(specifier, importer) {
62
+ if (BUILTIN_MODULES.has(specifier))
63
+ return undefined;
64
+ if (specifier.startsWith('file:')) {
65
+ try {
66
+ return fileURLToPath(specifier);
67
+ }
68
+ catch {
69
+ return undefined;
70
+ }
71
+ }
72
+ if (specifier.startsWith('.') || specifier.startsWith('/')) {
73
+ const clean = specifier.replace(/[?#].*$/, '');
74
+ const path = specifier.startsWith('/') ? clean : resolve(dirname(importer), clean);
75
+ return existsSync(path) && statSync(path).isFile() ? path : undefined;
76
+ }
77
+ try {
78
+ const resolved = createRequire(importer).resolve(specifier);
79
+ return BUILTIN_MODULES.has(resolved) ? undefined : resolved;
80
+ }
81
+ catch {
82
+ return undefined;
83
+ }
84
+ }
85
+ function customAssertionModuleEntries(path, baseDir) {
86
+ if (!baseDir && !isAbsolute(path)) {
87
+ return [[`module:${path}`, 'base-dir-unavailable']];
88
+ }
89
+ const entryPath = isAbsolute(path) ? path : resolve(baseDir, path);
90
+ const entries = [];
91
+ const visited = new Set();
92
+ let totalBytes = 0;
93
+ const visit = (modulePath) => {
94
+ if (visited.has(modulePath))
95
+ return;
96
+ if (visited.size >= MAX_ASSERTION_MODULE_FILES) {
97
+ throw new Error(`custom assertion module graph exceeds ${MAX_ASSERTION_MODULE_FILES} files: ${entryPath}`);
98
+ }
99
+ visited.add(modulePath);
100
+ let source;
101
+ try {
102
+ source = readFileSync(modulePath);
103
+ }
104
+ catch (error) {
105
+ const code = error.code;
106
+ entries.push([
107
+ `module:${displayDependencyPath(modulePath, baseDir)}`,
108
+ `unavailable:${code ?? 'unknown'}`,
109
+ ]);
110
+ return;
111
+ }
112
+ totalBytes += source.byteLength;
113
+ if (totalBytes > MAX_ASSERTION_MODULE_BYTES) {
114
+ throw new Error(`custom assertion module graph exceeds ${MAX_ASSERTION_MODULE_BYTES} bytes: ${entryPath}`);
115
+ }
116
+ entries.push([
117
+ `module:${displayDependencyPath(modulePath, baseDir)}`,
118
+ `sha256:${hashString(source)}`,
119
+ ]);
120
+ if (!/\.[cm]?js$/i.test(modulePath))
121
+ return;
122
+ let imports;
123
+ try {
124
+ [imports] = parse(source.toString('utf8'));
125
+ }
126
+ catch {
127
+ entries.push([
128
+ `module-parse:${displayDependencyPath(modulePath, baseDir)}`,
129
+ 'unparseable',
130
+ ]);
131
+ return;
132
+ }
133
+ for (const imported of imports) {
134
+ if (!imported.n) {
135
+ if (imported.d >= 0) {
136
+ entries.push([
137
+ `module-dynamic:${displayDependencyPath(modulePath, baseDir)}:${imported.ss}`,
138
+ 'non-literal',
139
+ ]);
140
+ }
141
+ continue;
142
+ }
143
+ const resolvedImport = resolveImportedModule(imported.n, modulePath);
144
+ if (resolvedImport)
145
+ visit(resolvedImport);
146
+ }
147
+ };
148
+ visit(entryPath);
149
+ return entries.sort(([left], [right]) => left.localeCompare(right));
150
+ }
151
+ function sampleDependencyEntries(sample, baseDir, includeCustomAssertions) {
152
+ const entries = [];
153
+ for (const [index, mock] of (sample.mocks ?? []).entries()) {
154
+ if (typeof mock.return_file !== 'string' || !mock.return_file)
155
+ continue;
156
+ entries.push([
157
+ `mock:${index}:return_file:${mock.return_file}`,
158
+ dependencyContentFingerprint(mock.return_file, baseDir),
159
+ ]);
160
+ }
161
+ if (includeCustomAssertions) {
162
+ for (const [index, path] of customAssertionPaths(sample.assertions).entries()) {
163
+ for (const [modulePath, fingerprint] of customAssertionModuleEntries(path, baseDir)) {
164
+ entries.push([
165
+ `assertion:${index}:fn:${path}:${modulePath}`,
166
+ fingerprint,
167
+ ]);
168
+ }
169
+ }
170
+ }
171
+ return entries.sort(([left], [right]) => left.localeCompare(right));
172
+ }
173
+ /**
174
+ * Content fingerprint for files that can change executor output while the
175
+ * inline sample contract remains byte-for-byte identical.
176
+ */
177
+ export function hashSampleExecutionDependencies(sample, baseDir) {
178
+ const entries = sampleDependencyEntries(sample, baseDir, false);
179
+ return entries.length > 0 ? hashString(canonicalStringify(entries)).slice(0, 12) : undefined;
180
+ }
181
+ /**
182
+ * Stable hash of the complete sample measurement contract, including external
183
+ * mock fixtures and the statically resolvable ESM import graph of custom
184
+ * assertion modules when a sample base directory is available.
185
+ */
186
+ export function hashSample(sample, baseDir) {
187
+ const contract = Object.fromEntries(Object.entries(sample).filter(([key, value]) => key !== 'sample_id' && value !== undefined));
188
+ const dependencies = sampleDependencyEntries(sample, baseDir, true);
189
+ return hashString(canonicalStringify({
190
+ contract,
191
+ ...(dependencies.length > 0 ? { dependencies } : {}),
192
+ })).slice(0, 12);
193
+ }
@@ -3,27 +3,13 @@
3
3
  * Single source of truth for object structures used across runner, renderer, and server.
4
4
  */
5
5
  import { computeJudgeAgreement } from '../grading/judge.js';
6
+ import { isToolCallCancelled, isToolCallFailure, isToolCallSuccess, isToolCallUnknown, } from '../shared/tool-call-status.js';
7
+ import { incrementRecordCount } from '../shared/record-count.js';
8
+ import { truncateToolCallsForPersistence, truncateTurnsForPersistence, } from '../shared/trace-projection.js';
6
9
  import { safeSliceForJson } from '../util/safe-slice.js';
7
10
  function ratioToScore(ratio) {
8
11
  return Number((1 + ratio * 4).toFixed(2));
9
12
  }
10
- const MAX_TURN_CONTENT = 2000;
11
- const MAX_TOOL_OUTPUT = 1000;
12
- function truncateTurns(turns) {
13
- return turns.map((t) => ({
14
- ...t,
15
- content: safeSliceForJson(t.content, MAX_TURN_CONTENT),
16
- ...(t.toolCalls && { toolCalls: truncateToolCalls(t.toolCalls) }),
17
- }));
18
- }
19
- function truncateToolCalls(toolCalls) {
20
- return toolCalls.map((tc) => ({
21
- ...tc,
22
- output: typeof tc.output === 'string'
23
- ? safeSliceForJson(tc.output, MAX_TOOL_OUTPUT)
24
- : tc.output,
25
- }));
26
- }
27
13
  export function buildVariantResult(execResult, gradeResult, options) {
28
14
  const execCostUSD = execResult.costUSD || 0;
29
15
  const judgeCostUSD = gradeResult?.judgeCostUSD || 0;
@@ -31,7 +17,12 @@ export function buildVariantResult(execResult, gradeResult, options) {
31
17
  const gradeMs = options?.gradeMs || 0;
32
18
  const assistantTurns = execResult.turns?.filter((turn) => turn.role === 'assistant').length;
33
19
  const toolTurns = execResult.turns?.filter((turn) => turn.role === 'tool').length;
34
- const numToolFailures = execResult.toolCalls?.filter((tc) => !tc.success).length;
20
+ const numToolFailures = execResult.toolCalls?.filter(isToolCallFailure).length;
21
+ const numToolCancelled = execResult.toolCalls?.filter(isToolCallCancelled).length;
22
+ const numToolUnknown = execResult.toolCalls?.filter(isToolCallUnknown).length;
23
+ const comparableToolCalls = Math.max(0, (execResult.toolCalls?.length ?? 0)
24
+ - (numToolCancelled ?? 0)
25
+ - (numToolUnknown ?? 0));
35
26
  const traceSignals = [
36
27
  execResult.turns && execResult.turns.length > 0,
37
28
  execResult.toolCalls && execResult.toolCalls.length > 0,
@@ -45,9 +36,15 @@ export function buildVariantResult(execResult, gradeResult, options) {
45
36
  durationApiMs: execResult.durationApiMs,
46
37
  inputTokens: execResult.inputTokens,
47
38
  outputTokens: execResult.outputTokens,
48
- totalTokens: execResult.inputTokens + execResult.outputTokens,
39
+ totalTokens: execResult.inputTokens
40
+ + execResult.outputTokens
41
+ + execResult.cacheReadTokens
42
+ + execResult.cacheCreationTokens,
49
43
  cacheReadTokens: execResult.cacheReadTokens,
50
44
  cacheCreationTokens: execResult.cacheCreationTokens,
45
+ ...(execResult.tokenUsageReportedByExecutor === false && {
46
+ tokenUsageReportedByExecutor: false,
47
+ }),
51
48
  execCostUSD,
52
49
  judgeCostUSD,
53
50
  // 初值 = exec + judge。diagnostic 在 evaluation-execution.ts 跑完后会把
@@ -61,6 +58,7 @@ export function buildVariantResult(execResult, gradeResult, options) {
61
58
  // judge 同理:任一 dim/single/async assertion 走了不报 cost 的 judge executor → grade 整体也不可信
62
59
  ...(gradeResult?.judgeCostReportedByExecutor === false && { judgeCostReportedByExecutor: false }),
63
60
  numTurns: execResult.numTurns,
61
+ ...(execResult.attemptCount != null && { attemptCount: execResult.attemptCount }),
64
62
  ...(execResult.fullNumTurns != null && { fullNumTurns: execResult.fullNumTurns }),
65
63
  ...(execResult.numSubAgents != null && { numSubAgents: execResult.numSubAgents }),
66
64
  ...(assistantTurns != null && { assistantTurns }),
@@ -68,10 +66,14 @@ export function buildVariantResult(execResult, gradeResult, options) {
68
66
  ...(execResult.toolCalls && execResult.toolCalls.length > 0 && {
69
67
  numToolCalls: execResult.toolCalls.length,
70
68
  numToolFailures,
71
- toolSuccessRate: Number((execResult.toolCalls.filter((tc) => tc.success).length / execResult.toolCalls.length).toFixed(2)),
69
+ numToolCancelled,
70
+ numToolUnknown,
71
+ ...(comparableToolCalls > 0 && {
72
+ toolSuccessRate: Number((execResult.toolCalls.filter(isToolCallSuccess).length / comparableToolCalls).toFixed(2)),
73
+ }),
72
74
  toolNames: [...new Set(execResult.toolCalls.map((tc) => tc.tool))],
73
75
  toolDistribution: execResult.toolCalls.reduce((d, tc) => {
74
- d[tc.tool] = (d[tc.tool] || 0) + 1;
76
+ incrementRecordCount(d, tc.tool);
75
77
  return d;
76
78
  }, {}),
77
79
  }),
@@ -111,8 +113,12 @@ export function buildVariantResult(execResult, gradeResult, options) {
111
113
  ...(options?.factCheck && options.factCheck.totalCount > 0 && { factCheck: options.factCheck }),
112
114
  outputPreview: execResult.output ? safeSliceForJson(execResult.output, 200, '') : null,
113
115
  ...(execResult.output && { fullOutput: execResult.output }),
114
- ...(execResult.turns && execResult.turns.length > 0 && { turns: truncateTurns(execResult.turns) }),
115
- ...(execResult.toolCalls && execResult.toolCalls.length > 0 && { toolCalls: truncateToolCalls(execResult.toolCalls) }),
116
+ ...(execResult.turns && execResult.turns.length > 0 && {
117
+ turns: truncateTurnsForPersistence(execResult.turns),
118
+ }),
119
+ ...(execResult.toolCalls && execResult.toolCalls.length > 0 && {
120
+ toolCalls: truncateToolCallsForPersistence(execResult.toolCalls),
121
+ }),
116
122
  ...(execResult.mockStats && { mockStats: execResult.mockStats }),
117
123
  timing: { execMs, gradeMs, totalMs: execMs + gradeMs },
118
124
  };
@@ -122,6 +128,11 @@ export function buildVariantResult(execResult, gradeResult, options) {
122
128
  */
123
129
  export function buildVariantSummary(entries) {
124
130
  const ok = entries.filter((e) => e.ok);
131
+ const observedTokenEntries = ok.filter((entry) => entry.tokenUsageReportedByExecutor !== false);
132
+ const tokenUsageCoverageRate = entries.length > 0
133
+ ? Number((entries.filter((entry) => entry.tokenUsageReportedByExecutor !== false).length
134
+ / entries.length).toFixed(4))
135
+ : 1;
125
136
  const compositeScores = entries.filter((e) => typeof e.compositeScore === 'number' && e.compositeScore > 0).map((e) => e.compositeScore);
126
137
  const assertionScores = entries.filter((e) => e.assertions?.score != null && e.assertions.score > 0).map((e) => e.assertions.score);
127
138
  const llmScores = entries.filter((e) => typeof e.llmScore === 'number' && e.llmScore > 0).map((e) => e.llmScore);
@@ -132,9 +143,16 @@ export function buildVariantSummary(entries) {
132
143
  errorCount,
133
144
  errorRate: entries.length > 0 ? Number((errorCount / entries.length * 100).toFixed(1)) : 0,
134
145
  avgDurationMs: ok.length > 0 ? Math.round(ok.reduce((s, e) => s + (e.timing?.totalMs || e.durationMs), 0) / ok.length) : 0,
135
- avgInputTokens: ok.length > 0 ? Math.round(ok.reduce((s, e) => s + e.inputTokens, 0) / ok.length) : 0,
136
- avgOutputTokens: ok.length > 0 ? Math.round(ok.reduce((s, e) => s + e.outputTokens, 0) / ok.length) : 0,
137
- avgTotalTokens: ok.length > 0 ? Math.round(ok.reduce((s, e) => s + e.totalTokens, 0) / ok.length) : 0,
146
+ avgInputTokens: observedTokenEntries.length > 0
147
+ ? Math.round(observedTokenEntries.reduce((s, e) => s + e.inputTokens, 0) / observedTokenEntries.length)
148
+ : 0,
149
+ avgOutputTokens: observedTokenEntries.length > 0
150
+ ? Math.round(observedTokenEntries.reduce((s, e) => s + e.outputTokens, 0) / observedTokenEntries.length)
151
+ : 0,
152
+ avgTotalTokens: observedTokenEntries.length > 0
153
+ ? Math.round(observedTokenEntries.reduce((s, e) => s + e.totalTokens, 0) / observedTokenEntries.length)
154
+ : 0,
155
+ ...(tokenUsageCoverageRate < 1 && { tokenUsageCoverageRate }),
138
156
  totalCostUSD: ok.reduce((s, e) => s + (e.costUSD || 0), 0),
139
157
  totalExecCostUSD: ok.reduce((s, e) => s + (e.execCostUSD || 0), 0),
140
158
  totalJudgeCostUSD: ok.reduce((s, e) => s + (e.judgeCostUSD || 0), 0),
@@ -180,7 +198,40 @@ export function buildVariantSummary(entries) {
180
198
  if (withTools.length === 0)
181
199
  return {};
182
200
  const totalToolCalls = withTools.reduce((s, e) => s + (e.numToolCalls || 0), 0);
183
- const avgSuccessRate = withTools.reduce((s, e) => s + (e.toolSuccessRate || 0), 0) / withTools.length;
201
+ const outcomeTotals = withTools.reduce((totals, entry) => {
202
+ const calls = entry.numToolCalls ?? 0;
203
+ const explicitUnknown = Math.min(calls, Math.max(0, entry.numToolUnknown ?? 0));
204
+ const explicitCancelled = Math.min(Math.max(0, calls - explicitUnknown), Math.max(0, entry.numToolCancelled ?? 0));
205
+ const comparable = Math.max(0, calls - explicitUnknown - explicitCancelled);
206
+ if (typeof entry.numToolFailures === 'number') {
207
+ const failures = Math.min(comparable, Math.max(0, entry.numToolFailures));
208
+ totals.comparable += comparable;
209
+ totals.failures += failures;
210
+ totals.successes += comparable - failures;
211
+ totals.cancelled += explicitCancelled;
212
+ totals.unknown += explicitUnknown;
213
+ return totals;
214
+ }
215
+ if (typeof entry.toolSuccessRate === 'number') {
216
+ const rate = Math.max(0, Math.min(1, entry.toolSuccessRate));
217
+ totals.comparable += comparable;
218
+ totals.successes += rate * comparable;
219
+ totals.failures += (1 - rate) * comparable;
220
+ totals.cancelled += explicitCancelled;
221
+ totals.unknown += explicitUnknown;
222
+ return totals;
223
+ }
224
+ // A call count without failure/success evidence only says execution was
225
+ // attempted. Treat the whole row as unresolved instead of 0% success.
226
+ totals.unknown += calls;
227
+ return totals;
228
+ }, {
229
+ comparable: 0,
230
+ successes: 0,
231
+ failures: 0,
232
+ cancelled: 0,
233
+ unknown: 0,
234
+ });
184
235
  // sum per-sample toolDistribution(真实 call count),
185
236
  // 不再用 dedup 后的 toolNames 累加(那只统计"出现该 tool 的 sample 数",不是调用次数)。
186
237
  // 旧报告没 toolDistribution 字段时 fallback 到 toolNames 老语义,保兼容性。
@@ -188,19 +239,23 @@ export function buildVariantSummary(entries) {
188
239
  for (const e of withTools) {
189
240
  if (e.toolDistribution) {
190
241
  for (const [name, count] of Object.entries(e.toolDistribution)) {
191
- dist[name] = (dist[name] || 0) + count;
242
+ incrementRecordCount(dist, name, count);
192
243
  }
193
244
  }
194
245
  else {
195
246
  for (const name of (e.toolNames || [])) {
196
- dist[name] = (dist[name] || 0) + 1;
247
+ incrementRecordCount(dist, name);
197
248
  }
198
249
  }
199
250
  }
200
251
  return {
201
252
  avgToolCalls: Number((totalToolCalls / withTools.length).toFixed(1)),
202
- avgToolFailures: Number((withTools.reduce((s, e) => s + (e.numToolFailures || 0), 0) / withTools.length).toFixed(1)),
203
- toolSuccessRate: Number(avgSuccessRate.toFixed(2)),
253
+ avgToolFailures: Number((outcomeTotals.failures / withTools.length).toFixed(1)),
254
+ avgToolCancelled: Number((outcomeTotals.cancelled / withTools.length).toFixed(1)),
255
+ avgToolUnknown: Number((outcomeTotals.unknown / withTools.length).toFixed(1)),
256
+ ...(outcomeTotals.comparable > 0 && {
257
+ toolSuccessRate: Number((outcomeTotals.successes / outcomeTotals.comparable).toFixed(2)),
258
+ }),
204
259
  toolDistribution: dist,
205
260
  };
206
261
  })(),
@@ -165,11 +165,15 @@ export declare function medianStabilityCV(report: Report): {
165
165
  runs: number;
166
166
  cv: number;
167
167
  } | null;
168
+ interface VerdictTextOptions {
169
+ verbose?: boolean;
170
+ lang?: Lang;
171
+ /** Managed skill NAME from `omk list`; omitted when the formatter cannot know it safely. */
172
+ promoteTarget?: string;
173
+ }
168
174
  /**
169
175
  * Plain-text formatter for the `omk eval` verdict. Stays terse for the
170
176
  * spec — one verdict, rationale bullets, one ship recommendation, and one next step.
171
177
  */
172
- export declare function formatVerdictText(result: VerdictResult, options?: {
173
- verbose?: boolean;
174
- lang?: Lang;
175
- }): string;
178
+ export declare function formatVerdictText(result: VerdictResult, options?: VerdictTextOptions): string;
179
+ export {};
@@ -31,6 +31,7 @@ import { evaluateLayerGates } from './layer-gates.js';
31
31
  import { ciLevelLabel } from './bootstrap.js';
32
32
  import { analyzeJudgeIndependence } from './judge-independence.js';
33
33
  import { MIN_HOLDOUT_SUBSET } from './holdout.js';
34
+ import { shellQuoteArg } from '../shared/shell-quote.js';
34
35
  /**
35
36
  * Below this sample count a non-significant diff is read as UNDERPOWERED
36
37
  * (only large effects are detectable) rather than NOISE. Matches the
@@ -584,13 +585,26 @@ function recommendation(level, _perPair, lang = 'en') {
584
585
  return 'ADD A CONTROL — single-variant report. Re-run with --control baseline --treatment <name>.';
585
586
  }
586
587
  }
587
- function releaseNextStep(level, lang) {
588
+ function nextStepTreatmentName(result) {
589
+ if (result.representative?.treatment)
590
+ return result.representative.treatment;
591
+ if (result.level === 'SOLO')
592
+ return result.variants[0] ?? '<name>';
593
+ return result.variants[1] ?? '<name>';
594
+ }
595
+ function promoteCommand(target) {
596
+ return target ? `omk promote ${shellQuoteArg(target)}` : 'omk promote <name>';
597
+ }
598
+ function releaseNextStep(result, lang, promoteTarget) {
599
+ const treatment = nextStepTreatmentName(result);
600
+ const treatmentArg = shellQuoteArg(treatment);
601
+ const promote = promoteCommand(promoteTarget);
588
602
  if (lang === 'zh') {
589
- switch (level) {
603
+ switch (result.level) {
590
604
  case 'PROGRESS':
591
- return '可以进入发布流程:请留存本次报告作为发布证据;如果这是受管 skill,再运行 `omk promote`。';
605
+ return `可以发布:按你的正常发布流程发布,并留存本次报告作为发布证据;如果这是受管 skill,继续运行 \`${promote}\` 记录接受决定(name 以 \`omk list\` 的 NAME 为准)。`;
592
606
  case 'CAUTIOUS':
593
- return '先看触发的告警(分层门控、评委分歧、稳定性或 holdout),修完再重跑。';
607
+ return '不要直接发布;先看触发的告警(分层门控、评委分歧、稳定性或 holdout),修完再重跑。';
594
608
  case 'REGRESS':
595
609
  return '不要发布;定位最差层和失败用例,修复后重跑。';
596
610
  case 'NOISE':
@@ -598,14 +612,14 @@ function releaseNextStep(level, lang) {
598
612
  case 'UNDERPOWERED':
599
613
  return '把样本数加到至少 20,或先按当前规模 2× 扩充后重跑。';
600
614
  case 'SOLO':
601
- return '补一个 baseline 对照,再跑 `omk eval --control baseline --treatment <名字>`。';
615
+ return `补一个 baseline 对照,再跑 \`omk eval --control baseline --treatment ${treatmentArg}\`。`;
602
616
  }
603
617
  }
604
- switch (level) {
618
+ switch (result.level) {
605
619
  case 'PROGRESS':
606
- return 'ready for release: keep this report as release evidence; for a managed skill, run `omk promote`.';
620
+ return `ship through your normal release path and keep this report as release evidence; for a managed skill, run \`${promote}\` to record acceptance (name is the NAME from \`omk list\`).`;
607
621
  case 'CAUTIOUS':
608
- return 'inspect the warnings (layer gates, judge dissent, stability, or holdout), fix them, then re-run.';
622
+ return 'do not ship directly; inspect the warnings (layer gates, judge dissent, stability, or holdout), fix them, then re-run.';
609
623
  case 'REGRESS':
610
624
  return 'do not ship; inspect the weakest layer and failing samples, fix them, then re-run.';
611
625
  case 'NOISE':
@@ -613,7 +627,7 @@ function releaseNextStep(level, lang) {
613
627
  case 'UNDERPOWERED':
614
628
  return 'increase the sample set to at least 20, or roughly 2x the current size, then re-run.';
615
629
  case 'SOLO':
616
- return 'add a baseline control and re-run `omk eval --control baseline --treatment <name>`.';
630
+ return `add a baseline control and re-run \`omk eval --control baseline --treatment ${treatmentArg}\`.`;
617
631
  }
618
632
  }
619
633
  /**
@@ -642,7 +656,7 @@ export function formatVerdictText(result, options = {}) {
642
656
  lines.push(zh ? ` 知识缺口:${result.rationale.gapSignal}` : ` Gap signal: ${result.rationale.gapSignal}`);
643
657
  if (result.rationale.shipRecommendation) {
644
658
  lines.push(` ${zh ? recommendation(result.level, [], 'zh') : result.rationale.shipRecommendation}`);
645
- lines.push(zh ? ` 下一步:${releaseNextStep(result.level, 'zh')}` : ` Next: ${releaseNextStep(result.level, 'en')}`);
659
+ lines.push(zh ? ` 下一步:${releaseNextStep(result, 'zh', options.promoteTarget)}` : ` Next: ${releaseNextStep(result, 'en', options.promoteTarget)}`);
646
660
  }
647
661
  if (options.verbose && result.perPair && result.perPair.length > 1) {
648
662
  lines.push('');
@@ -58,7 +58,7 @@ export declare function buildBatchVariantSpecs(entry: {
58
58
  name: string;
59
59
  skillPath: string;
60
60
  }, variantAllowedSkills?: Record<string, string[]>): VariantSpec[];
61
- export declare function buildBatchEvaluationReport({ batchRunId, skillDir, skillEntries, skillResults, model, judgeModel, noJudge, executorName, judgeExecutorName, project, owner, tags, concurrency, timeoutMs, totalCostUSD, repeat, judgeModels, noCache, bootstrap, bootstrapSamples, lengthDebias, budget, }: {
61
+ export declare function buildBatchEvaluationReport({ batchRunId, skillDir, skillEntries, skillResults, model, judgeModel, noJudge, executorName, judgeExecutorName, project, owner, tags, concurrency, timeoutMs, totalCostUSD, repeat, judgeModels, noCache, bootstrap, bootstrapSamples, lengthDebias, budget, strictBaseline, }: {
62
62
  batchRunId: string;
63
63
  skillDir: string;
64
64
  skillEntries: Array<{
@@ -85,6 +85,7 @@ export declare function buildBatchEvaluationReport({ batchRunId, skillDir, skill
85
85
  bootstrapSamples?: number;
86
86
  lengthDebias?: boolean;
87
87
  budget?: import('../types/index.js').EvalBudget;
88
+ strictBaseline?: boolean;
88
89
  }): {
89
90
  report: BatchEvaluationReport;
90
91
  job: import('../types/index.js').EvaluationJob;