oh-my-knowledge 0.40.0 → 0.41.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 (61) hide show
  1. package/README.md +1 -2
  2. package/README.zh.md +1 -2
  3. package/dist/analysis/report-diagnostics.js +27 -0
  4. package/dist/assets/agent-skills/omk/references/commands.md +1 -2
  5. package/dist/authoring/generator.js +3 -2
  6. package/dist/cli/commands/eval/index.d.ts +0 -1
  7. package/dist/cli/commands/eval/index.js +10 -10
  8. package/dist/cli/lib/cmd-flags.d.ts +0 -1
  9. package/dist/cli/lib/i18n-dict/run.js +2 -2
  10. package/dist/cli/lib/parse-run-config.d.ts +0 -1
  11. package/dist/cli/lib/parse-run-config.js +0 -2
  12. package/dist/eval-core/evaluation-job.d.ts +1 -2
  13. package/dist/eval-core/evaluation-job.js +1 -2
  14. package/dist/eval-core/evaluation-reporting.d.ts +0 -1
  15. package/dist/eval-core/evaluation-reporting.js +2 -38
  16. package/dist/eval-core/execution-strategy.js +3 -2
  17. package/dist/eval-core/judge-independence.d.ts +28 -0
  18. package/dist/eval-core/judge-independence.js +29 -0
  19. package/dist/eval-core/verdict.d.ts +9 -1
  20. package/dist/eval-core/verdict.js +43 -5
  21. package/dist/eval-workflows/batch-evaluation-workflow.d.ts +1 -1
  22. package/dist/eval-workflows/batch-evaluation-workflow.js +1 -2
  23. package/dist/eval-workflows/evaluation-pipeline/report-finalize.d.ts +1 -5
  24. package/dist/eval-workflows/evaluation-pipeline/report-finalize.js +1 -8
  25. package/dist/eval-workflows/evaluation-pipeline/run-state.d.ts +1 -2
  26. package/dist/eval-workflows/evaluation-pipeline/run-state.js +1 -2
  27. package/dist/eval-workflows/evaluation-pipeline.d.ts +1 -2
  28. package/dist/eval-workflows/evaluation-pipeline.js +1 -3
  29. package/dist/eval-workflows/run-evaluation.d.ts +2 -3
  30. package/dist/eval-workflows/run-evaluation.js +1 -2
  31. package/dist/executors/claude-cli.js +5 -6
  32. package/dist/executors/claude-sdk.d.ts +5 -2
  33. package/dist/executors/claude-sdk.js +13 -8
  34. package/dist/executors/codex-cli.js +3 -4
  35. package/dist/executors/shared.d.ts +2 -0
  36. package/dist/executors/shared.js +15 -0
  37. package/dist/grading/assertions.js +6 -122
  38. package/dist/grading/gold-cli.js +1 -1
  39. package/dist/grading/human-gold.d.ts +5 -3
  40. package/dist/grading/human-gold.js +5 -3
  41. package/dist/grading/index.d.ts +4 -4
  42. package/dist/grading/judge.d.ts +6 -14
  43. package/dist/grading/judge.js +5 -88
  44. package/dist/inputs/eval-config.js +6 -2
  45. package/dist/managed/evidence.js +1 -2
  46. package/dist/managed/version-scores.js +1 -1
  47. package/dist/renderer/html-renderer.js +0 -9
  48. package/dist/renderer/layout.js +4 -4
  49. package/dist/renderer/summary.js +23 -1
  50. package/dist/shared/llm-prompts/debias-instructions.d.ts +4 -0
  51. package/dist/shared/llm-prompts/debias-instructions.js +44 -0
  52. package/dist/shared/llm-prompts/judge-prompts.d.ts +30 -0
  53. package/dist/shared/llm-prompts/judge-prompts.js +205 -0
  54. package/dist/shared/llm-prompts/registry.d.ts +27 -0
  55. package/dist/shared/llm-prompts/registry.js +69 -0
  56. package/dist/types/eval.d.ts +8 -8
  57. package/dist/types/judge.d.ts +1 -1
  58. package/dist/types/report.d.ts +1 -3
  59. package/package.json +1 -1
  60. package/dist/grading/debias-validate.d.ts +0 -83
  61. package/dist/grading/debias-validate.js +0 -176
@@ -22,7 +22,7 @@ export interface EvaluationRunState {
22
22
  runningJob: EvaluationJob;
23
23
  resolvedJobStore: JobStore | null;
24
24
  }
