efaimo 0.1.0 → 0.1.2

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 (45) hide show
  1. package/CHANGELOG.md +160 -4
  2. package/README.md +89 -31
  3. package/SKILL.md +13 -1
  4. package/dist/check/check.js +14 -2
  5. package/dist/cli.js +95 -9
  6. package/dist/clients/introspect.js +10 -2
  7. package/dist/clients/rawprobe.d.ts +21 -5
  8. package/dist/clients/rawprobe.js +101 -43
  9. package/dist/clients/stateless.js +17 -14
  10. package/dist/core/engine.js +20 -2
  11. package/dist/core/grade.js +7 -1
  12. package/dist/core/types.d.ts +22 -3
  13. package/dist/reporters/markdown.js +16 -4
  14. package/dist/reporters/pretty.js +38 -25
  15. package/dist/rules/injection.d.ts +1 -2
  16. package/dist/rules/injection.js +7 -11
  17. package/dist/rules/mcp/index.d.ts +14 -0
  18. package/dist/rules/mcp/index.js +89 -25
  19. package/dist/rules/skill/index.js +5 -3
  20. package/dist/skills/parse.js +39 -3
  21. package/dist/targets/clientConfigs.d.ts +7 -0
  22. package/dist/targets/clientConfigs.js +4 -1
  23. package/dist/targets/repoScan.js +0 -0
  24. package/dist/testing/anthropicRunner.js +2 -0
  25. package/dist/testing/harness.d.ts +22 -0
  26. package/dist/testing/harness.js +98 -13
  27. package/dist/testing/openaiRunner.js +15 -1
  28. package/dist/testing/stats.d.ts +30 -0
  29. package/dist/testing/stats.js +89 -0
  30. package/dist/util/dotenv.d.ts +9 -1
  31. package/dist/util/dotenv.js +11 -1
  32. package/dist/util/safeText.d.ts +8 -0
  33. package/dist/util/safeText.js +48 -0
  34. package/dist/version.d.ts +1 -1
  35. package/dist/version.js +1 -1
  36. package/dist/weigh/weigh.js +3 -1
  37. package/dist/weigh/window.d.ts +26 -0
  38. package/dist/weigh/window.js +44 -0
  39. package/docs/METHODOLOGY.md +70 -0
  40. package/docs/RULES.md +31 -25
  41. package/examples/contoso-crm-import/SKILL.md +21 -0
  42. package/examples/csv-cleanup/SKILL.md +17 -0
  43. package/examples/scenario.crm.yaml +22 -0
  44. package/examples/scenario.example.yaml +17 -0
  45. package/package.json +4 -2
@@ -48,6 +48,29 @@ wrapped total. For the other two serializations no wrapper exists, and summing
48
48
  per-tool counts may differ from the total by a few tokens, because a tokenizer
49
49
  can merge characters across element boundaries when tools are concatenated.
50
50
 
51
+ ## The context window we compare against
52
+
53
+ Alongside the absolute token count, efaimo prints the share of a context window
54
+ it represents. There is no correct denominator for that: the window belongs to
55
+ whichever model the host is running, not to the server being measured. Current
56
+ frontier Claude models (Fable 5, Opus 5, Opus 4.8/4.7/4.6, Sonnet 5, Sonnet
57
+ 4.6) are **1M**, so that is the default. Haiku 4.5 is 200k, as are many
58
+ non-Claude and local models, where identical tool definitions cost five times
59
+ the share.
60
+
61
+ Because the denominator is an assumption rather than a measurement, efaimo
62
+ always names it in the output ("~4.3% of a 1M window") and lets you set your
63
+ own with `--window` (since 0.1.1; 0.1.0 hardcoded 200k and printed `of a 200k window`):
64
+
65
+ ```bash
66
+ npx efaimo weigh "npx -y my-server" --window 200000
67
+ ```
68
+
69
+ **No rule grades on this share.** The cost thresholds (E127, E128) are absolute
70
+ token counts, so changing `--window` changes what you read and never changes a
71
+ grade. The absolute number is the one efaimo stands behind; the share is a
72
+ readability aid on top of it.
73
+
51
74
  ## Skills
52
75
 
53
76
  For Agent Skills efaimo reports the three progressive-disclosure levels defined by
@@ -81,3 +104,50 @@ prompt-cache hits and reproducibility.
81
104
  efaimo estimates the **cost of tool/skill definitions sitting in context**. It does
