oh-my-knowledge 0.38.0 → 0.40.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 (55) hide show
  1. package/dist/assets/agent-skills/omk/references/commands.md +2 -0
  2. package/dist/authoring/evolver.js +9 -5
  3. package/dist/cli/commands/list.d.ts +4 -1
  4. package/dist/cli/commands/list.js +12 -4
  5. package/dist/cli/commands/observe/index.d.ts +9 -0
  6. package/dist/cli/commands/observe/index.js +74 -1
  7. package/dist/cli/commands/sample.d.ts +13 -0
  8. package/dist/cli/commands/sample.js +113 -16
  9. package/dist/cli/lib/cmd-flags.d.ts +1 -0
  10. package/dist/cli/lib/i18n-dict/common.d.ts +1 -1
  11. package/dist/cli/lib/i18n-dict/common.js +8 -0
  12. package/dist/cli/lib/i18n-dict/gen.d.ts +1 -1
  13. package/dist/cli/lib/i18n-dict/gen.js +8 -0
  14. package/dist/cli/lib/i18n-dict/list.d.ts +1 -1
  15. package/dist/cli/lib/i18n-dict/list.js +4 -0
  16. package/dist/cli/lib/record-evolve-outcome.js +19 -10
  17. package/dist/eval-core/bootstrap.d.ts +53 -2
  18. package/dist/eval-core/bootstrap.js +82 -5
  19. package/dist/eval-core/evaluation-reporting.js +37 -15
  20. package/dist/eval-core/execution-strategy.js +1 -0
  21. package/dist/eval-core/schema.js +5 -3
  22. package/dist/eval-core/verdict.d.ts +30 -0
  23. package/dist/eval-core/verdict.js +71 -21
  24. package/dist/grading/assertions.js +28 -0
  25. package/dist/grading/debias-validate.js +6 -2
  26. package/dist/grading/index.js +6 -1
  27. package/dist/grading/layered-scores.d.ts +15 -0
  28. package/dist/grading/layered-scores.js +64 -38
  29. package/dist/inputs/load-samples.d.ts +5 -0
  30. package/dist/inputs/load-samples.js +4 -2
  31. package/dist/inputs/skill-loader.d.ts +8 -0
  32. package/dist/inputs/skill-loader.js +23 -0
  33. package/dist/managed/evidence.d.ts +2 -1
  34. package/dist/managed/evidence.js +15 -2
  35. package/dist/managed/index.d.ts +2 -0
  36. package/dist/managed/index.js +2 -0
  37. package/dist/managed/list-view.d.ts +15 -1
  38. package/dist/managed/list-view.js +9 -1
  39. package/dist/managed/observe-feedback.d.ts +60 -0
  40. package/dist/managed/observe-feedback.js +49 -0
  41. package/dist/managed/store.d.ts +38 -1
  42. package/dist/managed/store.js +116 -3
  43. package/dist/managed/version-scores.d.ts +33 -0
  44. package/dist/managed/version-scores.js +70 -0
  45. package/dist/observability/skill-health-analyzer.d.ts +5 -0
  46. package/dist/observability/skill-health-analyzer.js +3 -2
  47. package/dist/renderer/managed-history-renderer.d.ts +2 -2
  48. package/dist/renderer/managed-history-renderer.js +189 -7
  49. package/dist/renderer/summary.js +23 -22
  50. package/dist/server/report-server.js +11 -2
  51. package/dist/types/eval.d.ts +2 -0
  52. package/dist/types/judge.d.ts +8 -0
  53. package/dist/types/managed.d.ts +40 -0
  54. package/dist/types/report.d.ts +10 -0
  55. package/package.json +3 -3
@@ -2,7 +2,8 @@ import { e, fmtNum, fmtCost, fmtDuration, COLORS, t } from './layout.js';
2
2
  import { icon as svgIcon } from './icons.js';
3
3
  import { generateAnalysisSummary } from '../analysis/report-diagnostics.js';
4
4
  import { pValueCategory } from '../eval-core/statistics.js';
