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
@@ -1,6 +1,8 @@
1
1
  import { readFileSync, readdirSync, statSync } from 'node:fs';
2
2
  import { dirname, join, resolve } from 'node:path';
3
3
  import yaml from 'js-yaml';
4
+ import { dependencyRequirementsValidationError, sampleContractValidationError, } from '../shared/sample-contract.js';
5
+ import { setOwnRecordValue } from '../shared/record-count.js';
4
6
  export function parseYaml(text) {
5
7
  try {
6
8
  return yaml.load(text);
@@ -70,7 +72,7 @@ function loadSamplesFromDir(dir) {
70
72
  `defined in both "${prev}" and "${f}"`);
71
73
  }
72
74
  seenIds.set(s.sample_id, f);
73
- sampleSourceById[s.sample_id] = path;
75
+ setOwnRecordValue(sampleSourceById, s.sample_id, path);
74
76
  }
75
77
  allSamples.push(...single.samples);
76
78
  mergedRequires = mergeRequires(mergedRequires, single.requires);
@@ -118,10 +120,20 @@ export function validateSamples(samples) {
118
120
  'workflow',
119
121
  'workflow_node',
120
122
  ]);
123
+ const firstIndexBySampleId = new Map();
121
124
  for (const [i, sample] of samples.entries()) {
125
+ if (!sample || typeof sample !== 'object' || Array.isArray(sample)) {
126
+ throw new Error(`samples[${i}] invalid sample contract: sample must be an object`);
127
+ }
122
128
  if (!sample.sample_id || typeof sample.sample_id !== 'string') {
123
129
  throw new Error(`samples[${i}] missing or invalid required field: sample_id (must be a non-empty string)`);
124
130
  }
131
+ const firstIndex = firstIndexBySampleId.get(sample.sample_id);
132
+ if (firstIndex !== undefined) {
133
+ throw new Error(`duplicate sample_id "${sample.sample_id}" at samples[${i}] `
134
+ + `(first defined at samples[${firstIndex}])`);
135
+ }
136
+ firstIndexBySampleId.set(sample.sample_id, i);
125
137
  if (!sample.prompt || typeof sample.prompt !== 'string') {
126
138
  throw new Error(`samples[${i}] (${sample.sample_id}) missing or invalid required field: prompt (must be a non-empty string)`);
127
139
  }
@@ -247,6 +259,10 @@ export function validateSamples(samples) {
247
259
  emitViolation(`${label}: regex pattern 含 CJK 字符 — 同样的语义匹配应走 rubric → judge,而不是字面正则: ${JSON.stringify(a.pattern)}`);
248
260
  }
249
261
  }
262
+ const contractError = sampleContractValidationError(sample);
263
+ if (contractError) {
264
+ throw new Error(`samples[${i}] (${sample.sample_id}) invalid sample contract: ${contractError}`);
265
+ }
250
266
  }
251
267
  }