82
105
  not measure per-call argument/result tokens, host framing, or model-side reasoning.
83
106
  For end-to-end token accounting, instrument your agent run directly.
107
+
108
+ ## `efaimo test`: what the verdict means
109
+
110
+ The verdict is a significance test, not a threshold on the size of the gap.
111
+
112
+ Each arm runs N trials of the same task, one with the skill in the system
113
+ prompt and one without, and an LLM judge returns PASS or FAIL per trial. The
114
+ two arms form a 2x2 table, and the report carries:
115
+
116
+ - a **two-sided Fisher exact p**. Fisher rather than chi-squared because the
117
+ counts are single digits, where the chi-squared approximation is not
118
+ trustworthy;
119
+ - a **95% Newcombe interval on the delta**, built from each arm's Wilson
120
+ interval. Wilson rather than the normal approximation because runs land on
121
+ the boundaries (8/8, 0/8), where the normal interval has zero width and
122
+ would claim certainty from eight trials.
123
+
124
+ `helps` and `hurts` require **p < 0.05**. Anything else is `no measurable
125
+ effect`, however large the gap looks, and the report says so with the p
126
+ attached. The default is 20 trials per arm: below roughly that, a partial
127
+ result cannot reach significance at all.
128
+
129
+ Until 2026-08-02 the rule was `>= +15 points helps`, with a default of 5
130
+ trials. At that size 5/5 against 4/5 is +20 points and p = 1.0000.
131
+
132
+ Two things the judge does, which matter for reading a result:
133
+
134
+ - it runs at **temperature 0**, so the same answer grades the same way twice.
135
+ The subject arm stays at temperature 1, because normal behaviour is the thing
136
+ being measured;
137
+ - a reply that is neither PASS nor FAIL is **excluded and counted separately**,
138
+ not scored as a failure. A refusal or an API error is not evidence about the
139
+ skill.
140
+
141
+ ### What this design still cannot tell you
142
+
143
+ Named here rather than left for a reader to discover:
144
+
145
+ - **The judge is the same model as the subject.** A separate judge model would
146
+ be stronger.
147
+ - **The control arm receives no system prompt at all**, so "the skill's
148
+ content" is confounded with "having any system prompt". A length-matched
149
+ placebo is the fix and is not implemented.
150
+ - **Two scenarios is a demonstration, not a benchmark.** The committed runs
151
+ show the method works, not that skills in general do or do not help.
152
+
153
+ Treat a single scenario as evidence about that scenario.
package/docs/RULES.md CHANGED
@@ -7,43 +7,49 @@ Grade starts at 100; each error costs 15, each warning 5, each info 1.
7
7
  `A >= 90, B >= 80, C >= 70, D >= 60, else F`.
8
8
 
9
9
  MCP checks split into **2026-07-28 readiness** (E101-E118) and **quality**
10
- (E121-E130); skill checks are S1xx. **Only quality and skill findings are
10
+ (E121-E128 and E130, with no E129); skill checks are S1xx. **Only quality and skill findings are
11
11
  graded.** Readiness findings are reported as a separate, ungraded **migration
12
- diff**: the stateless revision they target is a locked Release Candidate
13
- (2026-05-21) that does not finalize until 2026-07-28, and not yet migrating to
14
- an unreleased spec is a to-do, not a defect. Readiness severities still order
15
- the diff by urgency, but the diff never affects the grade, the badge, or the
16
- exit code (including `--strict`). Once the spec ratifies, expect readiness to
17
- start counting in a minor release. Source-pattern rules are heuristics and say
18
- so in their detail line.
12
+ diff**: the two answer different questions. The grade is what a model
13
+ experiences from this tool surface; readiness is whether a 2026-07-28 client can
14
+ talk to this server. Combining them scored the official reference server a C(79)
15
+ for a tool surface that is an A(95), which is why they are two numbers printed
16
+ next to each other rather than one. Readiness severities still order the diff by
17
+ urgency, but the diff never affects the grade or the badge, and never affects
18
+ the exit code unless you ask for it with `--strict-readiness`. Source-pattern
19
+ rules are heuristics and say so in their detail line.
19
20
 
20
21
  ## MCP readiness (E101-E118)
21
22
 
22
23
  | id | sev | what it catches | source of truth |
23
24
  |---|---|---|---|
