oh-my-knowledge 0.0.1

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 (2) hide show
  1. package/README.md +448 -0
  2. package/package.json +15 -0
package/README.md ADDED
@@ -0,0 +1,448 @@
1
+ # oh-my-knowledge
2
+
3
+ Knowledge artifact evaluation toolkit — benchmark your skills with objective data.
4
+
5
+ English | [中文](./README.zh-CN.md)
6
+
7
+ **Fixed model, variable knowledge artifact, data speaks.**
8
+
9
+ ## Why
10
+
11
+ Teams building AI skills (system prompts, knowledge packages, rule sets) need objective data to prove v2 is better than v1. `oh-my-knowledge` runs controlled experiments: same model, same test cases, only the knowledge artifact changes.
12
+
13
+ ## Quick Start
14
+
15
+ ```bash
16
+ # Install globally
17
+ npm i -g oh-my-knowledge
18
+
19
+ # Scaffold a new eval project
20
+ omk bench init my-eval
21
+ cd my-eval
22
+
23
+ # Preview the evaluation plan
24
+ omk bench run --dry-run
25
+
26
+ # Run the evaluation
27
+ omk bench run --variants v1,v2
28
+
29
+ # View the report
30
+ omk bench report
31
+ # Open http://127.0.0.1:7799
32
+ ```
33
+
34
+ ## How It Works
35
+
36
+ ```
37
+ eval-samples.json skills/v1.md skills/v2.md
38
+ │ │ │
39
+ └────────┬───────────┘ │
40
+ │ │
41
+ ┌──────▼──────┐ ┌──────▼──────┐
42
+ │ sample + │ │ sample + │
43
+ │ skill v1 │ │ skill v2 │
44
+ └──────┬──────┘ └──────┬──────┘
45
+ │ │
46
+ ┌──────▼──────┐ ┌──────▼──────┐
47
+ │ Executor │ │ Executor │
48
+ │ claude │ │ claude │
49
+ │ openai │ │ openai │
50
+ │ gemini │ │ gemini │
51
+ └──────┬──────┘ └──────┬──────┘
52
+ │ │
53
+ ┌──────▼──────────────────────▼──────┐
54
+ │ Grading │
55
+ │ ┌─────────────┐ ┌──────────────┐ │
56
+ │ │ Assertions │ │ LLM Judge │ │
57
+ │ │ (18 types) │ │ (rubric or │ │
58
+ │ │ │ │ dimensions) │ │
59
+ │ └─────────────┘ └──────────────┘ │
60
+ └──────────────────┬─────────────────┘
61
+
62
+ ┌─────────▼─────────┐
63
+ │ Report + Analysis │
64
+ │ (JSON/HTML) │
65
+ └───────────────────┘
66
+ ```
67
+
68
+ ## Eval Sample Format
69
+
70
+ Supports both JSON and YAML (`eval-samples.json`, `eval-samples.yaml`, `eval-samples.yml`).
71
+
72
+ The file contains an array of sample objects. Each sample represents one test case for evaluating a skill.
73
+
74
+ ```json
75
+ [
76
+ {
77
+ "sample_id": "s001",
78
+ "prompt": "Review this code",
79
+ "context": "function auth(u, p) { db.query('SELECT * FROM users WHERE name=' + u); }",
80
+ "rubric": "Should identify SQL injection and suggest parameterized queries",
81
+ "assertions": [
82
+ { "type": "contains", "value": "SQL injection", "weight": 1 },
83
+ { "type": "contains", "value": "parameterized", "weight": 1 },
84
+ { "type": "not_contains", "value": "looks good", "weight": 0.5 },
85
+ { "type": "json_valid" },
86
+ { "type": "cost_max", "value": 0.01 },
87
+ { "type": "custom", "fn": "my-assertion.mjs", "weight": 1 }
88
+ ],
89
+ "dimensions": {
90
+ "security": "Should identify injection vulnerability",
91
+ "actionability": "Should provide concrete fix with code"
92
+ }
93
+ }
94
+ ]
95
+ ```
96
+
97
+ ### Field Reference
98
+
99
+ | Field | Type | Required | Description |
100
+ |-------|------|----------|-------------|
101
+ | `sample_id` | `string` | **Yes** | Unique identifier for the sample (e.g., `"s001"`). Used in reports and analysis to reference this test case. |
102
+ | `prompt` | `string` | **Yes** | The user prompt sent to the model. This is the task or question the model should answer. |
103
+ | `context` | `string` | No | Additional context appended to the prompt (e.g., code snippet, document text). If provided, it is wrapped in a code block and concatenated after `prompt`. |
104
+ | `rubric` | `string` | No | Natural language scoring criteria for the LLM judge. The judge model reads this rubric and scores the output 1-5. Use when you need semantic/qualitative evaluation. |
105
+ | `assertions` | `array` | No | List of deterministic and async checks applied to the model output. Each assertion is an object with a `type` field (see [Assertion Types](#assertion-types)). |
106
+ | `assertions[].type` | `string` | **Yes** | The assertion type (e.g., `"contains"`, `"json_valid"`, `"custom"`). See full list below. |
107
+ | `assertions[].value` | `string\|number` | Varies | The value to check against. Required for `contains`, `starts_with`, `equals`, `min_length`, `cost_max`, etc. |
108
+ | `assertions[].values` | `array` | Varies | Array of strings. Required for `contains_all` and `contains_any`. |
109
+ | `assertions[].pattern` | `string` | Varies | Regex pattern. Required for `regex` type. |
110
+ | `assertions[].flags` | `string` | No | Regex flags (default: `"i"`). Only used with `regex` type. |
111
+ | `assertions[].schema` | `object` | Varies | JSON Schema object. Required for `json_schema` type. Validated via [ajv](https://ajv.js.org/) (full JSON Schema spec). |
112
+ | `assertions[].reference` | `string` | Varies | Reference text for semantic comparison. Required for `semantic_similarity` type. |
113
+ | `assertions[].threshold` | `number` | No | Minimum score (1-5) to consider a semantic similarity match passing. Default: `3`. |
114
+ | `assertions[].fn` | `string` | Varies | Path to a `.mjs` file exporting the check function. Required for `custom` type. Resolved relative to the samples file directory. |
115
+ | `assertions[].weight` | `number` | No | Weight of this assertion in the composite score calculation. Default: `1`. Higher weight = more influence on the final assertion score. |
116
+ | `dimensions` | `object` | No | Key-value map for multi-dimensional LLM scoring. Each key is a dimension name (e.g., `"security"`), and the value is the rubric text the LLM judge uses to score that dimension (1-5). Scores are averaged into a single LLM score. |
117
+
118
+ **Scoring priority:** If both `assertions` and `rubric`/`dimensions` are present, the composite score is a 50/50 weighted average. If only one is present, that score is used directly. If none are present, the score is 0.
119
+
120
+ **Prompt construction:** The final prompt sent to the model is: `prompt` alone if no `context`, or `prompt + "\n\n```\n" + context + "\n```"` if `context` is provided.
121
+
122
+ ### Grading Strategy
123
+
124
+ Each sample can use up to three grading methods. They can be used alone or combined.
125
+
126
+ #### 1. Assertions (deterministic scoring)
127
+
128
+ Assertions are rule-based checks that run locally without any LLM calls (except `semantic_similarity` and `custom`). Each assertion produces a **pass/fail** result.
129
+
130
+ **How the assertion score is calculated:**
131
+
132
+ 1. Each assertion has a `weight` (default: 1)
133
+ 2. Sum the weights of all passing assertions → `passedWeight`
134
+ 3. Sum the weights of all assertions → `totalWeight`
135
+ 4. Compute ratio: `passedWeight / totalWeight` (0.0 ~ 1.0)
136
+ 5. Normalize to 1-5 scale: **`score = 1 + ratio × 4`**
137
+
138
+ Example: 3 assertions (weight 1 each), 2 pass → ratio = 2/3 → score = 1 + 2.67 = **3.67**
139
+
140
+ #### 2. Rubric (single LLM judge)
141
+
142
+ A judge model (default: `haiku`, configurable via `--judge-model`) reads the model output and scores it against the rubric text. Returns an integer score from **1** (fail) to **5** (excellent) with a brief reason.
143
+
144
+ Only one of `rubric` or `dimensions` should be used per sample. If both are present, `dimensions` takes priority.
145
+
146
+ #### 3. Dimensions (multi-dimensional LLM judge)
147
+
148
+ Each dimension is scored independently by the judge model (1-5). The dimension scores are **averaged** to produce a single LLM score.
149
+
150
+ Example: `security: 5`, `actionability: 3` → LLM score = **(5 + 3) / 2 = 4.0**
151
+
152
+ #### Composite Score
153
+
154
+ | What's present | Composite score formula |
155
+ |----------------|----------------------|
156
+ | Assertions only | `assertionScore` |
157
+ | LLM only (rubric or dimensions) | `llmScore` |
158
+ | Both | `(assertionScore + llmScore) / 2` |
159
+ | Neither | `0` |
160
+
161
+ All scores are on a **1-5 scale**. A score of 0 means no grading criteria were defined.
162
+
163
+ ### Assertion Types
164
+
165
+ **Deterministic (sync, no LLM):**
166
+
167
+ | Type | Fields | Description |
168
+ |------|--------|-------------|
169
+ | `contains` | `value`, `weight` | Output contains substring (case-insensitive) |
170
+ | `not_contains` | `value`, `weight` | Output does NOT contain substring |
171
+ | `regex` | `pattern`, `flags`, `weight` | Output matches regex |
172
+ | `min_length` | `value`, `weight` | Output length >= value |
173
+ | `max_length` | `value`, `weight` | Output length <= value |
174
+ | `json_valid` | `weight` | Output is valid JSON |
175
+ | `json_schema` | `schema`, `weight` | Output matches JSON Schema (full spec via ajv) |
176
+ | `starts_with` | `value`, `weight` | Output starts with string (case-insensitive) |
177
+ | `ends_with` | `value`, `weight` | Output ends with string (case-insensitive) |
178
+ | `equals` | `value`, `weight` | Output exactly equals value (after trim) |
179
+ | `not_equals` | `value`, `weight` | Output does not equal value (after trim) |
180
+ | `word_count_min` | `value`, `weight` | Word count >= value |
181
+ | `word_count_max` | `value`, `weight` | Word count <= value |
182
+ | `contains_all` | `values`, `weight` | Output contains ALL substrings |
183
+ | `contains_any` | `values`, `weight` | Output contains at least one substring |
184
+ | `cost_max` | `value`, `weight` | Execution cost (USD) <= value |
185
+ | `latency_max` | `value`, `weight` | Execution latency (ms) <= value |
186
+
187
+ **Async (LLM-based):**
188
+
189
+ | Type | Fields | Description |
190
+ |------|--------|-------------|
191
+ | `semantic_similarity` | `reference`, `threshold`, `weight` | LLM judges similarity to reference text (threshold default: 3) |
192
+ | `custom` | `fn`, `weight` | Load external JS function (see below) |
193
+
194
+ ### Custom Assertions
195
+
196
+ Create a `.mjs` file that exports a function:
197
+
198
+ ```js
199
+ // my-assertion.mjs
200
+ export default function(output, { sample, assertion }) {
201
+ const hasKeyword = output.includes('SQL');
202
+ return { pass: hasKeyword, message: 'Checked for SQL keyword' };
203
+ }
204
+ ```
205
+
206
+ Reference it in your sample: `{ "type": "custom", "fn": "my-assertion.mjs" }`. The `fn` path is resolved relative to the samples file directory.
207
+
208
+ ## Report Example
209
+
210
+ After running `omk bench run --variants v1,v2`, the tool outputs a JSON report (also saved to `~/.oh-my-knowledge/reports/`):
211
+
212
+ ```json
213
+ {
214
+ "id": "2026-03-24T15-30-45-v1-v2",
215
+ "meta": {
216
+ "variants": ["v1", "v2"],
217
+ "model": "sonnet",
218
+ "judgeModel": "haiku",
219
+ "executor": "claude",
220
+ "sampleCount": 3,
221
+ "taskCount": 6,
222
+ "totalCostUSD": 0.0234,
223
+ "timestamp": "2026-03-24T15:30:45.000Z",
224
+ "cliVersion": "0.3.0",
225
+ "nodeVersion": "v22.0.0",
226
+ "skillHashes": { "v1": "a1b2c3d4e5f6", "v2": "f6e5d4c3b2a1" }
227
+ },
228
+ "summary": {
229
+ "v1": {
230
+ "totalSamples": 3,
231
+ "successCount": 3,
232
+ "errorCount": 0,
233
+ "avgCompositeScore": 3.67,
234
+ "avgAssertionScore": 3.0,
235
+ "avgLlmScore": 4.33,
236
+ "avgDurationMs": 2500,
237
+ "avgTotalTokens": 1850,
238
+ "totalCostUSD": 0.0112
239
+ },
240
+ "v2": {
241
+ "totalSamples": 3,
242
+ "successCount": 3,
243
+ "errorCount": 0,
244
+ "avgCompositeScore": 4.5,
245
+ "avgAssertionScore": 5.0,
246
+ "avgLlmScore": 4.0,
247
+ "avgDurationMs": 2800,
248
+ "avgTotalTokens": 2100,
249
+ "totalCostUSD": 0.0122
250
+ }
251
+ },
252
+ "results": [
253
+ {
254
+ "sample_id": "s001",
255
+ "variants": {
256
+ "v1": {
257
+ "ok": true,
258
+ "compositeScore": 3.5,
259
+ "assertions": {
260
+ "passed": 1,
261
+ "total": 2,
262
+ "score": 3.0,
263
+ "details": [
264
+ { "type": "contains", "value": "SQL injection", "weight": 1, "passed": true },
265
+ { "type": "contains", "value": "parameterized", "weight": 1, "passed": false }
266
+ ]
267
+ },
268
+ "llmScore": 4,
269
+ "llmReason": "Identified the vulnerability but did not provide a complete fix",
270
+ "durationMs": 2300,
271
+ "inputTokens": 850,
272
+ "outputTokens": 1200,
273
+ "totalTokens": 2050,
274
+ "costUSD": 0.0038,
275
+ "outputPreview": "This code has a SQL injection vulnerability..."
276
+ },
277
+ "v2": {
278
+ "ok": true,
279
+ "compositeScore": 4.5,
280
+ "assertions": {
281
+ "passed": 2,
282
+ "total": 2,
283
+ "score": 5.0,
284
+ "details": [
285
+ { "type": "contains", "value": "SQL injection", "weight": 1, "passed": true },
286
+ { "type": "contains", "value": "parameterized", "weight": 1, "passed": true }
287
+ ]
288
+ },
289
+ "llmScore": 4,
290
+ "llmReason": "Thorough analysis with actionable fix code",
291
+ "durationMs": 2600,
292
+ "inputTokens": 900,
293
+ "outputTokens": 1400,
294
+ "totalTokens": 2300,
295
+ "costUSD": 0.0042,
296
+ "outputPreview": "## Security Issue: SQL Injection\n\nThe code is vulnerable..."
297
+ }
298
+ }
299
+ }
300
+ ],
301
+ "analysis": {
302
+ "insights": [
303
+ {
304
+ "type": "uniform_scores",
305
+ "severity": "info",
306
+ "message": "1/3 samples show score difference < 0.5 between variants"
307
+ }
308
+ ],
309
+ "suggestions": []
310
+ }
311
+ }
312
+ ```
313
+
314
+ **Reading the report:**
315
+
316
+ - **`summary`** gives a quick comparison — in this example, v2 scores higher (4.5 vs 3.67) because it passes more assertions
317
+ - **`results`** shows per-sample detail — you can see exactly which assertions passed/failed and why
318
+ - **`analysis`** flags patterns — here it notes one sample has similar scores across variants
319
+ - View the HTML version at `http://127.0.0.1:7799/run/{id}` after running `omk bench report`
320
+
321
+ ## CLI Reference
322
+
323
+ ### `omk bench run`
324
+
325
+ ```bash
326
+ omk bench run [options]
327
+
328
+ Options:
329
+ --samples <path> Sample file (default: eval-samples.json, auto-detects .yaml/.yml)
330
+ --skill-dir <path> Skill directory (default: skills)
331
+ --variants <v1,v2> Variant names (default: v1,v2)
332
+ --model <name> Model under test (default: sonnet)
333
+ --judge-model <name> Judge model (default: haiku)
334
+ --output-dir <path> Output directory (default: ~/.oh-my-knowledge/reports/)
335
+ --no-judge Skip LLM judging
336
+ --dry-run Preview only
337
+ --blind Blind A/B mode: hide variant names in report
338
+ --concurrency <n> Number of parallel tasks (default: 1)
339
+ --repeat <n> Run evaluation N times for variance analysis (default: 1)
340
+ --executor <name> Executor (default: claude)
341
+ ```
342
+
343
+ ### `omk bench ci`
344
+
345
+ Run evaluation in CI and exit with pass/fail code.
346
+
347
+ ```bash
348
+ omk bench ci [options]
349
+
350
+ Options:
351
+ (same as "bench run", plus:)
352
+ --threshold <number> Minimum composite score to pass (default: 3.5)
353
+ ```
354
+
355
+ Exit code 0 = all variants pass, 1 = at least one variant below threshold.
356
+
357
+ ### `omk bench report`
358
+
359
+ ```bash
360
+ omk bench report [options]
361
+
362
+ Options:
363
+ --port <number> Server port (default: 7799)
364
+ --reports-dir <path> Reports directory (default: ~/.oh-my-knowledge/reports/)
365
+ ```
366
+
367
+ ### `omk bench init`
368
+
369
+ ```bash
370
+ omk bench init [dir] # Scaffold a new eval project
371
+ ```
372
+
373
+ ## Features
374
+
375
+ ### Blind A/B Testing
376
+
377
+ Use `--blind` to hide variant names in reports. Variants are randomly labeled as "Variant A", "Variant B", etc. A reveal button in the HTML report shows the mapping.
378
+
379
+ ### Parallel Execution
380
+
381
+ Use `--concurrency N` to run N tasks in parallel. Tasks maintain interleaved scheduling order to reduce time bias.
382
+
383
+ ### Multi-run Variance Analysis
384
+
385
+ Use `--repeat N` to run the evaluation N times. The report includes:
386
+ - Per-variant mean, standard deviation, 95% confidence interval
387
+ - Pairwise Welch's t-test between variants (significance at p < 0.05)
388
+
389
+ ### Auto-analysis
390
+
391
+ After each evaluation, the toolkit automatically detects:
392
+ - **Low-discrimination assertions**: assertions with identical results across all variants
393
+ - **Uniform scores**: samples where variants score within 0.5 of each other
394
+ - **All-pass / all-fail**: assertions that may be too loose or too strict
395
+ - **High-cost samples**: samples with disproportionately high cost
396
+
397
+ Insights and suggestions are shown in the HTML report.
398
+
399
+ ### Human Feedback
400
+
401
+ The HTML report includes star rating (1-5) and comment forms for each sample-variant pair. Feedback is persisted to the report JSON via `POST /api/run/:id/feedback`.
402
+
403
+ ### Traceability
404
+
405
+ Reports include `cliVersion`, `nodeVersion`, and `skillHashes` (SHA-256 of each skill file) in metadata for reproducibility.
406
+
407
+ ## Executors
408
+
409
+ Use `--executor` to select which model provider to use.
410
+
411
+ | Executor | CLI Tool | Default Model | Auth |
412
+ |----------|----------|---------------|------|
413
+ | `claude` | `claude -p` | `sonnet` | Claude Max plan or API key |
414
+ | `openai` | `openai api chat.completions.create` | `gpt-4o` | `OPENAI_API_KEY` env var |
415
+ | `gemini` | `gemini` (stdin pipe) | Default Gemini model | Google account or `GOOGLE_API_KEY` |
416
+
417
+ ```bash
418
+ # Use OpenAI
419
+ omk bench run --executor openai --model gpt-4o --variants v1,v2
420
+
421
+ # Use Gemini
422
+ omk bench run --executor gemini --model gemini-2.5-pro --variants v1,v2
423
+
424
+ # Compare the same skill across providers (run separately, compare reports)
425
+ omk bench run --executor claude --model sonnet --variants v1,v2
426
+ omk bench run --executor openai --model gpt-4o --variants v1,v2
427
+ ```
428
+
429
+ **Prerequisites:**
430
+ - **claude**: Install [Claude Code](https://claude.ai/code) and authenticate
431
+ - **openai**: `pip install openai` and set `OPENAI_API_KEY`
432
+ - **gemini**: `npm i -g @google/gemini-cli` and authenticate with Google
433
+
434
+ ## Environment Variables
435
+
436
+ | Variable | Description |
437
+ |----------|-------------|
438
+ | `CCV_PROXY_URL` | Route requests through cc-viewer proxy for real-time visualization |
439
+ | `OMK_BENCH_PORT` | Report server port (default: 7799) |
440
+
441
+ ## Requirements
442
+
443
+ - Node.js >= 20
444
+ - `claude` CLI installed and authenticated (Max plan works, no API key needed)
445
+
446
+ ## License
447
+
448
+ MIT
package/package.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "oh-my-knowledge",
3
+ "version": "0.0.1",
4
+ "description": "Knowledge artifact evaluation toolkit — benchmark your skills with objective data",
5
+ "keywords": [
6
+ "knowledge-engineering",
7
+ "skill-evaluation",
8
+ "benchmark",
9
+ "llm",
10
+ "ai",
11
+ "claude"
12
+ ],
13
+ "author": "lizhiyao",
14
+ "license": "MIT"
15
+ }