5
- import { computeVerdict, ENSEMBLE_STRONG_PEARSON, ENSEMBLE_DISSENT_PEARSON } from '../eval-core/verdict.js';
5
+ import { ciLevelLabel } from '../eval-core/bootstrap.js';
6
+ import { computeVerdict, medianStabilityCV, STABILITY_UNSTABLE_CV, ENSEMBLE_STRONG_PEARSON, ENSEMBLE_DISSENT_PEARSON } from '../eval-core/verdict.js';
6
7
  /**
7
8
  * Verdict pill — sticky banner at the top of the HTML report giving the same
8
9
  * one-line conclusion as the `omk eval` CLI. Both surfaces share the
@@ -95,19 +96,10 @@ const SHIP_LABEL = {
95
96
  SOLO: { zh: '缺对照', en: 'ADD CONTROL' },
96
97
  };
97
98
  function computeMedianCVPercent(report) {
98
- const variance = report.variance;
99
- if (!variance || (variance.runs ?? 0) < 2)
100
- return null;
101
- const cvs = [];
102
- for (const v of Object.values(variance.perVariant ?? {})) {
103
- if (typeof v.stddev === 'number' && typeof v.mean === 'number' && v.mean > 0) {
104
- cvs.push((v.stddev / v.mean) * 100);
105
- }
106
- }
107
- if (cvs.length === 0)
108
- return null;
109
- cvs.sort((a, b) => a - b);
110
- return cvs[Math.floor(cvs.length / 2)];
99
+ // 单一来源:与 verdict 稳定性门控读同一个真·中位 CV(偶数 variant 取中间两项平均,非上中位),
100
+ // 杜绝 hero CV chip 与 verdict 在偶数 variant 上对不齐。返回百分号刻度。
101
+ const stab = medianStabilityCV(report);
102
+ return stab ? stab.cv * 100 : null;
111
103
  }
112
104
  export function renderVerdictPill(report, lang) {
113
105
  let result;
@@ -134,11 +126,12 @@ export function renderVerdictPill(report, lang) {
134
126
  if (ci) {
135
127
  const sign = ci.estimate >= 0 ? '+' : '';
136
128
  const cvSuffix = cvPct != null
137
- ? (lang === 'zh' ? `;多轮稳定性 CV=${cvPct.toFixed(1)}% (${cvPct < 5 ? '稳' : cvPct < 15 ? '中' : '不稳'})` : `; CV=${cvPct.toFixed(1)}% (${cvPct < 5 ? 'stable' : cvPct < 15 ? 'moderate' : 'unstable'})`)
129
+ ? (lang === 'zh' ? `;多轮稳定性 CV=${cvPct.toFixed(1)}% (${cvPct < 5 ? '稳' : cvPct <= STABILITY_UNSTABLE_CV * 100 ? '中' : '不稳'})` : `; CV=${cvPct.toFixed(1)}% (${cvPct < 5 ? 'stable' : cvPct <= STABILITY_UNSTABLE_CV * 100 ? 'moderate' : 'unstable'})`)
138
130
  : '';
131
+ const pctLabel = ciLevelLabel(report.meta?.pairComparisons?.[0]?.alpha);
139
132
  const ciTipBase = lang === 'zh'
140
- ? `实验组与对照组综合分均值差(Δ)。bootstrap 95% 可信区间 [${ci.low}, ${ci.high}],${ci.significant ? '不含 0 = 差异显著' : '跨过 0 = 差异不显著'}${cvSuffix}`
141
- : `Treatment minus control mean composite score (Δ). Bootstrap 95% CI [${ci.low}, ${ci.high}], ${ci.significant ? 'excludes 0 ⇒ significant' : 'spans 0 ⇒ not significant'}${cvSuffix}`;
133
+ ? `实验组与对照组综合分均值差(Δ)。bootstrap ${pctLabel} 可信区间 [${ci.low}, ${ci.high}],${ci.significant ? '不含 0 = 差异显著' : '跨过 0 = 差异不显著'}${cvSuffix}`
134
+ : `Treatment minus control mean composite score (Δ). Bootstrap ${pctLabel} CI [${ci.low}, ${ci.high}], ${ci.significant ? 'excludes 0 ⇒ significant' : 'spans 0 ⇒ not significant'}${cvSuffix}`;
142
135
  metrics.push({
143
136
  label: lang === 'zh' ? '分差' : 'Δ',
144
137
  value: `${sign}${ci.estimate}`,
@@ -167,6 +160,12 @@ export function renderPairwiseDiff(pairs, lang) {
167
160
  const validPairs = pairs.filter((p) => p.diffBootstrapCI);
168
161
  if (validPairs.length === 0)
169
162
  return '';
163
+ // 同一报告内所有比较共享 family α(α/K),取首对的 alpha 作 family 级标签;K=1 无 alpha → 名义 95%、不加注解
164
+ // (与历史渲染逐字节一致,保护既有快照)。仅 K≥2(alpha 存在)才追加 Bonferroni 说明 + 非 95% 的 CI 标签。
165
+ const pctLabel = ciLevelLabel(validPairs[0].alpha);
166
+ const corrected = validPairs[0].alpha != null;
167
+ const bonferroniNoteZh = corrected ? `多比较已按 Bonferroni 收 α(故 CI 宽于 95%)。` : '';
168
+ const bonferroniNoteEn = corrected ? ` Bonferroni-corrected α across multiple comparisons (CI wider than 95%).` : '';
170
169
  const rows = validPairs.map((p) => {
171
170
  const ci = p.diffBootstrapCI;
172
171
  const sigClass = ci.significant ? 'green' : 'text-muted';
@@ -182,13 +181,13 @@ export function renderPairwiseDiff(pairs, lang) {
182
181
  }).join('');
183
182
  return `
184
183
  <h2 style="margin-top:24px">${lang === 'zh' ? '配对对比 (Bootstrap CI)' : 'Pairwise comparison (bootstrap CI)'}</h2>
185
- <p style="font-size:13px;color:var(--text-secondary);margin:4px 0 12px">${lang === 'zh' ? 'control vs treatment 的均值差 95% CI。CI 不含 0 = 显著差异。bootstrap 不假设分布,适合 LLM 序数评分。' : '95% CI on (treatment - control) mean diff. 0 outside CI = significant. Bootstrap is distribution-free, fits ordinal LLM scores.'}</p>
184
+ <p style="font-size:13px;color:var(--text-secondary);margin:4px 0 12px">${lang === 'zh' ? `control vs treatment 的均值差 ${pctLabel} CI。CI 不含 0 = 显著差异。bootstrap 不假设分布,适合 LLM 序数评分。${bonferroniNoteZh}` : `${pctLabel} CI on (treatment - control) mean diff. 0 outside CI = significant. Bootstrap is distribution-free, fits ordinal LLM scores.${bonferroniNoteEn}`}</p>
186
185
  <div class="table-wrap">
187
186
  <table class="summary-table">
188
187
  <thead><tr>
189
188
  <th>${lang === 'zh' ? '对照' : 'Pair'}</th>
190
189
  <th title="${t('bootstrapDiffLabel', lang)}">${t('bootstrapDiffLabel', lang)}</th>
191
- <th>95% CI</th>
190
+ <th>${pctLabel} CI</th>
192
191
  <th>${lang === 'zh' ? '显著性' : 'Significance'}</th>
193
192
  <th>${lang === 'zh' ? '重采样数' : 'samples'}</th>
194
193
  </tr></thead>
@@ -525,7 +524,7 @@ export function renderSummaryCards(variants, summary, lang, variance) {
525
524
  label = lang === 'zh' ? '稳定' : 'Stable';
526
525
  stabColor = 'var(--green)';
527
526
  }
528
- else if (cvPct < 15) {
527
+ else if (cvPct <= STABILITY_UNSTABLE_CV * 100) {
529
528
  label = lang === 'zh' ? '较稳' : 'Moderate';
530
529
  stabColor = 'var(--yellow)';
531
530
  }
@@ -873,9 +872,11 @@ function computeJudgeAgreementBadge(variants, summary, lang) {
873
872
  };
874
873
  }
875
874
  function computeSignificanceBadge(report, lang) {
876
- const ci = report.meta?.pairComparisons?.[0]?.diffBootstrapCI;
875
+ const pair0 = report.meta?.pairComparisons?.[0];
876
+ const ci = pair0?.diffBootstrapCI;
877
877
  if (!ci)
878
878
  return null;
879
+ const pctLabel = ciLevelLabel(pair0?.alpha);
879
880
  const status = ci.significant ? 'pass' : 'warn';
880
881
  const label = ci.significant
881
882
  ? (lang === 'zh' ? '差异显著' : 'significant')
@@ -884,7 +885,7 @@ function computeSignificanceBadge(report, lang) {
884
885
  key: 'sig',
885
886
  label,
886
887
  status,
887
- detail: lang === 'zh' ? `bootstrap 95% CI [${ci.low}, ${ci.high}]${ci.significant ? '(不含 0)' : '(跨过 0)'}` : `Bootstrap 95% CI [${ci.low}, ${ci.high}]${ci.significant ? ' (excludes 0)' : ' (spans 0)'}`,
888
+ detail: lang === 'zh' ? `bootstrap ${pctLabel} CI [${ci.low}, ${ci.high}]${ci.significant ? '(不含 0)' : '(跨过 0)'}` : `Bootstrap ${pctLabel} CI [${ci.low}, ${ci.high}]${ci.significant ? ' (excludes 0)' : ' (spans 0)'}`,
888
889
  };
889
890
  }
890
891
  function computeSaturationBadge(report, variants, lang) {
@@ -9,7 +9,7 @@ import { renderDoctorDetail } from '../renderer/doctor-detail-renderer.js';
9
9
  import { assessHealth } from '../renderer/skill-detail-renderer.js';
10
10
  import { renderObservationInboxPage } from '../renderer/observation-inbox-renderer.js';
11
11
  import { DEFAULT_LANG, t, layout } from '../renderer/layout.js';
12
- import { loadAllManagedRecords, resolveManagedDir, managedDir as projectManagedDir, listManagedRows } from '../managed/index.js';
12
+ import { loadAllManagedRecords, resolveManagedDir, managedDir as projectManagedDir, listManagedRows, buildVersionScores } from '../managed/index.js';
13
13
  import { renderManagedList, renderManagedHistory } from '../renderer/managed-history-renderer.js';
14
14
  import { DEFAULT_JOBS_DIR } from '../eval-core/default-dirs.js';
15
15
  import { resolveObserveHealthDir, projectObserveHealthDir, resolveDoctorsDir, projectDoctorsDir, projectReportsDir, globalReportsDir } from '../eval-core/measurement-dirs.js';
@@ -706,8 +706,17 @@ export function createReportServer({ port, host: hostOption, reportsDir, analyse
706
706
  res.end(lang === 'en' ? 'managed record not found' : '受管记录不存在');
707
707
  return;
708
708
  }
709
+ // 版本回归曲线要每版的 composite/CI —— 按 evidence.reportId 把报告读出来(去重),读不到的点
710
+ // buildVersionScores 自会跳过。曲线数据在路由侧算好,renderManagedHistory 保持纯函数、可 snapshot。
711
+ const reportsById = new Map();
712
+ for (const ev of record.evidence) {
713
+ // 按「打分视角」结构化看报告:受管证据指向的是单次 eval 报告(带 artifactHashes / summary);
714
+ // 万一是 batch 报告,meta 无 artifactHashes → buildVersionScores 自会跳过,不会误画。
715
+ if (!reportsById.has(ev.reportId))
716
+ reportsById.set(ev.reportId, (await reportStore.get(ev.reportId)));
717
+ }
709
718
  res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
710
- res.end(renderManagedHistory(record, lang));
719
+ res.end(renderManagedHistory(record, lang, buildVersionScores(record, reportsById)));
711
720
  return;
712
721
  }
713
722
  if (path === '/observe-inbox') {
@@ -139,6 +139,7 @@ export interface Artifact {
139
139
  contentHash?: string;
140
140
  locator?: string;
141
141
  ref?: string;
142
+ resolvedCommit?: string;
142
143
  cwd?: string;
143
144
  skillRoot?: string;
144
145
  execRoot?: string;
@@ -160,6 +161,7 @@ export interface VariantConfig {
160
161
  cwd: string | null;
161
162
  locator?: string;
162
163
  ref?: string;
164
+ resolvedCommit?: string;
163
165
  allowedSkills?: string[];
164
166
  }
165
167
  /** 远端 git 源的结构化引用 —— url/ref/spec 分字段,永不拼成单串再 split(避开 parseGitInput 的 `:`
@@ -129,6 +129,14 @@ export interface AssertionDetail {
129
129
  weight: number;
130
130
  passed: boolean;
131
131
  message?: string;
132
+ /**
133
+ * Layer override for `computeLayeredScores`. Only set for combinator types whose layer is not a
134
+ * static property of `type` — currently `assert-set`, whose layer is resolved at grading time from
135
+ * its leaf children: homogeneous (all fact / all behavior) → that layer; mixed (or empty) → field
136
+ * left unset so the combinator is excluded from layered attribution (no single layer is honest).
137
+ * Leaf assertions never set this; they classify by `ASSERTION_LAYER[type]`.
138
+ */
139
+ layer?: 'fact' | 'behavior';
132
140
  }