24
- | E101 | warn | depends on a pre-`2026-07-28` SDK line (TS `@modelcontextprotocol/sdk` 1.x, Python `mcp` 1.x) instead of the 2.x stateless line | changelog / SDK releases |
25
- | E102 | warn | source uses **Sampling** (`sampling/createMessage`), deprecated (SEP-2577) | draft changelog |
26
- | E103 | warn | source uses **Roots** (`roots/list`), deprecated (SEP-2577) | draft changelog |
27
- | E104 | warn | server declares the **logging** capability or source uses MCP Logging, deprecated (SEP-2577); `logging/setLevel` is removed | draft changelog |
28
- | E105 | warn | the server does not answer a bare stateless `tools/list` (timeout, crash, or a not-initialized error), so it requires the removed `initialize` handshake. A server that answers (even with an error) is judged by E106/E107/E118 instead | SEP-2567 / stateless core |
29
- | E106 | warn | `server/discover` is not implemented (MUST in the RC, SEP-2575) | draft changelog |
30
- | E107 | info | results omit the required `resultType` field (`"complete"` \| `"input_required"`) | draft changelog |
31
- | E108 | info | source relies on removed SSE resumability (`Last-Event-ID`) | draft changelog |
25
+ | E101 | warn | depends on a pre-`2026-07-28` SDK line (TS `@modelcontextprotocol/sdk` 1.x, Python `mcp` 1.x) instead of the stateless successors (TS `@modelcontextprotocol/server` 2.x, Python `mcp` 2.x) | changelog / SDK releases |
26
+ | E102 | warn | source uses **Sampling** (`sampling/createMessage`), deprecated (SEP-2577) | 2026-07-28 changelog |
27
+ | E103 | warn | source uses **Roots** (`roots/list`), deprecated (SEP-2577) | 2026-07-28 changelog |
28
+ | E104 | warn | server declares the **logging** capability or source uses MCP Logging, deprecated (SEP-2577); `logging/setLevel` is removed | 2026-07-28 changelog |
29
+ | E105 | warn | the server rejects a bare stateless `tools/list` with a not-initialized error, or exits on it, so it still requires the removed `initialize` handshake. A timeout is reported as unverified statelessness rather than as this warn, and a server that answers (even with another error) is judged by E106/E107/E118 instead | SEP-2567 / stateless core |
30
+ | E106 | warn | `server/discover` is not implemented (MUST in 2026-07-28, SEP-2575) | 2026-07-28 changelog |
31
+ | E107 | info | results omit the required `resultType` field (`"complete"` \| `"input_required"`) | 2026-07-28 changelog |
32
+ | E108 | info | source relies on removed SSE resumability (`Last-Event-ID`) | 2026-07-28 changelog |
32
33
  | E109 | info | auth advertises deprecated DCR (RFC 7591) with no detected CIMD support | PR #2858 / CIMD |
33
- | E110 | warn | source uses legacy elicitation (`elicitation/create`); replaced by MRTR `input_required` results (SEP-2322) | draft changelog |
34
+ | E110 | warn | source uses legacy elicitation (`elicitation/create`); replaced by MRTR `input_required` results (SEP-2322) | 2026-07-28 changelog |
34
35
  | E111 | info | in-process session-state patterns; statelessness expects server-minted handles in tool args | stateless core |
35
- | E112 | warn | `tools/list` order is nondeterministic across connections (hurts prompt-cache hits) | draft changelog |
36
+ | E112 | warn | `tools/list` order is nondeterministic across connections (hurts prompt-cache hits) | 2026-07-28 changelog |
36
37
  | E113 | info | no Server Card found at `/.well-known/mcp` (discovery metadata; heuristic path) | roadmap / Server Card WG |
37
- | E114 | info | source uses the removed `ping` utility | draft changelog |
38
- | E115 | info | source uses `resources/subscribe`, replaced by `subscriptions/listen` | draft changelog |
38
+ | E114 | info | source uses the removed `ping` utility | 2026-07-28 changelog |
39
+ | E115 | info | source uses `resources/subscribe`, replaced by `subscriptions/listen` | 2026-07-28 changelog |
39
40
  | E116 | warn | server prints non-JSON noise on stdout (breaks stdio framing) | transport basics |
