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,5 +1,4 @@
1
1
  import { dirname } from 'node:path';
2
- import { existsSync, mkdirSync } from 'node:fs';
3
2
  import { DEFAULT_ISOLATED_CWD_DIR } from './default-dirs.js';
4
3
  /**
5
4
  * Isolated cwd for strict baseline. Empty dir under ~/.oh-my-knowledge/
@@ -8,14 +7,11 @@ import { DEFAULT_ISOLATED_CWD_DIR } from './default-dirs.js';
8
7
  * leak the skill content into baseline via plain file-system access, even when
9
8
  * Skill auto-discovery is blocked).
10
9
  *
11
- * Stable path (not per-task tmpdir) so cache keys remain consistent.
10
+ * This is a stable logical path for cache identity. executeTasks replaces it
11
+ * with a fresh physical directory for every executor attempt, then removes it.
12
12
  */
13
13
  function getIsolatedCwd() {
14
- const dir = DEFAULT_ISOLATED_CWD_DIR;
15
- if (!existsSync(dir)) {
16
- mkdirSync(dir, { recursive: true });
17
- }
18
- return dir;
14
+ return DEFAULT_ISOLATED_CWD_DIR;
19
15
  }
20
16
  function combineUserPrompt(prefix, prompt) {
21
17
  if (!prefix)
@@ -84,6 +80,11 @@ function extractSkillDir(artifact) {
84
80
  return dirname(artifact.locator);
85
81
  }
86
82
  export function resolveExecutionStrategy(task, model, timeoutMs, verbose, effort, samplesBaseDir) {
83
+ if (Array.isArray(task.artifact.allowedSkills)
84
+ && task.artifact.allowedSkills.length > 0) {
85
+ throw new Error(`skill 白名单(allowedSkills=${JSON.stringify(task.artifact.allowedSkills)})不再支持:`
86
+ + '非空白名单无法在所有执行器上保证隔离。请使用 [](完全隔离)或省略该字段。');
87
+ }
87
88
  const skillDir = extractSkillDir(task.artifact);
88
89
  // strict-baseline cwd 沙箱:baseline + allowedSkills===[] + 用户没显式
89
90
  // cwd 时,改用 isolated empty dir。否则 baseline 的 Glob/Read 会走进用户工作目录
@@ -102,8 +103,8 @@ export function resolveExecutionStrategy(task, model, timeoutMs, verbose, effort
102
103
  ...(effort && { effort }),
103
104
  // pass skill-isolation declaration to executors. undefined keeps
104
105
  // SDK default; [] = strict isolation (skills:[] + disallowedTools:['Skill']).
105
- // non-empty allowedSkills is rejected upstream (validateEvalConfig) and by every
106
- // executor — a skill whitelist could not be fully isolated, so it was removed.
106
+ // Non-empty allowedSkills is rejected at this common planning boundary, including
107
+ // programmatic callers that bypass eval.yaml validation.
107
108
  ...(task.artifact.allowedSkills !== undefined && { allowedSkills: task.artifact.allowedSkills }),
108
109
  // Sample.mocks 透传到 executor。executor(claude-sdk / claude-cli)
109
110
  // 自决定怎么落地(in-process hook vs 临时 CLAUDE_CONFIG_DIR + on-disk hook)。
@@ -121,6 +122,7 @@ export function resolveExecutionStrategy(task, model, timeoutMs, verbose, effort
121
122
  return {
122
123
  strategy: resolveArtifactExecutionStrategy(task.artifact),
123
124
  cacheSystem: '',
125
+ ...(isStrictBaseline && !task.cwd && { isolatedCwd: true }),
124
126
  input: {
125
127
  ...baseInput,
126
128
  system: null,
@@ -3,15 +3,15 @@
3
3
  * Currently supports file/directory path verification via fs.existsSync.
4
4
  */
5
5
  import { existsSync } from 'node:fs';
6
- import { resolve } from 'node:path';
6
+ import { isAbsolute, relative, resolve } from 'node:path';
7
7
  // Patterns that look like file/directory paths in agent output
8
8
  const PATH_PATTERNS = [
9
9
  // Explicit code paths: repos/xxx, src/xxx, lib/xxx, packages/xxx
10
10
  /(?:repos|src|lib|packages|dist|test|components)\/[a-zA-Z0-9_/.@-]+/g,
11
- // .claude paths
12
- /\.claude\/[a-zA-Z0-9_/.@-]+/g,
11
+ // Agent configuration and skill paths across supported trace sources.
12
+ /\.(?:agents|claude|codex|gemini)\/[a-zA-Z0-9_/.@-]+/g,
13
13
  // Paths with file extensions mentioned in text
14
- /[a-zA-Z0-9_/-]+\.(?:ts|js|tsx|jsx|md|json|yaml|yml|sh)\b(?!\s*\()/g,
14
+ /(?<![a-zA-Z0-9_./-])[a-zA-Z0-9_/-]+\.(?:ts|js|tsx|jsx|md|json|yaml|yml|sh)\b(?!\s*\()/g,
15
15
  ];
16
16
  // Paths to ignore (too generic or always present)
17
17
  const IGNORE_PATTERNS = [
@@ -48,14 +48,22 @@ export function extractPathClaims(output) {
48
48
  */
49
49
  export function checkFacts(output, cwd) {
50
50
  const pathClaims = extractPathClaims(output);
51
+ const root = resolve(cwd);
51
52
  const claims = pathClaims.map((path) => {
52
- const fullPath = resolve(cwd, path);
53
- const exists = existsSync(fullPath);
53
+ const fullPath = resolve(root, path);
54
+ const relativePath = relative(root, fullPath);
55
+ const insideCwd = relativePath === ''
56
+ || (!relativePath.startsWith('..') && !isAbsolute(relativePath));
57
+ const exists = insideCwd && existsSync(fullPath);
54
58
  return {
55
59
  type: 'file-path',
56
60
  value: path,
57
61
  verified: exists,
58
- ...(!exists && { evidence: `${fullPath} not found` }),
62
+ ...(!exists && {
63
+ evidence: insideCwd
64
+ ? `${fullPath} not found`
65
+ : `${path} is outside the evaluation cwd`,
66
+ }),
59
67
  };
60
68
  });
61
69
  const verifiedCount = claims.filter((c) => c.verified).length;
@@ -12,6 +12,7 @@
12
12
  * signal the verdict's overfitting gate reads (`src/eval-core/verdict.ts`).
13
13
  */
14
14
  import { buildVariantSummary } from './schema.js';
15
+ import { setOwnRecordValue } from '../shared/record-count.js';
15
16
  /** Below this many samples on any side, a split is too small to be meaningful —
16
17
  * callers fall back to full-set scoring and mark the breakdown `disabled`. */
17
18
  export const MIN_HOLDOUT_SUBSET = 3;
@@ -105,14 +106,14 @@ export function computeHoldoutBreakdown(report, variantNames, ratio, sampleIdOrd
105
106
  }
106
107
  const perVariant = {};
107
108
  for (const v of variantNames) {
108
- perVariant[v] = {
109
+ setOwnRecordValue(perVariant, v, {
109
110
  trainScore: Number(subsetCompositeScore(report, v, split.trainIds).toFixed(4)),
110
111
  holdoutScore: Number(subsetCompositeScore(report, v, split.holdoutIds).toFixed(4)),
111
112
  trainCount: split.trainIds.size,
112
113
  holdoutCount: split.holdoutIds.size,
113
114
  trainScorable: subsetScorableCount(report, v, split.trainIds),
114
115
  holdoutScorable: subsetScorableCount(report, v, split.holdoutIds),
115
- };
116
+ });
116
117
  }
117
118
  return { ratio, perVariant };
118
119
  }
@@ -3,8 +3,8 @@ import { type ExecutorVendor } from '../executors/shared.js';
3
3
  /**
4
4
  * 评委独立性分析(单一来源,verdict caveat 与 analysis 诊断共用)。
5
5
  *
6
- * LLM 评委有自我偏好偏置:偏爱与自己同模型家族产出的输出。omk 默认评委(claude:haiku)与默认
7
- * 执行器(claude:*)同属一家,敞口默认就开着。本 helper 只算客观事实(评委/输出各属哪家、有没有
6
+ * LLM 评委有自我偏好偏置:偏爱与自己同模型家族产出的输出。CLI 默认评委跟随所选 runtime
7
+ * (Claude 用 haiku,Codex 沿用被测模型),故敞口默认就开着。本 helper 只算客观事实(评委/输出各属哪家、有没有
8
8
  * 跨厂商评委、是不是单厂商 ensemble、有没有挂 gold 校准),严重度与文案交给消费方(report-diagnostics
9
9
  * / verdict)决定。
10
10
  *
@@ -13,6 +13,22 @@ const fs = require('fs');
13
13
  const path = require('path');
14
14
  const os = require('os');
15
15
 
16
+ function recordCount(record, key) {
17
+ const value = Object.prototype.hasOwnProperty.call(record, key) ? record[key] : undefined;
18
+ return typeof value === 'number' && Number.isInteger(value) && value >= 0 ? value : 0;
19
+ }
20
+
21
+ function incrementRecordCount(record, key, amount = 1) {
22
+ const next = recordCount(record, key) + amount;
23
+ Object.defineProperty(record, key, {
24
+ value: next,
25
+ enumerable: true,
26
+ configurable: true,
27
+ writable: true,
28
+ });
29
+ return next;
30
+ }
31
+
16
32
  function expandHome(p) {
17
33
  if (p.startsWith('~/')) return path.join(os.homedir(), p.slice(2));
18
34
  if (p === '~') return os.homedir();
@@ -151,12 +167,13 @@ process.stdin.on('end', () => {
151
167
  // 统计文件:perMock(每条 mock 的命中次数,用于状态机 + 报告)+ hits_total + misses_total
152
168
  // 同一 spawn 内多次 hook 调用共享。
153
169
  const statsFile = path.join(path.dirname(mocksFile), 'hits.json');
154
- const stats = fs.existsSync(statsFile)
170
+ let stats = fs.existsSync(statsFile)
155
171
  ? JSON.parse(fs.readFileSync(statsFile, 'utf8'))
156
172
  : { perMock: {}, hits_total: 0, misses_total: 0 };
157
- if (!stats.perMock) stats.perMock = {};
158
- if (!stats.hits_total) stats.hits_total = 0;
159
- if (!stats.misses_total) stats.misses_total = 0;
173
+ if (!stats || typeof stats !== 'object' || Array.isArray(stats)) stats = {};
174
+ if (!stats.perMock || typeof stats.perMock !== 'object' || Array.isArray(stats.perMock)) stats.perMock = {};
175
+ stats.hits_total = recordCount(stats, 'hits_total');
176
+ stats.misses_total = recordCount(stats, 'misses_total');
160
177
 
161
178
  const writeStats = () => {
162
179
  try { fs.writeFileSync(statsFile, JSON.stringify(stats)); } catch { /* best effort */ }
@@ -176,9 +193,9 @@ process.stdin.on('end', () => {
176
193
  for (let i = 0; i < mocks.length; i++) {
177
194
  if (isMockHit(mocks[i], toolName, toolInput)) {
178
195
  const key = keyOfMock[i];
179
- const c = stats.perMock[key] || 0;
196
+ const c = recordCount(stats.perMock, key);
180
197
  const result = resolveMockReturn(mocks[i], c, baseDir);
181
- stats.perMock[key] = c + 1;
198
+ incrementRecordCount(stats.perMock, key);
182
199
  stats.hits_total += 1;
183
200
  writeStats();
184
201
  // 与 SDK 路径(mocks-runtime.ts)对齐:最小化前缀,避免拖慢多步 sample。
@@ -17,6 +17,7 @@ import { mkdtempSync, rmSync, writeFileSync, mkdirSync, readFileSync, existsSync
17
17
  import { homedir, tmpdir } from 'node:os';
18
18
  import { dirname, isAbsolute, join, resolve } from 'node:path';
19
19
  import { fileURLToPath } from 'node:url';
20
+ import { incrementRecordCount, setOwnRecordValue } from '../shared/record-count.js';
20
21
  // ─── Match logic ────────────────────────────────────────────────────────────
21
22
  function expandHome(p) {
22
23
  if (p.startsWith('~/'))
@@ -209,7 +210,7 @@ export function buildSdkHookCallback(mocks, baseDir, strict = false) {
209
210
  hitCounters.set(i, c + 1);
210
211
  stats.hits++;
211
212
  const key = keyOfMock[i];
212
- stats.perMock[key] = (stats.perMock[key] || 0) + 1;
213
+ incrementRecordCount(stats.perMock, key);
213
214
  // 关键 UX:LLM 看到 permissionDecision='deny' 容易误判"被拒绝/失败"。
214
215
  // 给 reason 加一行最小前缀,告诉它把内容当成真实成功输出。措辞越短越好 ——
215
216
  // 长措辞会显著拉慢多步 sample(每个工具调用都要读这段),实测 6 步流程从 60s 拖到 120s+。
@@ -289,11 +290,11 @@ export function materializeForCliConfigDir(mocks, baseDir, strict = false) {
289
290
  writeFileSync(mcpServerScript, fakeMcpServerSource(), 'utf8');
290
291
  const mcpServers = {};
291
292
  for (const serverName of fakeMcpServers.keys()) {
292
- mcpServers[serverName] = {
293
+ setOwnRecordValue(mcpServers, serverName, {
293
294
  command: 'node',
294
295
  args: [mcpServerScript, serverName],
295
296
  env: { OMK_MOCKS_FILE: mocksFile },
296
- };
297
+ });
297
298
  }
298
299
  writeFileSync(mcpConfigFile, JSON.stringify({ mcpServers }, null, 2));
299
300
  }
@@ -359,6 +360,20 @@ const mocksFile = process.env.OMK_MOCKS_FILE;
359
360
  const rl = readline.createInterface({ input: process.stdin });
360
361
 
361
362
  function send(obj) { process.stdout.write(JSON.stringify(obj) + '\\n'); }
363
+ function recordCount(record, key) {
364
+ const value = Object.prototype.hasOwnProperty.call(record, key) ? record[key] : undefined;
365
+ return typeof value === 'number' && Number.isInteger(value) && value >= 0 ? value : 0;
366
+ }
367
+ function incrementRecordCount(record, key, amount = 1) {
368
+ const next = recordCount(record, key) + amount;
369
+ Object.defineProperty(record, key, {
370
+ value: next,
371
+ enumerable: true,
372
+ configurable: true,
373
+ writable: true,
374
+ });
375
+ return next;
376
+ }
362
377
  function globMatch(pattern, value) {
363
378
  const escaped = pattern.replace(/[.+?^\${}()|[\\]\\\\]/g, '\\\\$&');
364
379
  return new RegExp('^' + escaped.replace(/\\*/g, '.*') + '$', 's').test(value);
@@ -445,7 +460,12 @@ function schemaForTool(name, mocks) {
445
460
  const input = m.match && m.match.input;
446
461
  if (typeof input !== 'object' || input === null || Array.isArray(input)) continue;
447
462
  for (const k of Object.keys(input)) {
448
- properties[k] = inferJsonSchema(input[k]);
463
+ Object.defineProperty(properties, k, {
464
+ value: inferJsonSchema(input[k]),
465
+ enumerable: true,
466
+ configurable: true,
467
+ writable: true,
468
+ });
449
469
  if (!required.includes(k)) required.push(k);
450
470
  }
451
471
  }
@@ -476,9 +496,10 @@ function recordHit(mockKey) {
476
496
  const statsFile = path.join(path.dirname(mocksFile), 'hits.json');
477
497
  let stats = { perMock: {}, hits_total: 0, misses_total: 0 };
478
498
  try { if (fs.existsSync(statsFile)) stats = JSON.parse(fs.readFileSync(statsFile, 'utf8')); } catch {}
479
- if (!stats.perMock) stats.perMock = {};
480
- stats.perMock[mockKey] = (stats.perMock[mockKey] || 0) + 1;
481
- stats.hits_total = (stats.hits_total || 0) + 1;
499
+ if (!stats || typeof stats !== 'object' || Array.isArray(stats)) stats = {};
500
+ if (!stats.perMock || typeof stats.perMock !== 'object' || Array.isArray(stats.perMock)) stats.perMock = {};
501
+ incrementRecordCount(stats.perMock, mockKey);
502
+ stats.hits_total = recordCount(stats, 'hits_total') + 1;
482
503
  fs.writeFileSync(statsFile, JSON.stringify(stats));
483
504
  }
484
505
  function recordMiss() {
@@ -486,7 +507,8 @@ function recordMiss() {
486
507
  const statsFile = path.join(path.dirname(mocksFile), 'hits.json');
487
508
  let stats = { perMock: {}, hits_total: 0, misses_total: 0 };
488
509
  try { if (fs.existsSync(statsFile)) stats = JSON.parse(fs.readFileSync(statsFile, 'utf8')); } catch {}
489
- stats.misses_total = (stats.misses_total || 0) + 1;
510
+ if (!stats || typeof stats !== 'object' || Array.isArray(stats)) stats = {};
511
+ stats.misses_total = recordCount(stats, 'misses_total') + 1;
490
512
  fs.writeFileSync(statsFile, JSON.stringify(stats));
491
513
  }
492
514
  const hitCounters = new Map();
@@ -0,0 +1,12 @@
1
+ import type { ReportDocument, ReportIndexCard } from '../types/index.js';
2
+ export declare function isCanonicalReportId(value: unknown): value is string;
3
+ /**
4
+ * Parse a persisted report at every storage boundary. Canonical report files
5
+ * are the source of truth; index cards must never relax this contract.
6
+ */
7
+ export declare function parseReportDocument(value: unknown, fallbackId: string, expectedId?: string): ReportDocument | null;
8
+ /**
9
+ * Validate a lightweight report discovery card. The card is still only a
10
+ * pointer: consumers must parse `path` through `parseReportDocument` before use.
11
+ */
12
+ export declare function parseReportIndexCard(value: unknown): ReportIndexCard | null;