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.
- package/README.md +50 -18
- package/README.zh.md +55 -23
- package/dist/analysis/coverage-analyzer.d.ts +1 -0
- package/dist/analysis/coverage-analyzer.js +125 -62
- package/dist/analysis/failure-clusterer.js +2 -1
- package/dist/analysis/gap-analyzer.d.ts +2 -2
- package/dist/analysis/gap-analyzer.js +13 -3
- package/dist/analysis/hedging-classifier.d.ts +2 -2
- package/dist/analysis/hedging-classifier.js +3 -4
- package/dist/analysis/report-diagnostics.js +9 -7
- package/dist/analysis/sample-diagnostics.js +6 -6
- package/dist/artifact-graph/doctor.js +15 -7
- package/dist/assets/agent-skills/omk/SKILL.md +27 -7
- package/dist/assets/agent-skills/omk/references/commands.md +18 -17
- package/dist/authoring/evolver.d.ts +10 -6
- package/dist/authoring/evolver.js +496 -83
- package/dist/authoring/generator.d.ts +3 -3
- package/dist/authoring/generator.js +5 -10
- package/dist/authoring/sample-fixer.d.ts +8 -6
- package/dist/authoring/sample-fixer.js +76 -5
- package/dist/cli/commands/doctor.js +31 -14
- package/dist/cli/commands/eval/index.d.ts +3 -0
- package/dist/cli/commands/eval/index.js +163 -17
- package/dist/cli/commands/evolve.d.ts +4 -4
- package/dist/cli/commands/evolve.js +27 -13
- package/dist/cli/commands/init.js +16 -3
- package/dist/cli/commands/observe/inbox.js +28 -21
- package/dist/cli/commands/observe/index.js +20 -11
- package/dist/cli/commands/observe/ingest.d.ts +3 -0
- package/dist/cli/commands/observe/ingest.js +30 -2
- package/dist/cli/commands/sample.d.ts +6 -3
- package/dist/cli/commands/sample.js +72 -68
- package/dist/cli/lib/codex-model-hint.d.ts +9 -0
- package/dist/cli/lib/codex-model-hint.js +45 -0
- package/dist/cli/lib/generation-failure-hint.d.ts +2 -0
- package/dist/cli/lib/generation-failure-hint.js +61 -0
- package/dist/cli/lib/i18n-dict/common.d.ts +1 -1
- package/dist/cli/lib/i18n-dict/common.js +4 -0
- package/dist/cli/lib/i18n-dict/gen.d.ts +1 -1
- package/dist/cli/lib/i18n-dict/gen.js +38 -6
- package/dist/cli/lib/i18n-dict/help.js +6 -6
- package/dist/cli/lib/i18n-dict/init.d.ts +1 -1
- package/dist/cli/lib/i18n-dict/init.js +13 -9
- package/dist/cli/lib/i18n-dict/run.d.ts +1 -1
- package/dist/cli/lib/i18n-dict/run.js +34 -2
- package/dist/cli/lib/llm-failure-classifier.d.ts +2 -0
- package/dist/cli/lib/llm-failure-classifier.js +8 -0
- package/dist/cli/lib/parse-run-config.d.ts +6 -5
- package/dist/cli/lib/parse-run-config.js +16 -9
- package/dist/cli/lib/runtime-defaults.d.ts +21 -0
- package/dist/cli/lib/runtime-defaults.js +79 -0
- package/dist/diagnosis/observe-mapper.js +14 -15
- package/dist/diagnosis/observe-producer.js +3 -1
- package/dist/diagnosis/studio-projection.js +14 -7
- package/dist/diagnosis/types.d.ts +2 -0
- package/dist/diagnosis/types.js +12 -0
- package/dist/doctor/endpoint-rule.js +2 -1
- package/dist/eval-core/artifact-file-names.js +18 -1
- package/dist/eval-core/artifact-index.d.ts +7 -11
- package/dist/eval-core/artifact-index.js +139 -80
- package/dist/eval-core/cache.d.ts +12 -3
- package/dist/eval-core/cache.js +89 -29
- package/dist/eval-core/comparability.js +10 -6
- package/dist/eval-core/evaluation-execution.d.ts +2 -1
- package/dist/eval-core/evaluation-execution.js +122 -37
- package/dist/eval-core/evaluation-job.d.ts +4 -1
- package/dist/eval-core/evaluation-job.js +4 -1
- package/dist/eval-core/evaluation-reporting.d.ts +15 -13
- package/dist/eval-core/evaluation-reporting.js +54 -52
- package/dist/eval-core/execution-strategy.d.ts +2 -0
- package/dist/eval-core/execution-strategy.js +11 -9
- package/dist/eval-core/fact-checker.js +15 -7
- package/dist/eval-core/holdout.js +3 -2
- package/dist/eval-core/judge-independence.d.ts +2 -2
- package/dist/eval-core/mock-hook.cjs +23 -6
- package/dist/eval-core/mocks-runtime.js +30 -8
- package/dist/eval-core/report-document.d.ts +12 -0
- package/dist/eval-core/report-document.js +1151 -0
- package/dist/eval-core/report-extensions.d.ts +4 -0
- package/dist/eval-core/report-extensions.js +500 -0
- package/dist/eval-core/report-file-migration.js +7 -2
- package/dist/eval-core/resume-compatibility.d.ts +31 -0
- package/dist/eval-core/resume-compatibility.js +141 -0
- package/dist/eval-core/sample-fingerprint.d.ts +12 -0
- package/dist/eval-core/sample-fingerprint.js +193 -0
- package/dist/eval-core/schema.js +86 -31
- package/dist/eval-core/verdict.d.ts +8 -4
- package/dist/eval-core/verdict.js +24 -10
- package/dist/eval-workflows/batch-evaluation-workflow.d.ts +2 -1
- package/dist/eval-workflows/batch-evaluation-workflow.js +25 -12
- package/dist/eval-workflows/evaluation-pipeline/preflight-warnings.d.ts +10 -5
- package/dist/eval-workflows/evaluation-pipeline/preflight-warnings.js +58 -21
- package/dist/eval-workflows/evaluation-pipeline/report-finalize.js +3 -1
- package/dist/eval-workflows/evaluation-pipeline/run-state.d.ts +4 -1
- package/dist/eval-workflows/evaluation-pipeline/run-state.js +4 -1
- package/dist/eval-workflows/evaluation-pipeline/test-set-hash.d.ts +6 -5
- package/dist/eval-workflows/evaluation-pipeline/test-set-hash.js +17 -10
- package/dist/eval-workflows/evaluation-pipeline.js +12 -7
- package/dist/eval-workflows/run-evaluation.d.ts +9 -7
- package/dist/eval-workflows/run-evaluation.js +79 -51
- package/dist/executors/anthropic-api.js +65 -9
- package/dist/executors/claude-cli.js +16 -79
- package/dist/executors/claude-protocol.d.ts +28 -0
- package/dist/executors/claude-protocol.js +180 -0
- package/dist/executors/claude-sdk-trace.js +56 -28
- package/dist/executors/claude-sdk.d.ts +1 -0
- package/dist/executors/claude-sdk.js +39 -93
- package/dist/executors/codex-cli-trace.js +166 -31
- package/dist/executors/codex-cli.d.ts +6 -8
- package/dist/executors/codex-cli.js +49 -151
- package/dist/executors/codex-protocol.d.ts +24 -0
- package/dist/executors/codex-protocol.js +234 -0
- package/dist/executors/codex-sdk.js +68 -120
- package/dist/executors/gemini.js +88 -13
- package/dist/executors/index.d.ts +2 -3
- package/dist/executors/index.js +5 -3
- package/dist/executors/openai-api.js +70 -9
- package/dist/executors/runtime-fingerprint.js +88 -11
- package/dist/executors/script-command.d.ts +8 -0
- package/dist/executors/script-command.js +87 -0
- package/dist/executors/script.js +202 -29
- package/dist/executors/shared.d.ts +35 -3
- package/dist/executors/shared.js +113 -15
- package/dist/grading/assertions.d.ts +1 -1
- package/dist/grading/assertions.js +19 -9
- package/dist/grading/diagnostic.d.ts +9 -2
- package/dist/grading/diagnostic.js +25 -2
- package/dist/grading/index.js +10 -4
- package/dist/grading/judge.js +19 -6
- package/dist/grading/layered-scores.d.ts +2 -3
- package/dist/grading/layered-scores.js +2 -3
- package/dist/inputs/load-samples.d.ts +1 -2
- package/dist/inputs/load-samples.js +23 -1
- package/dist/inputs/mcp-resolver.js +6 -3
- package/dist/inputs/sample-document.d.ts +11 -0
- package/dist/inputs/sample-document.js +96 -0
- package/dist/managed/evidence.d.ts +1 -0
- package/dist/managed/evidence.js +1 -1
- package/dist/managed/store.js +200 -91
- package/dist/observability/codex-trace-adapter.d.ts +5 -0
- package/dist/observability/codex-trace-adapter.js +850 -0
- package/dist/observability/experience.d.ts +32 -6
- package/dist/observability/experience.js +2695 -459
- package/dist/observability/feedback-matchers.js +16 -1
- package/dist/observability/inbox-view-model.d.ts +1 -1
- package/dist/observability/inbox-view-model.js +19 -14
- package/dist/observability/inbox.d.ts +7 -1
- package/dist/observability/inbox.js +632 -124
- package/dist/observability/problem-patterns.js +2 -0
- package/dist/observability/review-state.d.ts +6 -0
- package/dist/observability/review-state.js +235 -63
- package/dist/observability/skill-chain-advisories.js +1 -1
- package/dist/observability/skill-chain.js +17 -4
- package/dist/observability/skill-health-analyzer.d.ts +32 -7
- package/dist/observability/skill-health-analyzer.js +194 -121
- package/dist/observability/skill-health-report.d.ts +10 -0
- package/dist/observability/skill-health-report.js +620 -0
- package/dist/observability/soft-standards/constants.d.ts +0 -1
- package/dist/observability/soft-standards/constants.js +0 -1
- package/dist/observability/soft-standards/index.d.ts +1 -1
- package/dist/observability/soft-standards/index.js +1 -1
- package/dist/observability/soft-standards/llm-extractor.js +8 -10
- package/dist/observability/soft-standards/skill-standards-store.d.ts +2 -1
- package/dist/observability/soft-standards/skill-standards-store.js +59 -18
- package/dist/observability/soft-standards/types.d.ts +2 -2
- package/dist/observability/trace-adapter.d.ts +12 -7
- package/dist/observability/trace-adapter.js +11 -9
- package/dist/observability/trace-attribution.d.ts +13 -5
- package/dist/observability/trace-attribution.js +315 -21
- package/dist/observability/trace-ingestion.d.ts +9 -0
- package/dist/observability/trace-ingestion.js +80 -0
- package/dist/observability/trace-ir.d.ts +113 -0
- package/dist/observability/trace-ir.js +87 -0
- package/dist/observability/trace-segmenter.d.ts +19 -6
- package/dist/observability/trace-segmenter.js +377 -196
- package/dist/observability/trace-session-index.d.ts +19 -0
- package/dist/observability/trace-session-index.js +68 -0
- package/dist/observability/trace-source.d.ts +12 -4
- package/dist/observability/trace-source.js +939 -215
- package/dist/renderer/html-renderer.js +37 -6
- package/dist/renderer/icons.js +3 -0
- package/dist/renderer/observation-inbox-renderer.js +208 -90
- package/dist/renderer/skill-detail-renderer.js +452 -109
- package/dist/renderer/skill-health-renderer.js +69 -12
- package/dist/renderer/summary.js +28 -7
- package/dist/renderer/table.js +21 -4
- package/dist/renderer/test-view.d.ts +1 -0
- package/dist/renderer/test-view.js +44 -9
- package/dist/server/indexed-report-store.js +14 -18
- package/dist/server/job-store.js +64 -26
- package/dist/server/report-server.js +190 -78
- package/dist/server/report-store.js +57 -80
- package/dist/server/skill-index.js +143 -49
- package/dist/server/skill-insights.js +44 -5
- package/dist/shared/artifact-graph.d.ts +3 -0
- package/dist/shared/artifact-graph.js +224 -0
- package/dist/shared/assertion-types.d.ts +8 -0
- package/dist/shared/assertion-types.js +46 -0
- package/dist/shared/atomic-json.d.ts +8 -0
- package/dist/shared/atomic-json.js +33 -0
- package/dist/shared/diagnosis-schema.d.ts +9 -0
- package/dist/shared/diagnosis-schema.js +181 -0
- package/dist/shared/doctor-report.d.ts +3 -0
- package/dist/shared/doctor-report.js +103 -0
- package/dist/shared/evaluation-job.d.ts +6 -0
- package/dist/shared/evaluation-job.js +217 -0
- package/dist/shared/executor-result.d.ts +17 -0
- package/dist/shared/executor-result.js +221 -0
- package/dist/shared/file-lock.d.ts +12 -0
- package/dist/shared/file-lock.js +129 -0
- package/dist/shared/json-value.d.ts +5 -0
- package/dist/shared/json-value.js +36 -0
- package/dist/shared/keyed-mutex.d.ts +7 -0
- package/dist/shared/keyed-mutex.js +24 -0
- package/dist/shared/record-count.d.ts +8 -0
- package/dist/shared/record-count.js +43 -0
- package/dist/shared/sample-contract.d.ts +3 -0
- package/dist/shared/sample-contract.js +332 -0
- package/dist/shared/timestamp.d.ts +6 -0
- package/dist/shared/timestamp.js +64 -0
- package/dist/shared/token-usage.d.ts +19 -0
- package/dist/shared/token-usage.js +50 -0
- package/dist/shared/tool-call-status.d.ts +8 -0
- package/dist/shared/tool-call-status.js +28 -0
- package/dist/shared/tool-identity.d.ts +21 -0
- package/dist/shared/tool-identity.js +84 -0
- package/dist/shared/tool-search.js +73 -16
- package/dist/shared/trace-projection.d.ts +5 -0
- package/dist/shared/trace-projection.js +20 -0
- package/dist/shared/trace-source-kind.d.ts +3 -0
- package/dist/shared/trace-source-kind.js +12 -0
- package/dist/types/diagnosis.d.ts +2 -0
- package/dist/types/eval.d.ts +4 -0
- package/dist/types/executor.d.ts +32 -5
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +1 -0
- package/dist/types/judge.d.ts +2 -0
- package/dist/types/observability.d.ts +116 -9
- package/dist/types/report.d.ts +58 -6
- package/dist/types/skill-index.d.ts +7 -0
- package/dist/types/trace.d.ts +2 -0
- package/dist/types/trace.js +1 -0
- package/package.json +9 -5
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { normalizeRfc3339Timestamp } from './timestamp.js';
|
|
2
|
+
const SOURCE_KINDS = new Set(['doctor', 'eval', 'observe']);
|
|
3
|
+
const ARTIFACT_KINDS = new Set(['skill', 'prompt', 'agent', 'workflow']);
|
|
4
|
+
const LAYERS = new Set(['definition', 'measurement', 'production']);
|
|
5
|
+
const NODE_ROLES = new Set(['entity', 'observation', 'aggregate']);
|
|
6
|
+
const NODE_KINDS = new Set([
|
|
7
|
+
'skill',
|
|
8
|
+
'skill_file',
|
|
9
|
+
'frontmatter',
|
|
10
|
+
'reference',
|
|
11
|
+
'script',
|
|
12
|
+
'tool',
|
|
13
|
+
'env',
|
|
14
|
+
'preflight',
|
|
15
|
+
'hard_rule',
|
|
16
|
+
'workflow',
|
|
17
|
+
'workflow_node',
|
|
18
|
+
'sample',
|
|
19
|
+
'assertion',
|
|
20
|
+
'variant',
|
|
21
|
+
'doctor_rule_result',
|
|
22
|
+
'eval_result',
|
|
23
|
+
'judge_dimension',
|
|
24
|
+
'diagnostic',
|
|
25
|
+
'trace_session',
|
|
26
|
+
'skill_invocation',
|
|
27
|
+
'tool_call',
|
|
28
|
+
'gap_signal',
|
|
29
|
+
]);
|
|
30
|
+
const STATUSES = new Set(['ok', 'warning', 'failed', 'skipped', 'unknown', 'not_measured']);
|
|
31
|
+
const BINDING_STRENGTHS = new Set([
|
|
32
|
+
'content-hash',
|
|
33
|
+
'source-locator',
|
|
34
|
+
'runtime-trace',
|
|
35
|
+
'name-only',
|
|
36
|
+
'aggregate',
|
|
37
|
+
'explicit',
|
|
38
|
+
]);
|
|
39
|
+
const EDGE_KINDS = new Set([
|
|
40
|
+
'contains',
|
|
41
|
+
'declares',
|
|
42
|
+
'requires',
|
|
43
|
+
'references',
|
|
44
|
+
'defines_workflow',
|
|
45
|
+
'next_step',
|
|
46
|
+
'covers',
|
|
47
|
+
'evaluates',
|
|
48
|
+
'passes',
|
|
49
|
+
'fails',
|
|
50
|
+
'diagnoses',
|
|
51
|
+
'invokes',
|
|
52
|
+
'calls_tool',
|
|
53
|
+
'observes',
|
|
54
|
+
'signals_gap',
|
|
55
|
+
'derived_from',
|
|
56
|
+
]);
|
|
57
|
+
const EVIDENCE_SOURCE_KINDS = new Set([
|
|
58
|
+
'skill-file',
|
|
59
|
+
'doctor-report',
|
|
60
|
+
'eval-report',
|
|
61
|
+
'observe-report',
|
|
62
|
+
'trace',
|
|
63
|
+
'sample',
|
|
64
|
+
'managed-record',
|
|
65
|
+
]);
|
|
66
|
+
const SELECTOR_KINDS = new Set([
|
|
67
|
+
'json-pointer',
|
|
68
|
+
'line-range',
|
|
69
|
+
'sample-id',
|
|
70
|
+
'rule-id',
|
|
71
|
+
'trace-event-id',
|
|
72
|
+
'node-id',
|
|
73
|
+
]);
|
|
74
|
+
const SUMMARY_KINDS = new Set(['structure', 'coverage', 'risk', 'gap', 'workflow', 'collection']);
|
|
75
|
+
const SUMMARY_SEVERITIES = new Set(['info', 'low', 'medium', 'high']);
|
|
76
|
+
/** Validate a complete graph, including graph identities and referential integrity. */
|
|
77
|
+
export function parseArtifactGraphDocument(value) {
|
|
78
|
+
if (!isRecord(value)
|
|
79
|
+
|| value.documentKind !== 'artifact-graph'
|
|
80
|
+
|| value.schemaVersion !== 1
|
|
81
|
+
|| !nonEmptyString(value.graphId)
|
|
82
|
+
|| normalizeRfc3339Timestamp(value.generatedAt) === undefined
|
|
83
|
+
|| !isSource(value.source)
|
|
84
|
+
|| !isScope(value.scope)
|
|
85
|
+
|| !Array.isArray(value.nodes)
|
|
86
|
+
|| !Array.isArray(value.edges)
|
|
87
|
+
|| !value.nodes.every(isNode)
|
|
88
|
+
|| !value.edges.every(isEdge)
|
|
89
|
+
|| (value.summaries !== undefined
|
|
90
|
+
&& (!Array.isArray(value.summaries) || !value.summaries.every(isSummary))))
|
|
91
|
+
return null;
|
|
92
|
+
const nodes = value.nodes;
|
|
93
|
+
const edges = value.edges;
|
|
94
|
+
const nodeIds = new Set(nodes.map((node) => node.id));
|
|
95
|
+
const stableKeys = new Set(nodes.map((node) => node.stableKey));
|
|
96
|
+
const edgeIds = new Set(edges.map((edge) => edge.id));
|
|
97
|
+
if (nodeIds.size !== nodes.length
|
|
98
|
+
|| stableKeys.size !== nodes.length
|
|
99
|
+
|| edgeIds.size !== edges.length
|
|
100
|
+
|| edges.some((edge) => !nodeIds.has(edge.fromNodeId) || !nodeIds.has(edge.toNodeId)))
|
|
101
|
+
return null;
|
|
102
|
+
const summaries = (value.summaries ?? []);
|
|
103
|
+
if (summaries.some((summary) => summary.nodeIds?.some((id) => !nodeIds.has(id))
|
|
104
|
+
|| summary.edgeIds?.some((id) => !edgeIds.has(id))))
|
|
105
|
+
return null;
|
|
106
|
+
return value;
|
|
107
|
+
}
|
|
108
|
+
function isSource(value) {
|
|
109
|
+
return isRecord(value)
|
|
110
|
+
&& SOURCE_KINDS.has(String(value.sourceKind))
|
|
111
|
+
&& nonEmptyString(value.sourceId)
|
|
112
|
+
&& optionalString(value.sourcePath)
|
|
113
|
+
&& optionalString(value.cliVersion);
|
|
114
|
+
}
|
|
115
|
+
function isScope(value) {
|
|
116
|
+
return isRecord(value)
|
|
117
|
+
&& typeof value.cwd === 'string'
|
|
118
|
+
&& (value.artifactKind === undefined
|
|
119
|
+
|| ARTIFACT_KINDS.has(String(value.artifactKind)))
|
|
120
|
+
&& [
|
|
121
|
+
value.skillName,
|
|
122
|
+
value.artifactHash,
|
|
123
|
+
value.sourceLocator,
|
|
124
|
+
value.sampleSetHash,
|
|
125
|
+
].every(optionalString);
|
|
126
|
+
}
|
|
127
|
+
function isNode(value) {
|
|
128
|
+
return isRecord(value)
|
|
129
|
+
&& nonEmptyString(value.id)
|
|
130
|
+
&& nonEmptyString(value.stableKey)
|
|
131
|
+
&& NODE_KINDS.has(String(value.nodeKind))
|
|
132
|
+
&& NODE_ROLES.has(String(value.nodeRole))
|
|
133
|
+
&& LAYERS.has(String(value.layer))
|
|
134
|
+
&& nonEmptyString(value.label)
|
|
135
|
+
&& optionalEnum(value.status, STATUSES)
|
|
136
|
+
&& optionalRate(value.confidence)
|
|
137
|
+
&& optionalBinding(value.binding)
|
|
138
|
+
&& optionalMetrics(value.metrics)
|
|
139
|
+
&& optionalAttrs(value.attrs)
|
|
140
|
+
&& optionalEvidenceRefs(value.evidenceRefs);
|
|
141
|
+
}
|
|
142
|
+
function isEdge(value) {
|
|
143
|
+
return isRecord(value)
|
|
144
|
+
&& nonEmptyString(value.id)
|
|
145
|
+
&& nonEmptyString(value.fromNodeId)
|
|
146
|
+
&& nonEmptyString(value.toNodeId)
|
|
147
|
+
&& EDGE_KINDS.has(String(value.edgeKind))
|
|
148
|
+
&& LAYERS.has(String(value.layer))
|
|
149
|
+
&& optionalString(value.label)
|
|
150
|
+
&& optionalEnum(value.status, STATUSES)
|
|
151
|
+
&& optionalRate(value.confidence)
|
|
152
|
+
&& (value.weight === undefined || isFiniteMetric(value.weight))
|
|
153
|
+
&& optionalBinding(value.binding)
|
|
154
|
+
&& optionalMetrics(value.metrics)
|
|
155
|
+
&& optionalAttrs(value.attrs)
|
|
156
|
+
&& optionalEvidenceRefs(value.evidenceRefs);
|
|
157
|
+
}
|
|
158
|
+
function isSummary(value) {
|
|
159
|
+
return isRecord(value)
|
|
160
|
+
&& SUMMARY_KINDS.has(String(value.summaryKind))
|
|
161
|
+
&& nonEmptyString(value.title)
|
|
162
|
+
&& SUMMARY_SEVERITIES.has(String(value.severity))
|
|
163
|
+
&& optionalStringArray(value.nodeIds)
|
|
164
|
+
&& optionalStringArray(value.edgeIds)
|
|
165
|
+
&& optionalEvidenceRefs(value.evidenceRefs);
|
|
166
|
+
}
|
|
167
|
+
function optionalBinding(value) {
|
|
168
|
+
return value === undefined || (isRecord(value)
|
|
169
|
+
&& BINDING_STRENGTHS.has(String(value.bindingStrength))
|
|
170
|
+
&& isRecord(value.keys)
|
|
171
|
+
&& Object.values(value.keys).every((entry) => typeof entry === 'string'));
|
|
172
|
+
}
|
|
173
|
+
function optionalMetrics(value) {
|
|
174
|
+
return value === undefined || (isRecord(value) && Object.values(value).every(isFiniteMetric));
|
|
175
|
+
}
|
|
176
|
+
function optionalAttrs(value) {
|
|
177
|
+
return value === undefined || (isRecord(value)
|
|
178
|
+
&& [value.display, value.producer, value.experimental].every((entry) => entry === undefined || isRecord(entry)));
|
|
179
|
+
}
|
|
180
|
+
function optionalEvidenceRefs(value) {
|
|
181
|
+
return value === undefined || (Array.isArray(value) && value.every(isEvidenceRef));
|
|
182
|
+
}
|
|
183
|
+
function isEvidenceRef(value) {
|
|
184
|
+
return isRecord(value)
|
|
185
|
+
&& EVIDENCE_SOURCE_KINDS.has(String(value.sourceKind))
|
|
186
|
+
&& [
|
|
187
|
+
value.sourceId,
|
|
188
|
+
value.path,
|
|
189
|
+
value.contentHash,
|
|
190
|
+
value.label,
|
|
191
|
+
value.snippet,
|
|
192
|
+
].every(optionalString)
|
|
193
|
+
&& (value.redaction === undefined
|
|
194
|
+
|| value.redaction === 'none'
|
|
195
|
+
|| value.redaction === 'truncated'
|
|
196
|
+
|| value.redaction === 'redacted')
|
|
197
|
+
&& (value.selector === undefined
|
|
198
|
+
|| (isRecord(value.selector)
|
|
199
|
+
&& SELECTOR_KINDS.has(String(value.selector.selectorKind))
|
|
200
|
+
&& typeof value.selector.value === 'string'));
|
|
201
|
+
}
|
|
202
|
+
function optionalEnum(value, values) {
|
|
203
|
+
return value === undefined || values.has(String(value));
|
|
204
|
+
}
|
|
205
|
+
function optionalRate(value) {
|
|
206
|
+
return value === undefined || (typeof value === 'number' && Number.isFinite(value) && value >= 0 && value <= 1);
|
|
207
|
+
}
|
|
208
|
+
function isFiniteMetric(value) {
|
|
209
|
+
return typeof value === 'number'
|
|
210
|
+
&& Number.isFinite(value)
|
|
211
|
+
&& Math.abs(value) <= Number.MAX_SAFE_INTEGER;
|
|
212
|
+
}
|
|
213
|
+
function optionalString(value) {
|
|
214
|
+
return value === undefined || typeof value === 'string';
|
|
215
|
+
}
|
|
216
|
+
function optionalStringArray(value) {
|
|
217
|
+
return value === undefined || (Array.isArray(value) && value.every(nonEmptyString));
|
|
218
|
+
}
|
|
219
|
+
function nonEmptyString(value) {
|
|
220
|
+
return typeof value === 'string' && value.length > 0;
|
|
221
|
+
}
|
|
222
|
+
function isRecord(value) {
|
|
223
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
224
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare const SYNC_ASSERTION_TYPE_NAMES: readonly ["contains", "not_contains", "regex", "min_length", "max_length", "json_valid", "json_schema", "starts_with", "ends_with", "equals", "not_equals", "word_count_min", "word_count_max", "contains_all", "contains_any", "cost_max", "latency_max", "turns_max", "turns_min", "tools_called", "tools_not_called", "tools_count_max", "tools_count_min", "tool_output_contains", "tool_input_contains", "tool_input_not_contains", "mock_hit", "rouge_n_min", "levenshtein_max", "bleu_min", "assert-set"];
|
|
2
|
+
export declare const ASYNC_ASSERTION_TYPE_NAMES: readonly ["semantic_similarity", "custom", "faithfulness", "answer_relevancy", "context_recall"];
|
|
3
|
+
export type SyncAssertionType = typeof SYNC_ASSERTION_TYPE_NAMES[number];
|
|
4
|
+
export type AsyncAssertionType = typeof ASYNC_ASSERTION_TYPE_NAMES[number];
|
|
5
|
+
export type SupportedAssertionType = SyncAssertionType | AsyncAssertionType;
|
|
6
|
+
export declare const SYNC_ASSERTION_TYPES: ReadonlySet<string>;
|
|
7
|
+
export declare const ASYNC_ASSERTION_TYPES: ReadonlySet<string>;
|
|
8
|
+
export declare const SUPPORTED_ASSERTION_TYPES: ReadonlySet<string>;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export const SYNC_ASSERTION_TYPE_NAMES = [
|
|
2
|
+
'contains',
|
|
3
|
+
'not_contains',
|
|
4
|
+
'regex',
|
|
5
|
+
'min_length',
|
|
6
|
+
'max_length',
|
|
7
|
+
'json_valid',
|
|
8
|
+
'json_schema',
|
|
9
|
+
'starts_with',
|
|
10
|
+
'ends_with',
|
|
11
|
+
'equals',
|
|
12
|
+
'not_equals',
|
|
13
|
+
'word_count_min',
|
|
14
|
+
'word_count_max',
|
|
15
|
+
'contains_all',
|
|
16
|
+
'contains_any',
|
|
17
|
+
'cost_max',
|
|
18
|
+
'latency_max',
|
|
19
|
+
'turns_max',
|
|
20
|
+
'turns_min',
|
|
21
|
+
'tools_called',
|
|
22
|
+
'tools_not_called',
|
|
23
|
+
'tools_count_max',
|
|
24
|
+
'tools_count_min',
|
|
25
|
+
'tool_output_contains',
|
|
26
|
+
'tool_input_contains',
|
|
27
|
+
'tool_input_not_contains',
|
|
28
|
+
'mock_hit',
|
|
29
|
+
'rouge_n_min',
|
|
30
|
+
'levenshtein_max',
|
|
31
|
+
'bleu_min',
|
|
32
|
+
'assert-set',
|
|
33
|
+
];
|
|
34
|
+
export const ASYNC_ASSERTION_TYPE_NAMES = [
|
|
35
|
+
'semantic_similarity',
|
|
36
|
+
'custom',
|
|
37
|
+
'faithfulness',
|
|
38
|
+
'answer_relevancy',
|
|
39
|
+
'context_recall',
|
|
40
|
+
];
|
|
41
|
+
export const SYNC_ASSERTION_TYPES = new Set(SYNC_ASSERTION_TYPE_NAMES);
|
|
42
|
+
export const ASYNC_ASSERTION_TYPES = new Set(ASYNC_ASSERTION_TYPE_NAMES);
|
|
43
|
+
export const SUPPORTED_ASSERTION_TYPES = new Set([
|
|
44
|
+
...SYNC_ASSERTION_TYPE_NAMES,
|
|
45
|
+
...ASYNC_ASSERTION_TYPE_NAMES,
|
|
46
|
+
]);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Publish JSON with a same-directory rename so readers never observe a partial file.
|
|
3
|
+
*/
|
|
4
|
+
export declare function writeJsonFileAtomic(path: string, value: unknown): void;
|
|
5
|
+
/**
|
|
6
|
+
* Async counterpart of {@link writeJsonFileAtomic}.
|
|
7
|
+
*/
|
|
8
|
+
export declare function writeJsonFileAtomicAsync(path: string, value: unknown): Promise<void>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { existsSync, mkdirSync, renameSync, rmSync, writeFileSync, } from 'node:fs';
|
|
3
|
+
import { mkdir, rename, rm, writeFile, } from 'node:fs/promises';
|
|
4
|
+
import { dirname } from 'node:path';
|
|
5
|
+
/**
|
|
6
|
+
* Publish JSON with a same-directory rename so readers never observe a partial file.
|
|
7
|
+
*/
|
|
8
|
+
export function writeJsonFileAtomic(path, value) {
|
|
9
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
10
|
+
const tempPath = `${path}.${process.pid}.${randomUUID()}.tmp`;
|
|
11
|
+
try {
|
|
12
|
+
writeFileSync(tempPath, JSON.stringify(value, null, 2));
|
|
13
|
+
renameSync(tempPath, path);
|
|
14
|
+
}
|
|
15
|
+
finally {
|
|
16
|
+
if (existsSync(tempPath))
|
|
17
|
+
rmSync(tempPath, { force: true });
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Async counterpart of {@link writeJsonFileAtomic}.
|
|
22
|
+
*/
|
|
23
|
+
export async function writeJsonFileAtomicAsync(path, value) {
|
|
24
|
+
await mkdir(dirname(path), { recursive: true });
|
|
25
|
+
const tempPath = `${path}.${process.pid}.${randomUUID()}.tmp`;
|
|
26
|
+
try {
|
|
27
|
+
await writeFile(tempPath, JSON.stringify(value, null, 2));
|
|
28
|
+
await rename(tempPath, path);
|
|
29
|
+
}
|
|
30
|
+
finally {
|
|
31
|
+
await rm(tempPath, { force: true });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { DiagnosisBundle } from '../types/diagnosis.js';
|
|
2
|
+
/**
|
|
3
|
+
* Validate the persisted Diagnosis contract before downstream projections use it.
|
|
4
|
+
*
|
|
5
|
+
* Keep this parser independent from observe-inbox: doctor and eval can produce the
|
|
6
|
+
* same source-neutral bundle, and every persistence boundary should share one
|
|
7
|
+
* contract instead of maintaining source-specific casts.
|
|
8
|
+
*/
|
|
9
|
+
export declare function parseDiagnosisBundle(value: unknown): DiagnosisBundle | null;
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { isRfc3339Timestamp } from './timestamp.js';
|
|
2
|
+
const DIAGNOSIS_TYPES = new Set([
|
|
3
|
+
'definition_gap',
|
|
4
|
+
'runtime_issue',
|
|
5
|
+
'user_feedback_pattern',
|
|
6
|
+
'eval_failure',
|
|
7
|
+
'sample_design_issue',
|
|
8
|
+
'doctor_gap',
|
|
9
|
+
'standard_candidate',
|
|
10
|
+
'maintenance_issue',
|
|
11
|
+
]);
|
|
12
|
+
const DIAGNOSIS_SEVERITIES = new Set(['high', 'medium', 'low', 'info']);
|
|
13
|
+
const DIAGNOSIS_AUDIENCES = new Set(['skill-author', 'sample-author', 'omk-maintainer', 'reviewer']);
|
|
14
|
+
const DIAGNOSIS_LIFECYCLES = new Set(['detected', 'candidate', 'confirmed', 'rejected', 'resolved', 'stale']);
|
|
15
|
+
const DIAGNOSIS_SCOPES = new Set(['skill', 'definition', 'session', 'sample']);
|
|
16
|
+
const DIAGNOSIS_SOURCES = new Set(['observe', 'doctor', 'eval']);
|
|
17
|
+
const DIAGNOSIS_PRODUCERS = new Set(['deterministic_rule', 'llm_soft', 'manual']);
|
|
18
|
+
const DIAGNOSIS_PATCH_TARGETS = new Set([
|
|
19
|
+
'skill',
|
|
20
|
+
'sample-environment',
|
|
21
|
+
'sample-mocks',
|
|
22
|
+
'doctor-rule',
|
|
23
|
+
'definition',
|
|
24
|
+
]);
|
|
25
|
+
/**
|
|
26
|
+
* Validate the persisted Diagnosis contract before downstream projections use it.
|
|
27
|
+
*
|
|
28
|
+
* Keep this parser independent from observe-inbox: doctor and eval can produce the
|
|
29
|
+
* same source-neutral bundle, and every persistence boundary should share one
|
|
30
|
+
* contract instead of maintaining source-specific casts.
|
|
31
|
+
*/
|
|
32
|
+
export function parseDiagnosisBundle(value) {
|
|
33
|
+
if (!isRecord(value) || value.schemaVersion !== 1 || !isTimestamp(value.generatedAt))
|
|
34
|
+
return null;
|
|
35
|
+
if (!isSourceCoverage(value.sourceCoverage) || !isRecord(value.bySkill))
|
|
36
|
+
return null;
|
|
37
|
+
const diagnosisIds = new Set();
|
|
38
|
+
const stableKeys = new Set();
|
|
39
|
+
const occurrenceIds = new Set();
|
|
40
|
+
for (const [skillName, diagnoses] of Object.entries(value.bySkill)) {
|
|
41
|
+
if (!isNonEmptyString(skillName) || !Array.isArray(diagnoses))
|
|
42
|
+
return null;
|
|
43
|
+
for (const diagnosis of diagnoses) {
|
|
44
|
+
if (!isDiagnosis(diagnosis, skillName, value.sourceCoverage)
|
|
45
|
+
|| diagnosisIds.has(diagnosis.id)
|
|
46
|
+
|| stableKeys.has(diagnosis.stableKey))
|
|
47
|
+
return null;
|
|
48
|
+
diagnosisIds.add(diagnosis.id);
|
|
49
|
+
stableKeys.add(diagnosis.stableKey);
|
|
50
|
+
for (const occurrence of diagnosis.occurrences) {
|
|
51
|
+
if (occurrenceIds.has(occurrence.id))
|
|
52
|
+
return null;
|
|
53
|
+
occurrenceIds.add(occurrence.id);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return value;
|
|
58
|
+
}
|
|
59
|
+
function isDiagnosis(value, skillName, sourceCoverage) {
|
|
60
|
+
if (!isRecord(value))
|
|
61
|
+
return false;
|
|
62
|
+
if (!isNonEmptyString(value.id)
|
|
63
|
+
|| !isNonEmptyString(value.stableKey)
|
|
64
|
+
|| value.skillName !== skillName
|
|
65
|
+
|| !isEnum(value.type, DIAGNOSIS_TYPES)
|
|
66
|
+
|| !isNonEmptyString(value.signal)
|
|
67
|
+
|| !isNonEmptyString(value.title)
|
|
68
|
+
|| !isOptionalString(value.summary)
|
|
69
|
+
|| !isEnum(value.severity, DIAGNOSIS_SEVERITIES)
|
|
70
|
+
|| !isEnum(value.audience, DIAGNOSIS_AUDIENCES)
|
|
71
|
+
|| !isEnum(value.lifecycle, DIAGNOSIS_LIFECYCLES)
|
|
72
|
+
|| !isScope(value.scope, skillName)
|
|
73
|
+
|| !Array.isArray(value.occurrences)
|
|
74
|
+
|| !isNonNegativeInteger(value.occurrenceCount)
|
|
75
|
+
|| value.occurrenceCount < value.occurrences.length
|
|
76
|
+
|| !isOptionalString(value.evidenceSummary)
|
|
77
|
+
|| !isOptionalString(value.recommendation)
|
|
78
|
+
|| !isOptionalString(value.command)
|
|
79
|
+
|| !isOptionalPatch(value.patch))
|
|
80
|
+
return false;
|
|
81
|
+
const occurrenceIds = new Set();
|
|
82
|
+
const stableKey = value.stableKey;
|
|
83
|
+
return value.occurrences.every((occurrence) => {
|
|
84
|
+
if (!isOccurrence(occurrence, stableKey, sourceCoverage)
|
|
85
|
+
|| occurrenceIds.has(occurrence.id))
|
|
86
|
+
return false;
|
|
87
|
+
occurrenceIds.add(occurrence.id);
|
|
88
|
+
return true;
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
function isScope(value, skillName) {
|
|
92
|
+
if (!isRecord(value) || !isEnum(value.primary, DIAGNOSIS_SCOPES) || !isRecord(value.refs))
|
|
93
|
+
return false;
|
|
94
|
+
const refs = value.refs;
|
|
95
|
+
if (refs.skillName !== skillName)
|
|
96
|
+
return false;
|
|
97
|
+
return [
|
|
98
|
+
'traceId',
|
|
99
|
+
'sessionId',
|
|
100
|
+
'invocationId',
|
|
101
|
+
'sampleId',
|
|
102
|
+
'ruleId',
|
|
103
|
+
'workflowId',
|
|
104
|
+
'sourceTrace',
|
|
105
|
+
].every((key) => isOptionalString(refs[key]));
|
|
106
|
+
}
|
|
107
|
+
function isOccurrence(value, stableKey, sourceCoverage) {
|
|
108
|
+
if (!isRecord(value))
|
|
109
|
+
return false;
|
|
110
|
+
if (!isNonEmptyString(value.id)
|
|
111
|
+
|| value.diagnosisStableKey !== stableKey
|
|
112
|
+
|| !isEnum(value.source, DIAGNOSIS_SOURCES)
|
|
113
|
+
|| sourceCoverage[value.source] !== true
|
|
114
|
+
|| !isNonEmptyString(value.sourceId)
|
|
115
|
+
|| !isNonEmptyString(value.sourceKind)
|
|
116
|
+
|| !isOptionalTimestamp(value.timestamp)
|
|
117
|
+
|| !isOptionalEnum(value.severity, DIAGNOSIS_SEVERITIES)
|
|
118
|
+
|| !Array.isArray(value.evidenceRefs)
|
|
119
|
+
|| !value.evidenceRefs.every(isEvidenceRef)
|
|
120
|
+
|| !isOptionalString(value.rawRef)
|
|
121
|
+
|| !isEnum(value.producer, DIAGNOSIS_PRODUCERS)
|
|
122
|
+
|| (value.payload !== undefined && !isRecord(value.payload)))
|
|
123
|
+
return false;
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
function isEvidenceRef(value) {
|
|
127
|
+
if (!isRecord(value) || !isNonEmptyString(value.id) || !isNonEmptyString(value.kind))
|
|
128
|
+
return false;
|
|
129
|
+
if (![
|
|
130
|
+
'traceId',
|
|
131
|
+
'sourceTrace',
|
|
132
|
+
'sessionId',
|
|
133
|
+
'messageUuid',
|
|
134
|
+
'toolUseId',
|
|
135
|
+
'label',
|
|
136
|
+
'snippet',
|
|
137
|
+
].every((key) => isOptionalString(value[key]))
|
|
138
|
+
|| !['messageIndex', 'logicalMessageIndex', 'sourceLineIndex']
|
|
139
|
+
.every((key) => value[key] === undefined || isNonNegativeInteger(value[key]))
|
|
140
|
+
|| !isOptionalTimestamp(value.timestamp))
|
|
141
|
+
return false;
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
function isOptionalPatch(value) {
|
|
145
|
+
if (value === undefined)
|
|
146
|
+
return true;
|
|
147
|
+
return isRecord(value)
|
|
148
|
+
&& isEnum(value.target, DIAGNOSIS_PATCH_TARGETS)
|
|
149
|
+
&& isNonEmptyString(value.location)
|
|
150
|
+
&& isNonEmptyString(value.snippet);
|
|
151
|
+
}
|
|
152
|
+
function isSourceCoverage(value) {
|
|
153
|
+
return isRecord(value)
|
|
154
|
+
&& typeof value.observe === 'boolean'
|
|
155
|
+
&& typeof value.doctor === 'boolean'
|
|
156
|
+
&& typeof value.eval === 'boolean';
|
|
157
|
+
}
|
|
158
|
+
function isRecord(value) {
|
|
159
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
160
|
+
}
|
|
161
|
+
function isNonEmptyString(value) {
|
|
162
|
+
return typeof value === 'string' && value.trim().length > 0;
|
|
163
|
+
}
|
|
164
|
+
function isOptionalString(value) {
|
|
165
|
+
return value === undefined || typeof value === 'string';
|
|
166
|
+
}
|
|
167
|
+
function isTimestamp(value) {
|
|
168
|
+
return isRfc3339Timestamp(value);
|
|
169
|
+
}
|
|
170
|
+
function isOptionalTimestamp(value) {
|
|
171
|
+
return value === undefined || isTimestamp(value);
|
|
172
|
+
}
|
|
173
|
+
function isNonNegativeInteger(value) {
|
|
174
|
+
return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0;
|
|
175
|
+
}
|
|
176
|
+
function isEnum(value, allowed) {
|
|
177
|
+
return typeof value === 'string' && allowed.has(value);
|
|
178
|
+
}
|
|
179
|
+
function isOptionalEnum(value, allowed) {
|
|
180
|
+
return value === undefined || isEnum(value, allowed);
|
|
181
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { normalizeRfc3339Timestamp } from './timestamp.js';
|
|
2
|
+
const RULE_STATUSES = new Set(['pass', 'warn', 'fail', 'skipped']);
|
|
3
|
+
const SEVERITIES = new Set(['fatal', 'warn', 'info']);
|
|
4
|
+
const SKILL_STATUSES = new Set(['pass', 'warn', 'fail']);
|
|
5
|
+
const OUTCOMES = new Set(['passed', 'warnings_only', 'failed']);
|
|
6
|
+
/** Parse a persisted doctor report without trusting JSON casts at Studio boundaries. */
|
|
7
|
+
export function parseDoctorReport(value) {
|
|
8
|
+
if (!isRecord(value)
|
|
9
|
+
|| value.kind !== 'doctor'
|
|
10
|
+
|| typeof value.schemaVersion !== 'string'
|
|
11
|
+
|| value.schemaVersion.length === 0
|
|
12
|
+
|| typeof value.id !== 'string'
|
|
13
|
+
|| value.id.length === 0
|
|
14
|
+
|| normalizeRfc3339Timestamp(value.timestamp) === undefined
|
|
15
|
+
|| !allStrings([
|
|
16
|
+
value.cliVersion,
|
|
17
|
+
value.cwd,
|
|
18
|
+
value.executorName,
|
|
19
|
+
value.model,
|
|
20
|
+
])
|
|
21
|
+
|| !Array.isArray(value.skills)
|
|
22
|
+
|| !OUTCOMES.has(String(value.outcome))
|
|
23
|
+
|| !isCountRecord(value.totals, ['pass', 'warn', 'fail'])
|
|
24
|
+
|| !isCountRecord(value.ruleStats, ['pass', 'warn', 'fail', 'skipped', 'total']))
|
|
25
|
+
return null;
|
|
26
|
+
const skills = value.skills;
|
|
27
|
+
if (!skills.every(isDoctorSkillReport))
|
|
28
|
+
return null;
|
|
29
|
+
const totals = value.totals;
|
|
30
|
+
const ruleStats = value.ruleStats;
|
|
31
|
+
const expectedTotals = { pass: 0, warn: 0, fail: 0 };
|
|
32
|
+
const expectedRuleStats = { pass: 0, warn: 0, fail: 0, skipped: 0 };
|
|
33
|
+
for (const skill of skills) {
|
|
34
|
+
expectedTotals[skill.status] += 1;
|
|
35
|
+
for (const result of skill.results) {
|
|
36
|
+
expectedRuleStats[result.status] += 1;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const ruleTotal = Object.values(expectedRuleStats).reduce((sum, count) => sum + count, 0);
|
|
40
|
+
if (!sameCounts(totals, expectedTotals)
|
|
41
|
+
|| !sameCounts(ruleStats, expectedRuleStats)
|
|
42
|
+
|| ruleStats.total !== ruleTotal)
|
|
43
|
+
return null;
|
|
44
|
+
const expectedOutcome = totals.fail > 0
|
|
45
|
+
? 'failed'
|
|
46
|
+
: totals.warn > 0
|
|
47
|
+
? 'warnings_only'
|
|
48
|
+
: 'passed';
|
|
49
|
+
return value.outcome === expectedOutcome ? value : null;
|
|
50
|
+
}
|
|
51
|
+
function isDoctorSkillReport(value) {
|
|
52
|
+
if (!isRecord(value)
|
|
53
|
+
|| typeof value.skillName !== 'string'
|
|
54
|
+
|| value.skillName.length === 0
|
|
55
|
+
|| typeof value.skillPath !== 'string'
|
|
56
|
+
|| !Array.isArray(value.results)
|
|
57
|
+
|| !SKILL_STATUSES.has(String(value.status))
|
|
58
|
+
|| !value.results.every(isDoctorRuleResult))
|
|
59
|
+
return false;
|
|
60
|
+
return value.status === classifySkillStatus(value.results);
|
|
61
|
+
}
|
|
62
|
+
function isDoctorRuleResult(value) {
|
|
63
|
+
return isRecord(value)
|
|
64
|
+
&& allStrings([value.ruleId, value.labelKey, value.message])
|
|
65
|
+
&& String(value.ruleId).length > 0
|
|
66
|
+
&& String(value.labelKey).length > 0
|
|
67
|
+
&& SEVERITIES.has(String(value.severity))
|
|
68
|
+
&& RULE_STATUSES.has(String(value.status))
|
|
69
|
+
&& isNonNegativeNumber(value.durationMs)
|
|
70
|
+
&& (value.hint === undefined || typeof value.hint === 'string')
|
|
71
|
+
&& (value.groupId === undefined || typeof value.groupId === 'string')
|
|
72
|
+
&& (value.detail === undefined || isRecord(value.detail));
|
|
73
|
+
}
|
|
74
|
+
function classifySkillStatus(results) {
|
|
75
|
+
if (results.some((result) => result.status === 'fail' && result.severity === 'fatal')) {
|
|
76
|
+
return 'fail';
|
|
77
|
+
}
|
|
78
|
+
if (results.some((result) => result.status === 'warn' || result.status === 'fail')) {
|
|
79
|
+
return 'warn';
|
|
80
|
+
}
|
|
81
|
+
return 'pass';
|
|
82
|
+
}
|
|
83
|
+
function isCountRecord(value, keys) {
|
|
84
|
+
return isRecord(value) && keys.every((key) => isNonNegativeInteger(value[key]));
|
|
85
|
+
}
|
|
86
|
+
function sameCounts(actual, expected) {
|
|
87
|
+
return Object.entries(expected).every(([key, count]) => actual[key] === count);
|
|
88
|
+
}
|
|
89
|
+
function isNonNegativeInteger(value) {
|
|
90
|
+
return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0;
|
|
91
|
+
}
|
|
92
|
+
function isNonNegativeNumber(value) {
|
|
93
|
+
return typeof value === 'number'
|
|
94
|
+
&& Number.isFinite(value)
|
|
95
|
+
&& value >= 0
|
|
96
|
+
&& value <= Number.MAX_SAFE_INTEGER;
|
|
97
|
+
}
|
|
98
|
+
function allStrings(values) {
|
|
99
|
+
return values.every((value) => typeof value === 'string');
|
|
100
|
+
}
|
|
101
|
+
function isRecord(value) {
|
|
102
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
103
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { EvaluationJob, EvaluationRequest, EvaluationRun } from '../types/index.js';
|
|
2
|
+
export declare function isSafeEvaluationJobId(value: unknown): value is string;
|
|
3
|
+
export declare function isValidEvaluationRequest(value: unknown): value is EvaluationRequest;
|
|
4
|
+
export declare function isValidEvaluationRun(value: unknown, expectedRunId?: string): value is EvaluationRun;
|
|
5
|
+
export declare function isValidEvaluationJob(value: unknown, expectedJobId?: string): value is EvaluationJob;
|
|
6
|
+
export declare function evaluationRequestsEqual(left: EvaluationRequest, right: EvaluationRequest): boolean;
|