40
- | E117 | warn | only the deprecated HTTP+SSE transport worked; Streamable HTTP failed | draft changelog |
41
- | E118 | warn | `tools/list` result omits the now-required `ttlMs`/`cacheScope` cache fields (SEP-2549, CacheableResult; also required on prompts/list, resources/list, resources/read, resources/templates/list, server/discover) | draft changelog |
41
+ | E117 | warn | only the deprecated HTTP+SSE transport worked; Streamable HTTP failed | 2026-07-28 changelog |
42
+ | E118 | warn | a `tools/list` or `server/discover` result omits the required `ttlMs`/`cacheScope` cache fields (SEP-2549, CacheableResult; also required on prompts/list, resources/list, resources/read, resources/templates/list). Both surfaces efaimo calls are measured and the message names the ones that failed. `server/discover` counts only since the published spec made `DiscoverResult` extend `CacheableResult`; under the locked RC it did not | 2026-07-28 changelog |
42
43
 
43
- ## MCP quality (E121-E130)
44
+ ## MCP quality (E121-E128, E130)
45
+
46
+ There is no E129. The range used to be written "E121-E130", which implies a
47
+ contiguous block and invites a lookup that finds nothing; ids are stable and
48
+ never reused, so a gap is permanent and is better stated than smoothed over.
44
49
 
45
50
  | id | sev | what it catches |
46
51
  |---|---|---|
52
+ | E000 | warn | a rule threw and was skipped, so this report is incomplete. **Reported but never scored**: a broken rule is our defect, not the target's, and must not cost it points. It exists because `runRules` used to swallow the exception silently, which made a crashing rule indistinguishable from a passing one and silently RAISED the grade |
47
53
  | E121 | error/warn | tool description missing, a placeholder, or under ~20 chars |
48
54
  | E122 | warn | description misses 3+ of 4 quality axes (length, when-to-use, params documented, mentions result), mirrors Glama's tool-definition-quality dimensions, computed locally |
49
55
  | E123 | warn/info | no tools declare annotations; a destructive-looking tool lacks `destructiveHint` |
@@ -52,7 +58,7 @@ so in their detail line.
52
58
  | E126 | warn/info | tool names mix conventions or exceed 64 chars |
53
59
  | E127 | warn | a single tool definition costs 800+ tokens (needs `weigh` data) |
54
60
  | E128 | warn | total tool-definition tax over ~10k / ~25k tokens, estimated (needs `weigh` data); hard CI failure is left to the `--max-tokens`/`--diff` gate |
55
- | E130 | info | instruction-injection patterns in a tool description or server instructions (shallow heuristic, never a security verdict; use a dedicated scanner for depth) |
61
+ | E130 | info | instruction-injection patterns in a tool description or server instructions (shallow heuristic, never a security verdict; use a dedicated scanner for depth). **Reported but never scored** |
56
62
 
57
63
  ## Skills (S101-S106)
58
64
 
@@ -62,7 +68,7 @@ so in their detail line.
62
68
  | S102 | warn/info | description too thin or never says when to use the skill | agentskills.io |
63
69
  | S103 | error/warn | duplicate skill names, or two skills with heavily overlapping descriptions (trigger collision) | internal |
64
70
  | S104 | warn/info | metadata over ~100 tokens, body over ~5k tokens or 500 lines, or long body with no progressive disclosure | agentskills.io |
65
- | S105 | info | instruction-injection patterns in the skill body or description (shallow heuristic, never a security verdict) | internal |
71
+ | S105 | info | instruction-injection patterns in the skill body or description (shallow heuristic, never a security verdict). **Reported but never scored** | internal |
66
72
  | S106 | error/warn/info | referenced file missing, escaping the skill dir (`..`), or nested more than one level deep | agentskills.io |
67
73
 
68
74
  ## Notes on heuristics