133
141
  export interface AssertionResults {
134
142
  passed: number;
@@ -50,6 +50,13 @@ export interface ManagedEvidenceRef {
50
50
  judgePromptHash?: string;
51
51
  debiasMode?: Array<'length' | 'position'>;
52
52
  };
53
+ /** §7 #234/#236:这一版被测内容所在的 git commit(full SHA)= 评测时该 variant 的 git ref 解析出的 commit
54
+ * (resolveArtifacts 物化时 `git rev-parse <ref>^{commit}`),作每版「还原坐标」指针 —— list / Studio 据此给
55
+ * `git checkout <sha> -- <path>` 把源带回这一版(字节级还原由 git 做,omk 不存版本字节)。是 variant 自己的
56
+ * ref(可能 branch/tag/HEAD/旧 SHA)而非进程 cwd 的 HEAD —— 内容从 object DB 按 ref 取,故与工作树 dirty
57
+ * 与否无关。只对本地 git variant 记(远端源还原是重装 fetch-pin SHA;file 源无 git 坐标)。缺失 = 无坐标可
58
+ * 还原,诚实留空,不臆造。 */
59
+ gitCommit?: string;
53
60
  }
54
61
  export type ManagedDecisionKind = 'promote' | 'reject' | 'rollback';
55
62
  /** 一次人工管理决定。install 时为空,promote/reject/rollback 追加(append-only 事件流)。 */