252
268
  function loadSampleFile(samplesPath) {
@@ -264,6 +280,12 @@ function loadSampleFile(samplesPath) {
264
280
  const wrapper = parsed;
265
281
  samples = wrapper.samples;
266
282
  requires = wrapper.requires;
283
+ if (requires !== undefined) {
284
+ const requiresError = dependencyRequirementsValidationError(requires);
285
+ if (requiresError) {
286
+ throw new Error(`invalid samples file: ${samplesPath}: ${requiresError}`);
287
+ }
288
+ }
267
289
  }
268
290
  else {
269
291
  throw new Error(`invalid samples file shape: ${samplesPath} (expected an array or an object with a 'samples' field)`);
@@ -35,6 +35,7 @@ import { resolve } from 'node:path';
35
35
  import { Client } from '@modelcontextprotocol/sdk/client/index.js';
36
36
  import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
37
37
  import { isPlaceholderUrl } from './url-fetcher.js';
38
+ import { ownRecordValue, setOwnRecordValue } from '../shared/record-count.js';
38
39
  // Reuse the same URL regex from url-fetcher.mjs
39
40
  const URL_REGEX = /https?:\/\/[A-Za-z0-9\-._~:/?#[\]@!$&'()*+,;=%]+/g;
40
41
  const MCP_CALL_TIMEOUT_MS = 30_000;
@@ -83,7 +84,7 @@ export function loadMcpConfig(configPath) {
83
84
  const eligible = {};
84
85
  for (const [name, def] of Object.entries(serverDefs)) {
85
86
  if (isMcpServerDef(def) && def.urlPatterns.length > 0) {
86
- eligible[name] = def;
87
+ setOwnRecordValue(eligible, name, def);
87
88
  }
88
89
  }
89
90
  if (Object.keys(eligible).length === 0)
@@ -145,7 +146,9 @@ export async function resolveMcpUrls(samples, mcpServers) {
145
146
  const fetched = new Map(); // url -> { ok, content, error }
146
147
  const fetchPromises = [];
147
148
  for (const [serverName, urls] of serverUrlGroups) {
148
- fetchPromises.push(fetchFromServer(serverName, mcpServers[serverName], urls, fetched));
149
+ const server = ownRecordValue(mcpServers, serverName);
150
+ if (server)
151
+ fetchPromises.push(fetchFromServer(serverName, server, urls, fetched));
149
152
  }
150
153
  await Promise.allSettled(fetchPromises);
151
154
  // 4. Report failures
@@ -208,7 +211,7 @@ function buildToolArgs(fetchTool, url) {
208
211
  return null;
209
212
  const args = {};
210
213
  for (const [key, template] of Object.entries(params)) {
211
- args[key] = template.replace(/\$(\d+)/g, (_, i) => match[Number(i)] || '');
214
+ setOwnRecordValue(args, key, template.replace(/\$(\d+)/g, (_, i) => match[Number(i)] || ''));
212
215
  }
213
216
  return args;
214
217
  }
@@ -0,0 +1,11 @@
1
+ import type { Sample } from '../types/index.js';
2
+ import { type LoadSamplesResult } from './load-samples.js';
3
+ export declare function parseSampleDocument(filePath: string): unknown;
4
+ export declare function getSamplesArray(document: unknown, filePath: string): Sample[];
5
+ export declare function stringifySampleDocument(filePath: string, document: unknown): string;
6
+ /**
7
+ * Persist only changed samples back to the source file that originally owned each
8
+ * sample_id. JSON/YAML and array/wrapper shapes are retained, including `requires`
9
+ * and any other wrapper metadata.
10
+ */
11
+ export declare function writeFixedSamplesToSources(loaded: Pick<LoadSamplesResult, 'sourceFiles' | 'sampleSourceById'>, samples: Sample[], changedIds: ReadonlySet<string>): string[];
@@ -0,0 +1,96 @@
1
+ import { lstatSync, readFileSync, realpathSync, renameSync, statSync, unlinkSync, writeFileSync, } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import yaml from 'js-yaml';
4
+ import { withFileLock } from '../shared/file-lock.js';
5
+ import { ownRecordValue } from '../shared/record-count.js';
6
+ import { parseYaml, validateSamples } from './load-samples.js';
7
+ function isRecord(value) {
8
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
9
+ }
10
+ function isYamlPath(filePath) {
11
+ return /\.(ya?ml)$/i.test(filePath);
12
+ }
13
+ export function parseSampleDocument(filePath) {
14
+ const raw = readFileSync(filePath, 'utf-8');
15
+ return isYamlPath(filePath) ? parseYaml(raw) : JSON.parse(raw);
16
+ }
17
+ export function getSamplesArray(document, filePath) {
18
+ if (Array.isArray(document))
19
+ return document;
20
+ if (isRecord(document) && Array.isArray(document.samples)) {
21
+ return document.samples;
22
+ }
23
+ throw new Error(`invalid samples file shape: ${filePath} `
24
+ + `(expected an array or an object with a 'samples' field)`);
25
+ }
26
+ export function stringifySampleDocument(filePath, document) {
27
+ if (isYamlPath(filePath)) {
28
+ return yaml.dump(document, { lineWidth: -1, noRefs: true });
29
+ }
30
+ return JSON.stringify(document, null, 2);
31
+ }
32
+ function writableFilePath(filePath) {
33
+ return lstatSync(filePath).isSymbolicLink() ? realpathSync(filePath) : filePath;
34
+ }
35
+ function atomicWriteFile(filePath, content) {
36
+ const targetPath = writableFilePath(filePath);
37
+ const tempPath = join(dirname(targetPath), `.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`);
38
+ try {
39
+ writeFileSync(tempPath, content, { mode: statSync(targetPath).mode & 0o777 });
40
+ renameSync(tempPath, targetPath);
41
+ }
42
+ catch (error) {
43
+ try {
44
+ unlinkSync(tempPath);
45
+ }
46
+ catch {
47
+ // The rename may already have consumed the temporary file.
48
+ }
49
+ throw error;
50
+ }
51
+ }
52
+ /**
53
+ * Persist only changed samples back to the source file that originally owned each
54
+ * sample_id. JSON/YAML and array/wrapper shapes are retained, including `requires`
55
+ * and any other wrapper metadata.
56
+ */
57
+ export function writeFixedSamplesToSources(loaded, samples, changedIds) {
58
+ if (changedIds.size === 0)
59
+ return [];
60
+ const fixedById = new Map(samples.map((sample) => [sample.sample_id, sample]));
61
+ const idsByFile = new Map();
62
+ for (const sampleId of changedIds) {
63
+ const filePath = ownRecordValue(loaded.sampleSourceById, sampleId);
64
+ if (!filePath)
65
+ throw new Error(`sample ${sampleId} source file not found`);
66
+ if (!loaded.sourceFiles.includes(filePath)) {
67
+ throw new Error(`sample ${sampleId} source file is outside the loaded sample set`);
68
+ }
69
+ if (!fixedById.has(sampleId)) {
70
+ throw new Error(`fixed sample ${sampleId} is missing from the update set`);
71
+ }
72
+ const ids = idsByFile.get(filePath) ?? new Set();
73
+ ids.add(sampleId);
74
+ idsByFile.set(filePath, ids);
75
+ }
76
+ for (const [filePath, ids] of idsByFile.entries()) {
77
+ const targetPath = writableFilePath(filePath);
78
+ withFileLock(`${targetPath}.omk.lock`, () => {
79
+ const document = parseSampleDocument(filePath);
80
+ const fileSamples = getSamplesArray(document, filePath);
81
+ const existingIds = new Set(fileSamples.map((sample) => sample.sample_id));
82
+ for (const sampleId of ids) {
83
+ if (!existingIds.has(sampleId)) {
84
+ throw new Error(`sample ${sampleId} is no longer present in ${filePath}`);
85
+ }
86
+ }
87
+ const nextSamples = fileSamples.map((sample) => (ids.has(sample.sample_id) ? fixedById.get(sample.sample_id) : sample));
88
+ validateSamples(nextSamples);
89
+ const nextDocument = Array.isArray(document)
90
+ ? nextSamples
91
+ : { ...document, samples: nextSamples };
92
+ atomicWriteFile(filePath, stringifySampleDocument(filePath, nextDocument));
93
+ });
94
+ }
95
+ return [...idsByFile.keys()];
96
+ }
@@ -8,6 +8,7 @@ export declare function buildEvidenceRef(report: EvaluationReport, variant: stri
8
8
  export interface RecordedEvidence {
9
9
  recordId: string;
10
10
  name: string;
11
+ variant: string;
11
12
  contentHash: string;
12
13
  /** true ⇒ evidence.contentHash 等于记录当前 contentHash → deriveManagedState 计为当前证据 →
13
14
  * measurable。false ⇒ 测的是旧内容(已 drift),证据留存但不绑当前版本。 */
@@ -149,7 +149,7 @@ export function recordEvalEvidence(report, verdict, recordedAt, opts = {}) {
149
149
  const merged = appendManagedEvidence(dir, rec.id, ref);
150
150
  if (!merged)
151
151
  continue;
152
- out.push({ recordId: rec.id, name: rec.name, contentHash: ref.contentHash, bound: ref.contentHash === merged.contentHash });
152
+ out.push({ recordId: rec.id, name: rec.name, variant: variantKey, contentHash: ref.contentHash, bound: ref.contentHash === merged.contentHash });
153
153
  }
154
154
  return out;
155
155
  }