25
- export declare function initializeEvaluationRunState({ samplesPath, skillDir, artifacts, model, judgeModel, noJudge, executorName, judgeExecutorName, concurrency, timeoutMs, noCache, blind, project, owner, tags, runId, jobStore, persistJob, repeat, batch, judgeRepeat, judgeModels, bootstrap, bootstrapSamples, lengthDebias, budget, effort, }: {
25
+ export declare function initializeEvaluationRunState({ samplesPath, skillDir, artifacts, model, judgeModel, noJudge, executorName, judgeExecutorName, concurrency, timeoutMs, noCache, project, owner, tags, runId, jobStore, persistJob, repeat, batch, judgeRepeat, judgeModels, bootstrap, bootstrapSamples, lengthDebias, budget, effort, }: {
26
26
  samplesPath: string;
27
27
  skillDir: string;
28
28
  artifacts: Artifact[];
@@ -34,7 +34,6 @@ export declare function initializeEvaluationRunState({ samplesPath, skillDir, ar
34
34
  concurrency: number;
35
35
  timeoutMs?: number;
36
36
  noCache: boolean;
37
- blind: boolean;
38
37
  project?: string;
39
38
  owner?: string;
40
39
  tags?: string[];
@@ -14,7 +14,7 @@
14
14
  import { buildEvaluationRequest, createFailedJob, createEvaluationRun, createQueuedJob, createSucceededJob, finalizeEvaluationRun, markJobRunning, failEvaluationRun, } from '../../eval-core/evaluation-job.js';
15
15
  import { createFileJobStore } from '../../server/job-store.js';
16
16
  import { DEFAULT_JOBS_DIR } from '../../eval-core/default-dirs.js';
17
- export async function initializeEvaluationRunState({ samplesPath, skillDir, artifacts, model, judgeModel, noJudge, executorName, judgeExecutorName, concurrency, timeoutMs, noCache, blind, project, owner, tags, runId, jobStore, persistJob, repeat, batch, judgeRepeat, judgeModels, bootstrap, bootstrapSamples, lengthDebias, budget, effort, }) {
17
+ export async function initializeEvaluationRunState({ samplesPath, skillDir, artifacts, model, judgeModel, noJudge, executorName, judgeExecutorName, concurrency, timeoutMs, noCache, project, owner, tags, runId, jobStore, persistJob, repeat, batch, judgeRepeat, judgeModels, bootstrap, bootstrapSamples, lengthDebias, budget, effort, }) {
18
18
  const effectiveJudges = judgeModels && judgeModels.length > 0
19
19
  ? judgeModels
20
20
  : [{ executor: judgeExecutorName, model: judgeModel }];
@@ -29,7 +29,6 @@ export async function initializeEvaluationRunState({ samplesPath, skillDir, arti
29
29
  timeoutMs,
30
30
  noCache,
31
31
  dryRun: false,
32
- blind,
33
32
  project,
34
33
  owner,
35
34
  tags,
@@ -47,7 +47,6 @@ export interface EvaluationPipelineOptions {
47
47
  project?: string;
48
48
  owner?: string;
49
49
  tags?: string[];
50
- blind?: boolean;
51
50
  concurrency?: number;
52
51
  timeoutMs?: number;
53
52
  noCache?: boolean;
@@ -90,7 +89,7 @@ export interface EvaluationPipelineOptions {
90
89
  noDiagnostic?: boolean;
91
90
  }
92
91
  type VariantResult = import('../types/index.js').VariantResult;
93
- export declare function executeEvaluationPipeline({ samplesPath, samplesBaseDir, samplesSourceFiles, skillDir, samples, tasks, artifacts, model, judgeModel, noJudge, executorName, judgeExecutorName, executor, judgeExecutor, outputDir, project, owner, tags, blind, concurrency, timeoutMs, noCache, jobStore, persistJob, onProgress, skipConnectivity, verbose, retry, existingResults, requires: _requires, layeredStats, repeat, batch, judgeRepeat, judgeModels, bootstrap, bootstrapSamples, lengthDebias, budget, strictBaseline, runId, lang, effort, noDiagnostic, }: EvaluationPipelineOptions): Promise<{
92
+ export declare function executeEvaluationPipeline({ samplesPath, samplesBaseDir, samplesSourceFiles, skillDir, samples, tasks, artifacts, model, judgeModel, noJudge, executorName, judgeExecutorName, executor, judgeExecutor, outputDir, project, owner, tags, concurrency, timeoutMs, noCache, jobStore, persistJob, onProgress, skipConnectivity, verbose, retry, existingResults, requires: _requires, layeredStats, repeat, batch, judgeRepeat, judgeModels, bootstrap, bootstrapSamples, lengthDebias, budget, strictBaseline, runId, lang, effort, noDiagnostic, }: EvaluationPipelineOptions): Promise<{
94
93
  report: Report;
95
94
  filePath: string | null;
96
95
  }>;
@@ -30,7 +30,7 @@ import { emitIsolationWarnings, emitPowerWarnings } from './evaluation-pipeline/
30
30
  // 兼容 re-export:测试与 run-evaluation.ts 动态 import 仍打 evaluation-pipeline.js
31
31
  export { buildPowerWarnings, buildIsolationWarnings } from './evaluation-pipeline/preflight-warnings.js';
32
32
  export { _computeTestSetHashForTest } from './evaluation-pipeline/test-set-hash.js';
33
- export async function executeEvaluationPipeline({ samplesPath, samplesBaseDir, samplesSourceFiles, skillDir, samples, tasks, artifacts, model, judgeModel, noJudge, executorName, judgeExecutorName, executor, judgeExecutor, outputDir = DEFAULT_OUTPUT_DIR, project, owner, tags, blind = false, concurrency = 1, timeoutMs, noCache = false, jobStore = null, persistJob = true, onProgress = null, skipConnectivity = false, verbose = false, retry = 0, existingResults,
33
+ export async function executeEvaluationPipeline({ samplesPath, samplesBaseDir, samplesSourceFiles, skillDir, samples, tasks, artifacts, model, judgeModel, noJudge, executorName, judgeExecutorName, executor, judgeExecutor, outputDir = DEFAULT_OUTPUT_DIR, project, owner, tags, concurrency = 1, timeoutMs, noCache = false, jobStore = null, persistJob = true, onProgress = null, skipConnectivity = false, verbose = false, retry = 0, existingResults,
34
34
  // requires 现在由 runEvaluation 上游传给 doctor 处理; eval-pipeline 不再用
35
35
  // 但保留接口字段,避免破 programmatic API (类型层面接收, 内部忽略)
36
36
  requires: _requires, layeredStats = false, repeat, batch, judgeRepeat, judgeModels, bootstrap, bootstrapSamples, lengthDebias = true, budget, strictBaseline, runId, lang = 'zh', effort, noDiagnostic, }) {
@@ -47,7 +47,6 @@ requires: _requires, layeredStats = false, repeat, batch, judgeRepeat, judgeMode
47
47
  concurrency,
48
48
  timeoutMs,
49
49
  noCache,
50
- blind,
51
50
  project,
52
51
  owner,
53
52
  tags,
@@ -153,7 +152,6 @@ requires: _requires, layeredStats = false, repeat, batch, judgeRepeat, judgeMode
153
152
  results,
154
153
  artifacts,
155
154
  variantNames,
156
- blind,
157
155
  samplesPath,
158
156
  samplesSourceFiles,
159
157
  samples,
@@ -46,7 +46,7 @@ interface CommonEvaluationOptions {
46
46
  bootstrap?: boolean;
47
47
  /** --bootstrap-samples N. Default 1000. */
48
48
  bootstrapSamples?: number;
49
- /** length-debias toggle. Default true (judge prompt v3-cot-length).
49
+ /** length-debias toggle. Default true (length-debias instruction on).
50
50
  * CLI passes false when --no-debias-length is set. */
51
51
  lengthDebias?: boolean;
52
52
  /** hard budget caps. */
@@ -64,7 +64,6 @@ export interface RunEvaluationOptions extends CommonEvaluationOptions {
64
64
  skillDir: string;
65
65
  variantSpecs?: VariantSpec[];
66
66
  dryRun?: boolean;
67
- blind?: boolean;
68
67
  retry?: number;
69
68
  resume?: string;
70
69
  /** Explicit persisted run id. Used by batch workflows that need stable child ids. */
@@ -118,7 +117,7 @@ export interface DryRunReport extends DryRunBase {
118
117
  samplesPath: string;
119
118
  tasks: DryRunTask[];
120
119
  }
121
- export declare function runEvaluation({ samplesPath, skillDir, variantSpecs, model, outputDir, project, owner, tags, noJudge, dryRun, blind, concurrency, timeoutMs, noCache, executorName, jobStore, persistJob, onProgress, skipConnectivity, skipDoctor, lang, mcpConfig, verbose, retry, resume, runId, layeredStats, repeat, batch, judgeRepeat, judgeModels, bootstrap, bootstrapSamples, lengthDebias, budget, strictBaseline, effort, noDiagnostic, }: RunEvaluationOptions): Promise<{
120
+ export declare function runEvaluation({ samplesPath, skillDir, variantSpecs, model, outputDir, project, owner, tags, noJudge, dryRun, concurrency, timeoutMs, noCache, executorName, jobStore, persistJob, onProgress, skipConnectivity, skipDoctor, lang, mcpConfig, verbose, retry, resume, runId, layeredStats, repeat, batch, judgeRepeat, judgeModels, bootstrap, bootstrapSamples, lengthDebias, budget, strictBaseline, effort, noDiagnostic, }: RunEvaluationOptions): Promise<{
122
121
  report: Report | DryRunReport;
123
122
  filePath: string | null;
124
123
  }>;
@@ -8,7 +8,7 @@ import { buildDryRunTaskReport, prepareEvaluationRun, } from './evaluation-prepa
8
8
  import { executeEvaluationPipeline } from './evaluation-pipeline.js';
9
9
  import { findSaturationPoint } from '../analysis/saturation.js';
10
10
  import { bootstrapMeanCI, DEFAULT_BOOTSTRAP_ALPHA, DEFAULT_BOOTSTRAP_SAMPLES } from '../eval-core/bootstrap.js';
11
- export async function runEvaluation({ samplesPath, skillDir, variantSpecs = [], model = DEFAULT_MODEL, outputDir = DEFAULT_OUTPUT_DIR, project, owner, tags, noJudge = false, dryRun = false, blind = false, concurrency = 1, timeoutMs, noCache = false, executorName = 'claude', jobStore = null, persistJob = true, onProgress = null, skipConnectivity = false, skipDoctor = false, lang = 'zh', mcpConfig, verbose = false, retry = 0, resume, runId, layeredStats = false, repeat, batch, judgeRepeat, judgeModels, bootstrap, bootstrapSamples, lengthDebias, budget, strictBaseline, effort, noDiagnostic, }) {
11
+ export async function runEvaluation({ samplesPath, skillDir, variantSpecs = [], model = DEFAULT_MODEL, outputDir = DEFAULT_OUTPUT_DIR, project, owner, tags, noJudge = false, dryRun = false, concurrency = 1, timeoutMs, noCache = false, executorName = 'claude', jobStore = null, persistJob = true, onProgress = null, skipConnectivity = false, skipDoctor = false, lang = 'zh', mcpConfig, verbose = false, retry = 0, resume, runId, layeredStats = false, repeat, batch, judgeRepeat, judgeModels, bootstrap, bootstrapSamples, lengthDebias, budget, strictBaseline, effort, noDiagnostic, }) {
12
12
  // Unified judgeModels → derive single-judge fields for downstream pipeline / grading
13
13
  // (which still operate on string `judgeModel` + `judgeExecutorName` fields per call).
14
14
  const effectiveJudgeModels = judgeModels && judgeModels.length > 0
@@ -152,7 +152,6 @@ export async function runEvaluation({ samplesPath, skillDir, variantSpecs = [],
152
152
  project,
153
153
  owner,
154
154
  tags,
155
- blind,
156
155
  concurrency,
157
156
  timeoutMs,
158
157
  noCache,
@@ -2,20 +2,19 @@ import { extractAgentTrace, isClaudeSdkResultMessage } from './claude-sdk-trace.
2
2
  import { buildExecEnv, DEFAULT_TIMEOUT_MS, errorMessage, interruptedExecResult, MAX_BUFFER, spawnWithSigintPropagation, timeoutExecResult, } from './shared.js';
3
3
  import { materializeForCliConfigDir } from '../eval-core/mocks-runtime.js';
4
4
  // claude CLI 用 `--disable-slash-commands` (文档:"Disable all skills") +
5
- // `--disallowedTools Skill` 实现与 SDK 等价的完全隔离。但 CLI 没有 partial whitelist
6
- // flag,所以 [name1, ...] 必须 throw。
5
+ // `--disallowedTools Skill` 实现与 SDK 等价的完全隔离。非空 skill 白名单不再支持
6
+ // (它无法真正隔离,见 claude-sdk.ts buildSdkIsolationOptions),所以 [name1, ...] 必须 throw。
7
7
  //
8
8
  // undefined → 不传任何 flag(原行为,全发现)
9
9
  // [] → --disable-slash-commands + --disallowedTools Skill
10
10
  // (main session skill discovery + subagent Skill 工具调用都堵)
11
- // [...] (length > 0) → throw,提示用 claude-sdk executor 走精准白名单
11
+ // [...] (length > 0) → throw,非空白名单已移除
12
12
  function applySkillIsolationToCliArgs(args, allowedSkills) {
13
13
  if (allowedSkills === undefined)
14
14
  return;
15
15
  if (allowedSkills.length > 0) {
16
- throw new Error(`claude-cli executor 不支持 partial skill 白名单(allowedSkills=${JSON.stringify(allowedSkills)})。\n`
17
- + ` 仅支持 [](映射为 --disable-slash-commands + --disallowedTools Skill)或 undefined(默认)。\n`
18
- + ` 精确白名单请改用 --executor claude-sdk(SDK skills option pass-through)。`);
16
+ throw new Error(`skill 白名单(allowedSkills=${JSON.stringify(allowedSkills)})不再支持:非空白名单无法真正隔离。\n`
17
+ + ` 仅支持 [](映射为 --disable-slash-commands + --disallowedTools Skill,全封死)或 undefined(不隔离)。`);
19
18
  }
20
19
  // 完全隔离:双堵 main session skill 发现 + subagent Skill 工具
21
20
  args.push('--disable-slash-commands', '--disallowedTools', 'Skill');
@@ -3,8 +3,11 @@ import type { ExecResult, ExecutorInput } from '../types/index.js';
3
3
  * Map ExecutorInput.allowedSkills to SDK query options for skill isolation.
4
4
  * undefined → {} (SDK default: full ~/.claude/skills/ discovery)
5
5
  * [] → { skills: [], disallowedTools: ['Skill'] } (main session + subagent 双堵)
6
- * [...] → { skills: [...] } (main session whitelist; subagent 走独立 channel,
7
- * 白名单场景 v1 不强制 subagent 跟随)
6
+ * [...] → throw(非空 skill 白名单不再支持:它从不能真隔离 —— 主会话 skill 发现虽被
7
+ * `skills:[...]` 收窄,但子代理 Skill 工具与 cwd 文件系统两条 channel 封不住,
8
+ * 会产出看着干净、实则被白名单外 skill 污染的报告。隔离只留两档:undefined
9
+ * (不隔离)与 [](全封死),与 claude-cli / codex-cli 一致;多 skill 组合实验
10
+ * 请控制评测环境而非靠白名单。)
8
11
  *
9
12
  * Exported for unit tests to lock the option-shape contract.
10
13
  */
@@ -9,8 +9,11 @@ let sdkQuery = null;
9
9
  * Map ExecutorInput.allowedSkills to SDK query options for skill isolation.
10
10
  * undefined → {} (SDK default: full ~/.claude/skills/ discovery)
11
11
  * [] → { skills: [], disallowedTools: ['Skill'] } (main session + subagent 双堵)
12
- * [...] → { skills: [...] } (main session whitelist; subagent 走独立 channel,
13
- * 白名单场景 v1 不强制 subagent 跟随)
12
+ * [...] → throw(非空 skill 白名单不再支持:它从不能真隔离 —— 主会话 skill 发现虽被
13
+ * `skills:[...]` 收窄,但子代理 Skill 工具与 cwd 文件系统两条 channel 封不住,
14
+ * 会产出看着干净、实则被白名单外 skill 污染的报告。隔离只留两档:undefined
15
+ * (不隔离)与 [](全封死),与 claude-cli / codex-cli 一致;多 skill 组合实验
16
+ * 请控制评测环境而非靠白名单。)
14
17
  *
15
18
  * Exported for unit tests to lock the option-shape contract.
16
19
  */
@@ -19,7 +22,8 @@ export function buildSdkIsolationOptions(allowedSkills) {
19
22
  return {};
20
23
  if (allowedSkills.length === 0)
21
24
  return { skills: allowedSkills, disallowedTools: ['Skill'] };
22
- return { skills: allowedSkills };
25
+ throw new Error(`skill 白名单(allowedSkills=${JSON.stringify(allowedSkills)})不再支持:非空白名单无法真正隔离`
26
+ + `(子代理 Skill 工具 + cwd 文件系统两条 channel 封不住)。仅支持 [](全封死)或 undefined(不隔离)。`);
23
27
  }
24
28
  async function getSdkQuery() {
25
29
  if (!sdkQuery) {
@@ -29,6 +33,12 @@ async function getSdkQuery() {
29
33
  return sdkQuery;
30
34
  }
31
35
  export async function claudeSdkExecutor({ model, system, prompt, cwd, skillDir, timeoutMs = DEFAULT_TIMEOUT_MS, verbose = false, allowedSkills, mocks, mocksBaseDir, mocksStrict, lean, effort }) {
36
+ // 隔离选项在 timer / try 之前解析:非空 allowedSkills(不再支持的 skill 白名单)必须在这里
37
+ // fail-fast 抛错,而不是被下面的 catch 吞成 ok:false 的 ExecResult(那会把配置错误伪装成
38
+ // 每个 sample 执行失败、产出全失败报告,与 claude-cli / codex 的硬抛口径不一致)。lean 仍
39
+ // 覆盖为硬堵,但 buildSdkIsolationOptions 先跑一遍,故 lean 也无法绕过非空校验。
40
+ const baseIsolationOpts = buildSdkIsolationOptions(allowedSkills);
41
+ const isolationOpts = lean ? { skills: [], disallowedTools: ['*'] } : baseIsolationOpts;
32
42
  const start = Date.now();
33
43
  const abortController = new AbortController();
34
44
  const timer = setTimeout(() => abortController.abort(), timeoutMs);
@@ -45,11 +55,6 @@ export async function claudeSdkExecutor({ model, system, prompt, cwd, skillDir,
45
55
  const mockStatsOf = () => hookHandle ? { ...hookHandle.stats } : undefined;
46
56
  try {
47
57
  const query = await getSdkQuery();
48
- // lean 模式:纯文本生成路径,不需要工具循环 / skill 发现,
49
- // 用 disallowedTools:['*'] + skills:[] 直接堵住,优先级高于 isolationOpts。
50
- const isolationOpts = lean
51
- ? { skills: [], disallowedTools: ['*'] }
52
- : buildSdkIsolationOptions(allowedSkills);
53
58
  // effort:lean 强制 'low'(生成路径不需要思考),否则透传调用方传入。
54
59
  // SDK 暴露 EffortLevel = 'low' | 'medium' | 'high' | 'xhigh' | 'max',直接对应。
55
60
  const effectiveEffort = lean ? 'low' : effort;
@@ -11,14 +11,13 @@ import { buildExecEnv, DEFAULT_TIMEOUT_MS, errorMessage, interruptedExecResult,
11
11
  // undefined → 不传 -C(原行为,看 cwd 里有什么 codex 自己决定)
12
12
  // [] → 必须提供 cwd 非空(否则 throw),caller 应传一个
13
13
  // isolated 空目录(如 ~/.oh-my-knowledge/state/isolated-cwd/)
14
- // [...] (length>0) → throw,codex CLI 没有 partial 白名单 flag
14
+ // [...] (length>0) → throw,非空白名单已移除(无法真正隔离)
15
15
  export function isolateCodexCwd(allowedSkills, cwd, executorName = 'codex-cli') {
16
16
  if (allowedSkills === undefined)
17
17
  return;
18
18
  if (allowedSkills.length > 0) {
19
- throw new Error(`${executorName} executor 不支持 partial skill 白名单(allowedSkills=${JSON.stringify(allowedSkills)})。\n`
20
- + ` 仅支持 [](强制 cwd 隔离,需提供 cwd 非空)或 undefined(默认)。\n`
21
- + ` codex CLI 无 partial 白名单 flag,请改用其他 executor 或显式 cwd 隔离。`);
19
+ throw new Error(`skill 白名单(allowedSkills=${JSON.stringify(allowedSkills)})不再支持:非空白名单无法真正隔离。\n`
20
+ + ` 仅支持 [](强制 cwd 隔离,需提供 cwd 非空,全封死)或 undefined(不隔离)。`);
22
21
  }
23
22
  // allowedSkills === [] 时必须有 cwd(channel 3 cwd 隔离是 codex 唯一 channel)
24
23
  if (!cwd) {
@@ -5,6 +5,8 @@ export declare const DEFAULT_MODEL = "sonnet";
5
5
  export declare const JUDGE_MODEL = "haiku";
6
6
  export declare const DEFAULT_TIMEOUT_MS = 600000;
7
7
  export declare const MAX_BUFFER: number;
8
+ export type ExecutorVendor = 'anthropic' | 'openai' | 'google' | 'unknown';
9
+ export declare function executorVendor(executor: string): ExecutorVendor;
8
10
  export interface TokenUsage {
9
11
  input_tokens?: number;
10
12
  output_tokens?: number;
@@ -9,6 +9,21 @@ export const DEFAULT_MODEL = 'sonnet';
9
9
  export const JUDGE_MODEL = 'haiku';
10
10
  export const DEFAULT_TIMEOUT_MS = 600_000;
11
11
  export const MAX_BUFFER = 10 * 1024 * 1024;
12
+ // 把 executor 名归到模型厂商家族,用于评委独立性判断(同厂商评委 = 自我偏好敞口)。
13
+ // codex 是 OpenAI 的代码模型,故 codex* 归 OpenAI。自定义 script executor 无法归类 → unknown
14
+ // (调用方据此跳过判断,不误报)。
15
+ const EXECUTOR_VENDOR = {
16
+ claude: 'anthropic',
17
+ 'claude-sdk': 'anthropic',
18
+ 'anthropic-api': 'anthropic',
19
+ codex: 'openai',
20
+ 'codex-sdk': 'openai',
21
+ 'openai-api': 'openai',
22
+ gemini: 'google',
23
+ };
24
+ export function executorVendor(executor) {
25
+ return EXECUTOR_VENDOR[executor] ?? 'unknown';
26
+ }
12
27
  export function asErrorLike(err) {
13
28
  return typeof err === 'object' && err !== null ? err : {};
14
29
  }
@@ -1,6 +1,7 @@
1
1
  import { resolve } from 'node:path';
2
2
  import _Ajv from 'ajv';
3
3
  import { ASSERTION_LAYER } from './layered-scores.js';
4
+ import { buildSemanticSimilarityPrompt, SEMANTIC_SIMILARITY_SYSTEM, buildRagJudgePrompt } from '../shared/llm-prompts/judge-prompts.js';
4
5
  const Ajv = _Ajv.default ?? _Ajv;
5
6
  const ajv = new Ajv();
6
7
  const CUSTOM_ASSERTION_TIMEOUT_MS = 30_000;
@@ -332,24 +333,10 @@ export async function runAsyncAssertions(output, assertions, { executor, judgeMo
332
333
  let message = '';
333
334
  if (assertion.type === 'semantic_similarity') {
334
335
  const reference = assertion.reference || '';
335
- const judgePrompt = [
336
- '请判断以下两段文本的语义相似度。',
337
- '',
338
- '## 参考文本',
339
- reference,
340
- '',
341
- '## 待评估文本',
342
- output,
343
- '',
344
- '请返回 JSON(不要包含 markdown 代码块标记):',
345
- '{"score": <1-5的整数>, "reason": "<简短理由>"}',
346
- '',
347
- '评分:1=完全无关, 2=略有关联, 3=部分相似, 4=大致相同, 5=高度一致',
348
- ].join('\n');
349
336
  const result = await executor({
350
337
  model: judgeModel,
351
- system: '你是语义相似度评审员。只返回 JSON,不要其他内容。',
352
- prompt: judgePrompt,
338
+ system: SEMANTIC_SIMILARITY_SYSTEM,
339
+ prompt: buildSemanticSimilarityPrompt(reference, output),
353
340
  });
354
341
  asyncCostUSD += result.costUSD || 0;
355
342
  if (result.costReportedByExecutor === false)
@@ -423,40 +410,6 @@ export async function runAsyncAssertions(output, assertions, { executor, judgeMo
423
410
  ...(anyCostUnreported && { judgeCostReportedByExecutor: false }),
424
411
  };
425
412
  }
426
- // ===========================================================================
427
- // RAG-specific judge metrics
428
- // ===========================================================================
429
- //
430
- // Three metrics, all running through the LLM judge:
431
- //
432
- // - faithfulness: does the output's content stay grounded in the
433
- // reference context? Anti-hallucination check.
434
- // - answer_relevancy: does the output directly answer the user's question?
435
- // Catches verbose dodges and topic drift.
436
- // - context_recall: are the key facts from the gold context actually
437
- // used in the output? Catches retrieved-but-ignored
438
- // context (a common RAG bug).
439
- //
440
- // Implementation notes:
441
- //
442
- // 1. Each prompt is a SINGLE-CALL judge (1-5 score) rather than the multi-step
443
- // statement-decomposition that RAGAS uses. This is honest tradeoff: simpler,
444
- // faster, less rigorous than RAGAS but consistent with omk's other LLM-judge
445
- // assertions. Users who need RAGAS-grade decomposition can drop down to a
446
- // custom assertion.
447
- // 2. The prompt includes the SAME length-debias paragraph as the main judge
448
- // prompt (v3-cot-length) — output verbosity is not a quality signal here
449
- // either. This is the "auto-inherit length-debias" claim from the plan.
450
- // 3. Reference resolution:
451
- // faithfulness: sample.context (or assertion.reference override)
452
- // context_recall: assertion.reference (or sample.context fallback)
453
- // answer_relevancy: sample.prompt — no reference needed
454
- // 4. Threshold defaults to 3 (same as semantic_similarity). User can override.
455
- const RAG_LENGTH_DEBIAS = [
456
- '## 重要:长度不是质量信号',
457
- '评分时聚焦内容实质,不要因输出更长就给更高分。',
458
- '简洁正确的回答与冗长正确的回答应得相同分数。',
459
- ].join('\n');
460
413
  async function runRagJudge(assertion, output, sample, executor, judgeModel) {
461
414
  const threshold = assertion.threshold ?? 3;
462
415
  let prompt;
@@ -466,55 +419,10 @@ async function runRagJudge(assertion, output, sample, executor, judgeModel) {
466
419
  if (!context) {
467
420
  return { passed: false, message: 'faithfulness: 缺少 sample.context 或 assertion.reference', costUSD: 0 };
468
421
  }
469
- system = '你是 RAG 评审员,专注判断输出是否被参考 context 支持。只返回 JSON。';
470
- prompt = [
471
- '请判断"待评估输出"中的事实性陈述是否被"参考 context"支持。',
472
- '',
473
- '## 参考 context',
474
- context,
475
- '',
476
- '## 待评估输出',
477
- output,
478
- '',
479
- RAG_LENGTH_DEBIAS,
480
- '',
481
- '## 评分流程',
482
- '1. 列出待评估输出中所有事实性陈述',
483
- '2. 逐条判断是否能在 context 中找到支持',
484
- '3. 给出 1-5 分:',
485
- ' 5 = 全部陈述都有 context 支持,无编造',
486
- ' 4 = 多数有支持,有 1-2 处不重要的编造',
487
- ' 3 = 一半有支持',
488
- ' 2 = 多数无支持',
489
- ' 1 = 完全编造或与 context 矛盾',
490
- '',
491
- '请返回 JSON(不要 markdown 代码块):',
492
- '{"score": <1-5的整数>, "reason": "<简短理由>"}',
493
- ].join('\n');
422
+ ({ system, prompt } = buildRagJudgePrompt('faithfulness', { output, context }));
494
423
  }
495
424
  else if (assertion.type === 'answer_relevancy') {
496
- system = '你是答题切题度评审员。只返回 JSON。';
497
- prompt = [
498
- '请判断"AI 输出"是否直接、切题地回答了"用户问题"。',
499
- '',
500
- '## 用户问题',
501
- sample.prompt,
502
- '',
503
- '## AI 输出',
504
- output,
505
- '',
506
- RAG_LENGTH_DEBIAS,
507
- '',
508
- '## 评分',
509
- '5 = 完整切题回答,无冗余无遗漏',
510
- '4 = 切题但有少量冗余或小遗漏',
511
- '3 = 部分切题,部分跑题或避而不答',
512
- '2 = 大部分跑题',
513
- '1 = 完全跑题或拒答',
514
- '',
515
- '请返回 JSON(不要 markdown 代码块):',
516
- '{"score": <1-5的整数>, "reason": "<简短理由>"}',
517
- ].join('\n');
425
+ ({ system, prompt } = buildRagJudgePrompt('answer_relevancy', { output, question: sample.prompt }));
518
426
  }
519
427
  else {
520
428
  // context_recall
@@ -522,31 +430,7 @@ async function runRagJudge(assertion, output, sample, executor, judgeModel) {
522
430
  if (!reference) {
523
431
  return { passed: false, message: 'context_recall: 缺少 assertion.reference 或 sample.context', costUSD: 0 };
524
432
  }
525
- system = '你是 context 覆盖率评审员。只返回 JSON。';
526
- prompt = [
527
- '请判断"参考 gold"中的关键事实在"AI 输出"中被覆盖的程度。',
528
- '',
529
- '## 参考 gold',
530
- reference,
531
- '',
532
- '## AI 输出',
533
- output,
534
- '',
535
- RAG_LENGTH_DEBIAS,
536
- '',
537
- '## 评分流程',
538
- '1. 列出参考中的关键事实(忽略修饰性内容)',
539
- '2. 检查每条是否在输出中被提及/使用',
540
- '3. 给出 1-5 分:',
541
- ' 5 = 全部关键事实被覆盖',
542
- ' 4 = 大部分覆盖,缺 1-2 条次要事实',
543
- ' 3 = 一半覆盖',
544
- ' 2 = 仅覆盖少量',
545
- ' 1 = 完全未覆盖',
546
- '',
547
- '请返回 JSON(不要 markdown 代码块):',
548
- '{"score": <1-5的整数>, "reason": "<简短理由>"}',
549
- ].join('\n');
433
+ ({ system, prompt } = buildRagJudgePrompt('context_recall', { output, reference }));
550
434
  }
551
435
  const result = await executor({ model: judgeModel, system, prompt });
552
436
  const reported = result.costReportedByExecutor === false ? { costReportedByExecutor: false } : {};
@@ -93,7 +93,7 @@ export function formatGoldCompare(result, gold) {
93
93
  lines.push(` 报告缺失: ${result.missing.join(', ')}`);
94
94
  return lines.join('\n');
95
95
  }
96
- lines.push(` Krippendorff α: ${fmt(a.alpha)} (主指标,序数加权)`);
96
+ lines.push(` Krippendorff α: ${fmt(a.alpha)} (主指标,区间加权)`);
97
97
  lines.push(` α 95% CI: [${fmt(a.alphaCI.low)}, ${fmt(a.alphaCI.high)}]`);
98
98
  lines.push(` 加权 κ: ${fmt(a.weightedKappa)} (副指标)`);
99
99
  lines.push(` Pearson r: ${fmt(a.pearson)} (仅查 rank order)`);
@@ -15,9 +15,11 @@
15
15
  *
16
16
  * Three metrics are exported:
17
17
  *
18
- * - **Krippendorff's α (interval)** — primary. Distribution-free, supports
19
- * ordinal/interval scales naturally, doesn't assume coders are exchangeable
20
- * (good fit when one "coder" is a model and the other a human annotator).
18
+ * - **Krippendorff's α (interval weights)** — primary. Distribution-free,
19
+ * doesn't assume coders are exchangeable (good fit when one "coder" is a model
20
+ * and the other a human annotator). Uses interval distance δ²=(c−k)² — a
21
+ * defensible choice for 1-5 Likert; an ordinal-distance variant would change α
22
+ * (BREAKING-COMPARABILITY) and is not implemented here.
21
23
  * - **Quadratic-weighted Cohen's κ** — secondary. Familiar to many readers,
22
24
  * useful as a sanity check. Reports lower than α when marginals diverge.
23
25
  * - **Pearson r** — tertiary. Captures rank-order agreement only; doesn't
@@ -15,9 +15,11 @@
15
15
  *
16
16
  * Three metrics are exported:
17
17
  *
18
- * - **Krippendorff's α (interval)** — primary. Distribution-free, supports
19
- * ordinal/interval scales naturally, doesn't assume coders are exchangeable
20
- * (good fit when one "coder" is a model and the other a human annotator).
18
+ * - **Krippendorff's α (interval weights)** — primary. Distribution-free,
19
+ * doesn't assume coders are exchangeable (good fit when one "coder" is a model
20
+ * and the other a human annotator). Uses interval distance δ²=(c−k)² — a
21
+ * defensible choice for 1-5 Likert; an ordinal-distance variant would change α
22
+ * (BREAKING-COMPARABILITY) and is not implemented here.
21
23
  * - **Quadratic-weighted Cohen's κ** — secondary. Familiar to many readers,
22
24
  * useful as a sanity check. Reports lower than α when marginals diverge.
23
25
  * - **Pearson r** — tertiary. Captures rank-order agreement only; doesn't
@@ -45,10 +45,10 @@ interface GradeOptions {
45
45
  */
46
46
  judgeRepeat?: number;
47
47
  /**
48
- * v0.21 length-debias toggle. Defaults to true — judge prompt includes the
49
- * "length is not a quality signal" instruction, prompt template version is
50
- * v3-cot-length. Set false (via `--no-debias-length`) to revert to the
51
- * legacy v2-cot prompt for reproducing historical reports.
48
+ * length-debias toggle. Defaults to true — judge prompt includes the
49
+ * "length is not a quality signal" instruction. Set false (via `--no-debias-length`)
50
+ * to drop it (the debias-off prompt variant) for reproducing older no-length-debias
51
+ * reports. The presentation/tone neutrality instruction is always on regardless.
52
52
  */
53
53
  lengthDebias?: boolean;
54
54
  }
@@ -1,13 +1,5 @@
1
1
  import type { DimensionResult, ExecutorFn, JudgeAgreement, JudgeConfig, ToolCallInfo, TurnInfo } from '../types/index.js';
2
- export declare function buildJudgePrompt(prompt: string, rubric: string, output: string, traceSummary: string | null, lengthDebias?: boolean): string;
3
- /**
4
- * Stable hash of the judge prompt template. Saved into ReportMeta.judgePromptHash so
5
- * downstream readers can detect "the judge prompt changed between these two reports".
6
- *
7
- * `lengthDebias` defaults to true (v0.21+ default). Pass false when running under
8
- * `--no-debias-length` so the hash matches historical v2-cot reports.
9
- */
10
- export declare function getJudgePromptHash(lengthDebias?: boolean): string;
2
+ export { buildJudgePrompt, getJudgePromptHash } from '../shared/llm-prompts/judge-prompts.js';
11
3
  interface LlmJudgeOptions {
12
4
  output: string;
13
5
  rubric: string;
@@ -17,9 +9,10 @@ interface LlmJudgeOptions {
17
9
  traceSummary?: string | null;
18
10
  /**
19
11
  * When true (default), the judge prompt includes an explicit
20
- * "length is not a quality signal" instruction. Pass false to fall back to
21
- * the legacy v2-cot prompt — only useful for reproducing pre-v0.21 reports
22
- * or running A/B comparisons with alternate length-debias settings.
12
+ * "length is not a quality signal" instruction. Pass false to drop it (the
13
+ * debias-off prompt variant) — only useful for reproducing older no-length-debias
14
+ * reports or running A/B comparisons with alternate length-debias settings.
15
+ * (The presentation/tone neutrality instruction is always on, independent of this.)
23
16
  */
24
17
  lengthDebias?: boolean;
25
18
  }
@@ -60,7 +53,7 @@ export declare function judgeId(config: JudgeConfig): string;
60
53
  export declare function computeJudgeAgreement(judgeScores: number[][]): JudgeAgreement;
61
54
  /**
62
55
  * Judge a single (output, rubric) pair with N judge models in parallel. Each judge
63
- * may use a different executor (e.g. claude:opus + openai:gpt-4o + gemini:pro). Each
56
+ * may use a different executor (e.g. claude:opus + openai-api:gpt-4o + gemini:pro). Each
64
57
  * judge can also be repeated `judgeRepeat` times — final per-judge score is its mean.
65
58
  *
66
59
  * Returns: aggregate DimensionResult (score = mean across judges; this is the "consensus"
@@ -72,4 +65,3 @@ export declare function computeJudgeAgreement(judgeScores: number[][]): JudgeAgr
72
65
  * provide the raw ensemble so downstream can recompute.
73
66
  */
74
67
  export declare function llmJudgeEnsemble(options: LlmJudgeOptions, judges: JudgeConfig[], executorByName: (name: string) => ExecutorFn, judgeRepeat?: number): Promise<DimensionResult>;
75
- export {};