@@ -72,6 +79,36 @@ export interface ManagedDecision {
72
79
  overriddenBlocks?: string[];
73
80
  };
74
81
  }
82
+ /** observe → 管理支柱的生产健康观测(#235)。append-only,与 evidence / decisions 同为事实事件流。
83
+ * observe 报告只带 skill **名**(无 contentHash)→ 按 name + kind 绑记录,不锚 contentHash;且观测的是线上
84
+ * **正在跑的那一版**(可能已非记录当前版),故是**版本无关的生产信号**,产出读时 marker(production gap),
85
+ * 绝不翻 stale 生命周期(§6.1 只有内容漂移翻 stale)。样本建议只提示,不改样本集。 */
86
+ export interface ManagedObservation {
87
+ /** 限定判别字(裸 kind 留给 ArtifactKind)。 */
88
+ observationKind: 'production-health';
89
+ /** observe-health 报告 id —— append 去重主键(observe 无 contentHash,故不用 (reportId, contentHash))。 */
90
+ reportId: string;
91
+ /** 被观测**流量窗口的结束时刻**(report.meta.timeRange.to)——不是报告生成的「此刻」(generatedAt 恒约等于
92
+ * now,拿它做 latest-wins 会让所有观测看起来一样新)。deriveProductionGap 据此取最新一条观测。注:观测是
93
+ * **版本无关的生产信号**(量的是线上部署版),不按源码版归因,见 deriveProductionGap。 */
94
+ observedAt: string;
95
+ /** 该 skill 在窗口内的盲区率与严重度加权盲区率(直接取 observe 已算好的 per-skill 值,不重算)。 */
96
+ gapRate: number;
97
+ weightedGapRate: number;
98
+ /** 统计功效:underpowered = 段数太少不可知(§6.1 unknown,既不放行也不拦截,不当确诊盲区)。 */
99
+ confidence: 'high' | 'low' | 'underpowered';
100
+ /** per-skill 色带,由 observe CLI 侧用 observability 的 healthBandOf 算好传入(阈值单一来源)。 */
101
+ healthBand: 'green' | 'yellow' | 'red';
102
+ /** 该 skill 在窗口内的被观测段数(功效上下文)。 */
103
+ segmentCount: number;
104
+ /** 盲区按信号类型计数 —— 驱动「建议补哪类用例」提示(只展示,不生成用例)。 */
105
+ gapByType: {
106
+ failed_search: number;
107
+ explicit_marker: number;
108
+ hedging: number;
109
+ repeated_failure: number;
110
+ };
111
+ }
75
112
  export interface ManagedArtifactSource {
76
113
  /** 源类型(限定判别字,非裸 kind)。`file`=本地路径;`git`=当前仓库某 ref 或远端(带 url)。 */
77
114
  sourceKind: 'file' | 'git';
@@ -104,6 +141,9 @@ export interface ManagedArtifactRecord {
104
141
  distribution: ManagedDistributionTarget[];
105
142
  evidence: ManagedEvidenceRef[];
106
143
  decisions: ManagedDecision[];
144
+ /** #235 observe 生产健康观测(append-only)。旧记录无 → optional;deriveManagedState 不依赖它,
145
+ * production gap 是读时 marker(deriveProductionGap),不翻 stale。 */
146
+ observations?: ManagedObservation[];
107
147
  }
108
148
  /** 读时推导的生命周期标签——不持久(见文件头说明)。`promoted` 由当前内容(contentHash 匹配)有一条
109
149
  * promote 决定推出:它是 measurable 之上的「已人工接受当前版本」,故优先级高于 measurable。 */
@@ -182,6 +182,16 @@ export interface VariantPairComparison {
182
182
  samples: number;
183
183
  significant: boolean;
184
184
  };
185
+ /**
186
+ * Effective per-comparison significance level used for `diffBootstrapCI` after
187
+ * multiple-comparison (Bonferroni) correction: `DEFAULT_BOOTSTRAP_ALPHA / K`, where K is the
188
+ * number of treatment-vs-control comparisons in this report. Present **only when K ≥ 2** —
189
+ * the CI and `significant` then reflect the corrected (wider) interval. Absent ⇒ the nominal
190
+ * α (single comparison / classic A-B, no correction). Renderers derive the displayed
191
+ * confidence level ((1 − alpha)·100%) from this so a Bonferroni-widened CI is never mislabeled
192
+ * "95%".
193
+ */
194
+ alpha?: number;
185
195
  }
186
196
  export interface GitInfo {
187
197
  commit: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oh-my-knowledge",
3
- "version": "0.38.0",
3
+ "version": "0.40.0",
4
4
  "packageManager": "yarn@4.16.0",
5
5
  "description": "Evaluation framework for LLM knowledge inputs — prompts, RAG corpora, skills, agent workflows. Fix the model, vary the artifact. Built-in statistical rigor: bootstrap CI, Krippendorff α, length-debias, saturation curves.",
6
6
  "type": "module",
@@ -92,11 +92,11 @@
92
92
  "license": "MIT",
93
93
  "dependencies": {
94
94
  "@anthropic-ai/claude-agent-sdk": "^0.3.143",
95
- "@anthropic-ai/sdk": "^0.102.0",
95
+ "@anthropic-ai/sdk": "^0.104.1",
96
96
  "@inquirer/prompts": "^8.4.3",
97
97
  "@modelcontextprotocol/sdk": "^1.29.0",
98
98
  "@oclif/core": "^4",
99
- "@openai/codex-sdk": "0.137.0",
99
+ "@openai/codex-sdk": "0.139.0",
100
100
  "ajv": "^8.18.0",
101
101
  "chart.js": "^4.5.1",
102
102
  "js-yaml": "^4.1.1",