@@ -0,0 +1,21 @@
1
+ ---
2
+ name: contoso-crm-import
3
+ description: Format a contact list as a Contoso CRM bulk-import file. Use when the user asks to prepare, convert, or export contacts for Contoso CRM import.
4
+ license: Apache-2.0
5
+ ---
6
+
7
+ # Contoso CRM bulk-import format
8
+
9
+ Convert a contact list into the exact file Contoso CRM accepts on import. These
10
+ rules are an internal convention and are not guessable; follow them exactly.
11
+
12
+ ## Output rules
13
+ 1. Semicolon-delimited. No header row.
14
+ 2. Four columns, in this order: member_id;display_name;email;source
15
+ 3. member_id: the first three letters of the last name, uppercased, then a hyphen,
16
+ then a row number zero-padded to four digits starting at 0001 (0001, 0002, ...).
17
+ 4. display_name: the full name exactly as given.
18
+ 5. email: lowercased.
19
+ 6. source: always the literal value import-2026.
20
+
21
+ Output only the rows.
@@ -0,0 +1,17 @@
1
+ ---
2
+ name: csv-cleanup
3
+ description: Clean and validate CSV data. Use when the user asks to deduplicate, trim, or sanity-check a CSV before import.
4
+ license: Apache-2.0
5
+ ---
6
+
7
+ # CSV cleanup
8
+
9
+ Use this when the user wants to tidy CSV data.
10
+
11
+ ## Steps
12
+ 1. Detect the delimiter (comma, tab, or semicolon) from the header row.
13
+ 2. Trim leading and trailing whitespace from every cell.
14
+ 3. Drop rows that are exact duplicates of an earlier row.
15
+ 4. Report the final row and column counts, and how many duplicates were removed.
16
+
17
+ Always show the cleaned result and a one-line summary of what changed.
@@ -0,0 +1,22 @@
1
+ # efaimo test scenario: a skill that encodes a convention the model cannot guess.
2
+ # Contrast with scenario.example.yaml, where a capable model needs no skill.
3
+ # Real run (spends tokens): npx efaimo test examples/scenario.crm.yaml --live
4
+ name: contoso-crm-import helps on an unknowable format
5
+ skill: ./contoso-crm-import
6
+ model: claude-sonnet-5
7
+ trials: 8
8
+ task: |
9
+ Prepare these contacts for Contoso CRM import:
10
+
11
+ Ada Lovelace, ADA@Example.com
12
+ Grace Hopper, Grace.Hopper@NAVY.MIL
13
+ Alan Turing, a.turing@Bletchley.uk
14
+ judge: |
15
+ PASS if the answer contains these three rows (ignore any surrounding prose or code
16
+ fences), each semicolon-delimited as member_id;display_name;email;source:
17
+ LOV-0001;Ada Lovelace;ada@example.com;import-2026
18
+ HOP-0002;Grace Hopper;grace.hopper@navy.mil;import-2026
19
+ TUR-0003;Alan Turing;a.turing@bletchley.uk;import-2026
20
+ The member_id (first three letters of the last name uppercased, a hyphen, then a
21
+ four-digit zero-padded row number), the lowercased email, and the literal source
22
+ value import-2026 must all be exact. Any other format is FAIL.
@@ -0,0 +1,17 @@
1
+ # efaimo test scenario: does the csv-cleanup skill actually help?
2
+ # Dry run (no API calls): npx efaimo test examples/scenario.example.yaml
3
+ # Real run (spends tokens): npx efaimo test examples/scenario.example.yaml --live
4
+ name: csv-cleanup helps on a messy CSV
5
+ skill: ./csv-cleanup
6
+ model: claude-sonnet-5
7
+ trials: 8
8
+ task: |
9
+ Clean this CSV and summarize what you changed:
10
+
11
+ name , email ,city
12
+ Ada ,ada@example.com, London
13
+ Ada ,ada@example.com, London
14
+ Grace,grace@example.com ,New York
15
+ judge: |
16
+ PASS only if the answer (a) removes the duplicate Ada row, (b) trims the stray
17
+ whitespace around cells, and (c) states the final row count. Otherwise FAIL.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "efaimo",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Audit what your agent loads: quality and context cost for MCP servers and Agent Skills",
5
5
  "keywords": [
6
6
  "mcp",
@@ -46,6 +46,7 @@
46
46
  },
47
47
  "files": [
48
48
  "dist",
49
+ "examples",
49
50
  "SKILL.md",
50
51
  "NOTICE",
51
52
  "CHANGELOG.md",
@@ -57,11 +58,12 @@
57
58
  "build": "tsc -p tsconfig.build.json",
58
59
  "typecheck": "tsc --noEmit",
59
60
  "test": "vitest run",
61
+ "spec:drift": "node scripts/spec-drift.mjs",
60
62
  "test:watch": "vitest",
61
63
  "prepublishOnly": "npm run typecheck && npm run build && npm run test"
62
64
  },
63
65
  "dependencies": {
64
- "@modelcontextprotocol/sdk": "^1.29.0",
66
+ "@modelcontextprotocol/sdk": "^1.30.0",
65
67
  "commander": "^15.0.0",
66
68
  "cross-spawn": "^7.0.6",
67
69
  "gpt-tokenizer": "^3.4.0",