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,4 @@
1
+ import type { AnalysisResult, ResultEntry, SampleSnapshot, VarianceData } from '../types/index.js';
2
+ export declare function isSampleSnapshotRecord(value: unknown, sampleIds: string[]): value is Record<string, SampleSnapshot>;
3
+ export declare function isAnalysisResult(value: unknown, variants: string[], results: ResultEntry[]): value is AnalysisResult;
4
+ export declare function isVarianceData(value: unknown, variants: string[]): value is VarianceData;
@@ -0,0 +1,500 @@
1
+ import { isJsonValue } from '../shared/json-value.js';
2
+ import { sampleContractValidationError } from '../shared/sample-contract.js';
3
+ function isRecord(value) {
4
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
5
+ }
6
+ function isFiniteNumber(value) {
7
+ return typeof value === 'number'
8
+ && Number.isFinite(value)
9
+ && Math.abs(value) <= Number.MAX_SAFE_INTEGER;
10
+ }
11
+ function isNonNegativeNumber(value) {
12
+ return isFiniteNumber(value) && value >= 0;
13
+ }
14
+ function isNonNegativeInteger(value) {
15
+ return Number.isSafeInteger(value) && value >= 0;
16
+ }
17
+ function isRate(value) {
18
+ return isFiniteNumber(value) && value >= 0 && value <= 1;
19
+ }
20
+ function isScore(value) {
21
+ return isFiniteNumber(value) && value >= 0 && value <= 5;
22
+ }
23
+ function hasExactKeys(value, expected) {
24
+ const keys = Object.keys(value);
25
+ return keys.length === expected.length && expected.every((key) => Object.hasOwn(value, key));
26
+ }
27
+ function hasOnlyKeys(value, allowed) {
28
+ const allowedKeys = new Set(allowed);
29
+ return Object.keys(value).every((key) => allowedKeys.has(key));
30
+ }
31
+ function isStringArray(value) {
32
+ return Array.isArray(value)
33
+ && value.every((entry) => typeof entry === 'string' && entry.length > 0);
34
+ }
35
+ function isSampleSnapshot(value, expectedId) {
36
+ return sampleContractValidationError(value, expectedId) === undefined;
37
+ }
38
+ export function isSampleSnapshotRecord(value, sampleIds) {
39
+ return isRecord(value)
40
+ && hasExactKeys(value, sampleIds)
41
+ && sampleIds.every((sampleId) => isSampleSnapshot(value[sampleId], sampleId));
42
+ }
43
+ function isCountRecord(value) {
44
+ return isRecord(value)
45
+ && Object.keys(value).every((key) => key.length > 0)
46
+ && Object.values(value).every(isNonNegativeInteger);
47
+ }
48
+ function safeCountSum(value) {
49
+ let total = 0;
50
+ for (const count of Object.values(value)) {
51
+ total += count;
52
+ if (!Number.isSafeInteger(total))
53
+ return undefined;
54
+ }
55
+ return total;
56
+ }
57
+ function isCoverage(value) {
58
+ if (!isRecord(value)
59
+ || !Array.isArray(value.entries)
60
+ || !value.entries.every((entry) => isRecord(entry)
61
+ && typeof entry.path === 'string'
62
+ && entry.path.length > 0
63
+ && (entry.type === 'principle'
64
+ || entry.type === 'semantic'
65
+ || entry.type === 'design'
66
+ || entry.type === 'script'
67
+ || entry.type === 'code'
68
+ || entry.type === 'other')
69
+ && typeof entry.accessed === 'boolean'
70
+ && isNonNegativeInteger(entry.accessCount)
71
+ && entry.accessed === (entry.accessCount > 0)
72
+ && (entry.lineCount === undefined || isNonNegativeInteger(entry.lineCount)))
73
+ || !isNonNegativeInteger(value.filesCovered)
74
+ || !isNonNegativeInteger(value.filesTotal)
75
+ || !isRate(value.fileCoverageRate)
76
+ || !isStringArray(value.uncoveredFiles)
77
+ || !isNonNegativeInteger(value.grepPatternsUsed)
78
+ || !isRate(value.overallRate))
79
+ return false;
80
+ const entries = value.entries;
81
+ const paths = entries.map((entry) => entry.path);
82
+ if (new Set(paths).size !== paths.length)
83
+ return false;
84
+ const filesCovered = entries.filter((entry) => entry.accessed).length;
85
+ const uncoveredFiles = entries
86
+ .filter((entry) => !entry.accessed)
87
+ .map((entry) => entry.path);
88
+ if (value.filesTotal !== entries.length
89
+ || value.filesCovered !== filesCovered
90
+ || value.uncoveredFiles.length !== uncoveredFiles.length
91
+ || value.uncoveredFiles.some((path, index) => path !== uncoveredFiles[index]))
92
+ return false;
93
+ const fileCoverageRate = value.filesTotal > 0
94
+ ? Number((filesCovered / value.filesTotal).toFixed(2))
95
+ : 0;
96
+ const grepBonus = value.grepPatternsUsed > 0
97
+ ? Math.min(1, value.grepPatternsUsed / Math.max(5, value.filesTotal))
98
+ : 0;
99
+ const overallRate = Number((fileCoverageRate * 0.6 + grepBonus * 0.4).toFixed(2));
100
+ return value.fileCoverageRate === fileCoverageRate
101
+ && value.overallRate === overallRate;
102
+ }
103
+ function isGapSignal(value) {
104
+ return isRecord(value)
105
+ && typeof value.sampleId === 'string'
106
+ && value.sampleId.length > 0
107
+ && (value.type === 'failed_search'
108
+ || value.type === 'explicit_marker'
109
+ || value.type === 'hedging'
110
+ || value.type === 'repeated_failure')
111
+ && (value.turn === undefined || isNonNegativeInteger(value.turn))
112
+ && typeof value.context === 'string'
113
+ && (value.evidence === undefined || (isRecord(value.evidence) && isJsonValue(value.evidence)))
114
+ && isRate(value.weight)
115
+ && (value.classifierVerdict === undefined
116
+ || (isRecord(value.classifierVerdict)
117
+ && typeof value.classifierVerdict.isUncertainty === 'boolean'
118
+ && isRate(value.classifierVerdict.confidence)
119
+ && typeof value.classifierVerdict.reason === 'string'));
120
+ }
121
+ function isGapReport(value, variant, successfulSampleIds) {
122
+ const sampleCount = successfulSampleIds.size;
123
+ if (!isRecord(value)
124
+ || value.variant !== variant
125
+ || value.sampleCount !== sampleCount
126
+ || !isNonNegativeInteger(value.samplesWithGap)
127
+ || value.samplesWithGap > sampleCount
128
+ || !isRate(value.gapRate)
129
+ || !isRate(value.weightedGapRate)
130
+ || value.weightedGapRate > value.gapRate
131
+ || (value.testSetPath !== undefined
132
+ && value.testSetPath !== null
133
+ && typeof value.testSetPath !== 'string')
134
+ || (value.testSetHash !== undefined
135
+ && value.testSetHash !== null
136
+ && typeof value.testSetHash !== 'string')
137
+ || !Array.isArray(value.signals)
138
+ || !value.signals.every(isGapSignal)
139
+ || value.signals.some((signal) => isRecord(signal)
140
+ && typeof signal.sampleId === 'string'
141
+ && !successfulSampleIds.has(signal.sampleId))
142
+ || !isRecord(value.byType)
143
+ || !hasExactKeys(value.byType, [
144
+ 'failed_search',
145
+ 'explicit_marker',
146
+ 'hedging',
147
+ 'repeated_failure',
148
+ ])
149
+ || !Object.values(value.byType).every(isNonNegativeInteger))
150
+ return false;
151
+ const sampleWeights = new Map();
152
+ for (const signal of value.signals) {
153
+ sampleWeights.set(signal.sampleId, Math.max(sampleWeights.get(signal.sampleId) ?? 0, signal.weight));
154
+ }
155
+ if (value.samplesWithGap !== sampleWeights.size)
156
+ return false;
157
+ const expectedRate = sampleCount > 0
158
+ ? Number((value.samplesWithGap / sampleCount).toFixed(4))
159
+ : 0;
160
+ if (Math.abs(value.gapRate - expectedRate) > 1e-9)
161
+ return false;
162
+ const expectedWeightedRate = sampleCount > 0
163
+ ? Number(([...sampleWeights.values()].reduce((sum, weight) => sum + weight, 0)
164
+ / sampleCount).toFixed(4))
165
+ : 0;
166
+ if (Math.abs(value.weightedGapRate - expectedWeightedRate) > 1e-9)
167
+ return false;
168
+ const observedByType = {
169
+ failed_search: 0,
170
+ explicit_marker: 0,
171
+ hedging: 0,
172
+ repeated_failure: 0,
173
+ };
174
+ for (const signal of value.signals) {
175
+ observedByType[signal.type]++;
176
+ }
177
+ return Object.entries(observedByType).every(([type, count]) => value.byType[type] === count);
178
+ }
179
+ function isSampleQuality(value, sampleCount) {
180
+ if (!isRecord(value)
181
+ || !isCountRecord(value.capabilityCoverage)
182
+ || !isRecord(value.difficultyDistribution)
183
+ || !hasExactKeys(value.difficultyDistribution, ['easy', 'medium', 'hard', 'unspecified'])
184
+ || !Object.values(value.difficultyDistribution).every(isNonNegativeInteger)
185
+ || !isCountRecord(value.constructDistribution)
186
+ || !isCountRecord(value.provenanceBreakdown)
187
+ || !isNonNegativeInteger(value.avgRubricLength)
188
+ || !isNonNegativeInteger(value.sampleCountWithCapability)
189
+ || !isNonNegativeInteger(value.sampleCountWithDifficulty)
190
+ || !isNonNegativeInteger(value.sampleCountWithConstruct)
191
+ || !isNonNegativeInteger(value.sampleCountWithProvenance)
192
+ || value.sampleCountWithCapability > sampleCount
193
+ || value.sampleCountWithDifficulty > sampleCount
194
+ || value.sampleCountWithConstruct > sampleCount
195
+ || value.sampleCountWithProvenance > sampleCount
196
+ || safeCountSum(value.difficultyDistribution) !== sampleCount
197
+ || safeCountSum(value.constructDistribution) !== sampleCount
198
+ || safeCountSum(value.provenanceBreakdown) !== sampleCount)
199
+ return false;
200
+ const capabilityCoverage = value.capabilityCoverage;
201
+ const sampleCountWithCapability = value.sampleCountWithCapability;
202
+ const capabilityCounts = Object.values(capabilityCoverage);
203
+ const capabilityTagCount = safeCountSum(capabilityCoverage);
204
+ if (capabilityCounts.some((count) => count <= 0 || count > sampleCountWithCapability)
205
+ || (sampleCountWithCapability === 0
206
+ ? capabilityTagCount !== 0
207
+ : capabilityTagCount === undefined
208
+ || capabilityTagCount < sampleCountWithCapability))
209
+ return false;
210
+ const difficulty = value.difficultyDistribution;
211
+ const construct = value.constructDistribution;
212
+ const provenance = value.provenanceBreakdown;
213
+ const allowedProvenance = new Set([
214
+ 'human',
215
+ 'llm-generated',
216
+ 'production-trace',
217
+ 'unspecified',
218
+ ]);
219
+ if (value.sampleCountWithDifficulty
220
+ !== difficulty.easy + difficulty.medium + difficulty.hard
221
+ || value.sampleCountWithConstruct
222
+ !== sampleCount - (construct.unspecified ?? 0)
223
+ || value.sampleCountWithProvenance
224
+ !== sampleCount - (provenance.unspecified ?? 0)
225
+ || Object.keys(provenance).some((key) => !allowedProvenance.has(key)))
226
+ return false;
227
+ if (value.representativeness === undefined)
228
+ return true;
229
+ const rep = value.representativeness;
230
+ if (!isRecord(rep)
231
+ || !isNonNegativeInteger(rep.capabilityCount)
232
+ || rep.capabilityCount !== Object.keys(capabilityCoverage).length)
233
+ return false;
234
+ if (!isRate(rep.capabilityConcentration)
235
+ || (rep.dominantCapability !== undefined && typeof rep.dominantCapability !== 'string')
236
+ || !isRate(rep.difficultyConcentration)
237
+ || !(rep.dominantDifficulty === undefined
238
+ || rep.dominantDifficulty === 'easy'
239
+ || rep.dominantDifficulty === 'medium'
240
+ || rep.dominantDifficulty === 'hard')
241
+ || !isRate(rep.constructConcentration)
242
+ || (rep.dominantConstruct !== undefined && typeof rep.dominantConstruct !== 'string'))
243
+ return false;
244
+ const [dominantCapability, capabilityConcentration] = dominantShare(capabilityCoverage);
245
+ const [dominantDifficulty, difficultyConcentration] = dominantShare({
246
+ easy: difficulty.easy,
247
+ medium: difficulty.medium,
248
+ hard: difficulty.hard,
249
+ });
250
+ const declaredConstruct = Object.fromEntries(Object.entries(construct).filter(([key]) => key !== 'unspecified'));
251
+ const [dominantConstruct, constructConcentration] = dominantShare(declaredConstruct);
252
+ return rep.dominantCapability === dominantCapability
253
+ && closeTo(rep.capabilityConcentration, capabilityConcentration, 1e-12)
254
+ && rep.dominantDifficulty === dominantDifficulty
255
+ && closeTo(rep.difficultyConcentration, difficultyConcentration, 1e-12)
256
+ && rep.dominantConstruct === dominantConstruct
257
+ && closeTo(rep.constructConcentration, constructConcentration, 1e-12);
258
+ }
259
+ function dominantShare(counts) {
260
+ let label;
261
+ let max = 0;
262
+ let total = 0;
263
+ for (const [key, count] of Object.entries(counts)) {
264
+ total += count;
265
+ if (!Number.isSafeInteger(total))
266
+ return [undefined, Number.NaN];
267
+ if (count > max) {
268
+ max = count;
269
+ label = key;
270
+ }
271
+ }
272
+ return total > 0 ? [label, max / total] : [undefined, 0];
273
+ }
274
+ function isHoldout(value, variants, sampleCount) {
275
+ if (!isRecord(value)
276
+ || !isRate(value.ratio)
277
+ || value.ratio <= 0
278
+ || value.ratio >= 1
279
+ || (value.disabled !== undefined && typeof value.disabled !== 'boolean')
280
+ || !isRecord(value.perVariant)
281
+ || (value.testSetPath !== undefined
282
+ && value.testSetPath !== null
283
+ && typeof value.testSetPath !== 'string')
284
+ || (value.testSetHash !== undefined
285
+ && value.testSetHash !== null
286
+ && typeof value.testSetHash !== 'string'))
287
+ return false;
288
+ if (value.disabled === true)
289
+ return Object.keys(value.perVariant).length === 0;
290
+ if (!hasExactKeys(value.perVariant, variants))
291
+ return false;
292
+ return Object.values(value.perVariant).every((entry) => isRecord(entry)
293
+ && isScore(entry.trainScore)
294
+ && isScore(entry.holdoutScore)
295
+ && isNonNegativeInteger(entry.trainCount)
296
+ && isNonNegativeInteger(entry.holdoutCount)
297
+ && entry.trainCount + entry.holdoutCount === sampleCount
298
+ && isNonNegativeInteger(entry.trainScorable)
299
+ && isNonNegativeInteger(entry.holdoutScorable)
300
+ && entry.trainScorable <= entry.trainCount
301
+ && entry.holdoutScorable <= entry.holdoutCount);
302
+ }
303
+ export function isAnalysisResult(value, variants, results) {
304
+ const sampleCount = results.length;
305
+ if (!isRecord(value)
306
+ || (value.summary !== undefined && typeof value.summary !== 'string')
307
+ || !Array.isArray(value.insights)
308
+ || !value.insights.every((insight) => isRecord(insight)
309
+ && typeof insight.type === 'string'
310
+ && insight.type.length > 0
311
+ && (insight.severity === 'error'
312
+ || insight.severity === 'warning'
313
+ || insight.severity === 'info')
314
+ && isJsonValue(insight.details))
315
+ || (value.suggestions !== undefined
316
+ && (!Array.isArray(value.suggestions)
317
+ || !value.suggestions.every((suggestion) => typeof suggestion === 'string')))
318
+ || (value.coverage !== undefined
319
+ && (!isRecord(value.coverage)
320
+ || !hasOnlyKeys(value.coverage, variants)
321
+ || !Object.values(value.coverage).every(isCoverage)))
322
+ || (value.gapReports !== undefined
323
+ && (!isRecord(value.gapReports)
324
+ || !hasOnlyKeys(value.gapReports, variants)
325
+ || !Object.entries(value.gapReports).every(([variant, report]) => isGapReport(report, variant, new Set(results
326
+ .filter((entry) => entry.variants[variant]?.ok === true)
327
+ .map((entry) => entry.sample_id))))))
328
+ || (value.sampleQuality !== undefined
329
+ && !isSampleQuality(value.sampleQuality, sampleCount))
330
+ || (value.holdout !== undefined
331
+ && !isHoldout(value.holdout, variants, sampleCount)))
332
+ return false;
333
+ return true;
334
+ }
335
+ function mean(values) {
336
+ return values.reduce((sum, value) => sum + value, 0) / values.length;
337
+ }
338
+ function sampleStddev(values) {
339
+ if (values.length < 2)
340
+ return 0;
341
+ const avg = mean(values);
342
+ const variance = values.reduce((sum, value) => sum + (value - avg) ** 2, 0)
343
+ / (values.length - 1);
344
+ return Math.sqrt(variance);
345
+ }
346
+ function closeTo(left, right, tolerance = 1e-4) {
347
+ return Math.abs(left - right) <= tolerance;
348
+ }
349
+ function isVarianceMetric(value, runs, scoreDomain) {
350
+ if (!isRecord(value)
351
+ || !Array.isArray(value.scores)
352
+ || value.scores.length === 0
353
+ || value.scores.length > runs
354
+ || !value.scores.every(scoreDomain ? isScore : isNonNegativeNumber)
355
+ || !isFiniteNumber(value.mean)
356
+ || !isFiniteNumber(value.lower)
357
+ || !isFiniteNumber(value.upper)
358
+ || value.lower > value.mean
359
+ || value.mean > value.upper
360
+ || !isNonNegativeNumber(value.stddev))
361
+ return false;
362
+ return closeTo(value.mean, Number(mean(value.scores).toFixed(4)))
363
+ && closeTo(value.stddev, Number(sampleStddev(value.scores).toFixed(4)));
364
+ }
365
+ function isEffectSize(value, leftCount, rightCount) {
366
+ return isRecord(value)
367
+ && isFiniteNumber(value.cohensD)
368
+ && isFiniteNumber(value.hedgesG)
369
+ && (value.primary === 'd'
370
+ || value.primary === 'g'
371
+ || value.primary === 'none')
372
+ && (value.magnitude === 'negligible'
373
+ || value.magnitude === 'small'
374
+ || value.magnitude === 'medium'
375
+ || value.magnitude === 'large'
376
+ || value.magnitude === 'none')
377
+ && isNonNegativeNumber(value.pooledStddev)
378
+ && value.n1 === leftCount
379
+ && value.n2 === rightCount;
380
+ }
381
+ function isVarianceComparisonMetric(value, left, right) {
382
+ return isRecord(value)
383
+ && isFiniteNumber(value.meanDiff)
384
+ && closeTo(value.meanDiff, Number((left.mean - right.mean).toFixed(4)))
385
+ && isFiniteNumber(value.tStatistic)
386
+ && isNonNegativeInteger(value.df)
387
+ && typeof value.significant === 'boolean'
388
+ && isEffectSize(value.effectSize, left.scores.length, right.scores.length);
389
+ }
390
+ function metricMapIsValid(value, keys, runs, scoreDomain) {
391
+ return isRecord(value)
392
+ && hasOnlyKeys(value, keys)
393
+ && Object.values(value).every((metric) => isVarianceMetric(metric, runs, scoreDomain));
394
+ }
395
+ function comparisonMetricMapIsValid(value, keys, left, right) {
396
+ if (!isRecord(value) || !hasOnlyKeys(value, keys))
397
+ return false;
398
+ return Object.entries(value).every(([key, metric]) => {
399
+ const leftMetric = left?.[key];
400
+ const rightMetric = right?.[key];
401
+ return leftMetric !== undefined
402
+ && rightMetric !== undefined
403
+ && isVarianceComparisonMetric(metric, leftMetric, rightMetric);
404
+ });
405
+ }
406
+ function isSaturation(value, variants, runs) {
407
+ if (!isRecord(value)
408
+ || !Array.isArray(value.checkpointSampleCounts)
409
+ || value.checkpointSampleCounts.length !== runs
410
+ || !value.checkpointSampleCounts.every(isNonNegativeInteger)
411
+ || value.checkpointSampleCounts.some((count, index, all) => index > 0 && count < all[index - 1])
412
+ || !isRecord(value.perVariant)
413
+ || !hasExactKeys(value.perVariant, variants))
414
+ return false;
415
+ const checkpointSampleCounts = value.checkpointSampleCounts;
416
+ for (const traces of Object.values(value.perVariant)) {
417
+ if (!Array.isArray(traces)
418
+ || traces.length !== runs
419
+ || traces.some((trace, index, all) => isRecord(trace)
420
+ && index > 0
421
+ && isRecord(all[index - 1])
422
+ && isNonNegativeInteger(trace.n)
423
+ && isNonNegativeInteger(all[index - 1].n)
424
+ && trace.n < all[index - 1].n)
425
+ || !traces.every((trace) => isRecord(trace)
426
+ && isNonNegativeInteger(trace.n)
427
+ && isScore(trace.mean)
428
+ && isScore(trace.ciLow)
429
+ && isScore(trace.ciHigh)
430
+ && trace.ciLow <= trace.mean
431
+ && trace.mean <= trace.ciHigh))
432
+ return false;
433
+ }
434
+ if (value.verdicts === undefined)
435
+ return true;
436
+ if (runs < 5
437
+ || !isRecord(value.verdicts)
438
+ || !hasOnlyKeys(value.verdicts, variants))
439
+ return false;
440
+ return Object.values(value.verdicts).every((verdict) => isRecord(verdict)
441
+ && typeof verdict.saturated === 'boolean'
442
+ && (verdict.atN === null
443
+ || (Number.isSafeInteger(verdict.atN)
444
+ && verdict.atN > 0
445
+ && verdict.atN <= (checkpointSampleCounts.at(-1) ?? 0)))
446
+ && (verdict.confidence === 'high'
447
+ || verdict.confidence === 'medium'
448
+ || verdict.confidence === 'low')
449
+ && (verdict.method === 'slope'
450
+ || verdict.method === 'bootstrap-ci-width'
451
+ || verdict.method === 'plateau-height')
452
+ && isNonNegativeNumber(verdict.threshold)
453
+ && typeof verdict.reason === 'string');
454
+ }
455
+ export function isVarianceData(value, variants) {
456
+ if (!isRecord(value)
457
+ || !Number.isSafeInteger(value.runs)
458
+ || value.runs < 2
459
+ || !isRecord(value.perVariant)
460
+ || !hasOnlyKeys(value.perVariant, variants))
461
+ return false;
462
+ const runs = value.runs;
463
+ for (const variant of Object.values(value.perVariant)) {
464
+ const extended = variant;
465
+ if (!isVarianceMetric(variant, runs, true)
466
+ || (extended.byMetric !== undefined
467
+ && !metricMapIsValid(extended.byMetric, ['cost', 'efficiency'], runs, false))
468
+ || (extended.byLayer !== undefined
469
+ && !metricMapIsValid(extended.byLayer, ['fact', 'behavior', 'judge'], runs, true)))
470
+ return false;
471
+ }
472
+ if (!Array.isArray(value.comparisons))
473
+ return false;
474
+ const pairs = new Set();
475
+ for (const comparison of value.comparisons) {
476
+ if (!isRecord(comparison)
477
+ || typeof comparison.a !== 'string'
478
+ || typeof comparison.b !== 'string'
479
+ || comparison.a === comparison.b
480
+ || !variants.includes(comparison.a)
481
+ || !variants.includes(comparison.b))
482
+ return false;
483
+ const pair = [comparison.a, comparison.b].sort().join('\u0000');
484
+ if (pairs.has(pair))
485
+ return false;
486
+ pairs.add(pair);
487
+ const left = value.perVariant[comparison.a];
488
+ const right = value.perVariant[comparison.b];
489
+ if (!left
490
+ || !right
491
+ || !isVarianceComparisonMetric(comparison, left, right)
492
+ || (comparison.byMetric !== undefined
493
+ && !comparisonMetricMapIsValid(comparison.byMetric, ['cost', 'efficiency'], left.byMetric, right.byMetric))
494
+ || (comparison.byLayer !== undefined
495
+ && !comparisonMetricMapIsValid(comparison.byLayer, ['fact', 'behavior', 'judge'], left.byLayer, right.byLayer)))
496
+ return false;
497
+ }
498
+ return value.saturation === undefined
499
+ || isSaturation(value.saturation, variants, runs);
500
+ }
@@ -1,5 +1,6 @@
1
1
  import { existsSync, readdirSync, readFileSync, renameSync, unlinkSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
+ import { isDeepStrictEqual } from 'node:util';
3
4
  import { doctorReportFileStem, isReportFileName, reportFilePath, stripDomainPrefix } from './artifact-file-names.js';
4
5
  function legacyJsonStem(fileName) {
5
6
  if (!fileName.endsWith('.json'))
@@ -66,8 +67,10 @@ export function migrateLegacyReportFiles(dir, domain) {
66
67
  continue;
67
68
  const sourcePath = join(dir, file);
68
69
  let targetStem = null;
70
+ let sourceData;
69
71
  try {
70
- targetStem = targetStemForLegacyReport(domain, file, JSON.parse(readFileSync(sourcePath, 'utf-8')));
72
+ sourceData = JSON.parse(readFileSync(sourcePath, 'utf-8'));
73
+ targetStem = targetStemForLegacyReport(domain, file, sourceData);
71
74
  }
72
75
  catch {
73
76
  continue;
@@ -77,7 +80,9 @@ export function migrateLegacyReportFiles(dir, domain) {
77
80
  const targetPath = reportFilePath(dir, targetStem);
78
81
  try {
79
82
  if (existsSync(targetPath)) {
80
- unlinkSync(sourcePath);
83
+ const targetData = JSON.parse(readFileSync(targetPath, 'utf-8'));
84
+ if (isDeepStrictEqual(sourceData, targetData))
85
+ unlinkSync(sourcePath);
81
86
  }
82
87
  else {
83
88
  renameSync(sourcePath, targetPath);
@@ -0,0 +1,31 @@
1
+ import type { Artifact, EvalBudget, JudgeConfig, Report, Sample, Task } from '../types/index.js';
2
+ export interface ResumeCompatibilityInput {
3
+ variants: string[];
4
+ model: string;
5
+ executorName: string;
6
+ effort?: 'low' | 'medium' | 'high' | 'xhigh' | 'max';
7
+ noJudge: boolean;
8
+ judgeModels: JudgeConfig[];
9
+ judgeRepeat?: number;
10
+ lengthDebias?: boolean;
11
+ budget?: EvalBudget;
12
+ timeoutMs?: number;
13
+ retry?: number;
14
+ noDiagnostic?: boolean;
15
+ skillDir: string;
16
+ samples: Sample[];
17
+ samplesBaseDir?: string;
18
+ tasks: Task[];
19
+ artifacts: Artifact[];
20
+ }
21
+ export interface ResumeCompatibilityResult {
22
+ compatible: boolean;
23
+ mismatches: string[];
24
+ }
25
+ /**
26
+ * Decide whether successful entries from an earlier report can be inserted
27
+ * into the current measurement without mixing protocols. Every value compared
28
+ * here is either the report's public measurement evidence or a request option
29
+ * that can alter a persisted VariantResult.
30
+ */
31
+ export declare function checkResumeCompatibility(report: Report, current: ResumeCompatibilityInput): ResumeCompatibilityResult;