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.
- package/CHANGELOG.md +160 -4
- package/README.md +89 -31
- package/SKILL.md +13 -1
- package/dist/check/check.js +14 -2
- package/dist/cli.js +95 -9
- package/dist/clients/introspect.js +10 -2
- package/dist/clients/rawprobe.d.ts +21 -5
- package/dist/clients/rawprobe.js +101 -43
- package/dist/clients/stateless.js +17 -14
- package/dist/core/engine.js +20 -2
- package/dist/core/grade.js +7 -1
- package/dist/core/types.d.ts +22 -3
- package/dist/reporters/markdown.js +16 -4
- package/dist/reporters/pretty.js +38 -25
- package/dist/rules/injection.d.ts +1 -2
- package/dist/rules/injection.js +7 -11
- package/dist/rules/mcp/index.d.ts +14 -0
- package/dist/rules/mcp/index.js +89 -25
- package/dist/rules/skill/index.js +5 -3
- package/dist/skills/parse.js +39 -3
- package/dist/targets/clientConfigs.d.ts +7 -0
- package/dist/targets/clientConfigs.js +4 -1
- package/dist/targets/repoScan.js +0 -0
- package/dist/testing/anthropicRunner.js +2 -0
- package/dist/testing/harness.d.ts +22 -0
- package/dist/testing/harness.js +98 -13
- package/dist/testing/openaiRunner.js +15 -1
- package/dist/testing/stats.d.ts +30 -0
- package/dist/testing/stats.js +89 -0
- package/dist/util/dotenv.d.ts +9 -1
- package/dist/util/dotenv.js +11 -1
- package/dist/util/safeText.d.ts +8 -0
- package/dist/util/safeText.js +48 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/weigh/weigh.js +3 -1
- package/dist/weigh/window.d.ts +26 -0
- package/dist/weigh/window.js +44 -0
- package/docs/METHODOLOGY.md +70 -0
- package/docs/RULES.md +31 -25
- package/examples/contoso-crm-import/SKILL.md +21 -0
- package/examples/csv-cleanup/SKILL.md +17 -0
- package/examples/scenario.crm.yaml +22 -0
- package/examples/scenario.example.yaml +17 -0
- package/package.json +4 -2
package/docs/METHODOLOGY.md
CHANGED
|
@@ -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
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
|
25
|
-
| E102 | warn | source uses **Sampling** (`sampling/createMessage`), deprecated (SEP-2577) |
|
|
26
|
-
| E103 | warn | source uses **Roots** (`roots/list`), deprecated (SEP-2577) |
|
|
27
|
-
| E104 | warn | server declares the **logging** capability or source uses MCP Logging, deprecated (SEP-2577); `logging/setLevel` is removed |
|
|
28
|
-
| E105 | warn | the server
|
|
29
|
-
| E106 | warn | `server/discover` is not implemented (MUST in
|
|
30
|
-
| E107 | info | results omit the required `resultType` field (`"complete"` \| `"input_required"`) |
|
|
31
|
-
| E108 | info | source relies on removed SSE resumability (`Last-Event-ID`) |
|
|
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) |
|
|
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) |
|
|
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 |
|
|
38
|
-
| E115 | info | source uses `resources/subscribe`, replaced by `subscriptions/listen` |
|
|
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 |
|
|
41
|
-
| E118 | warn | `tools/list` result omits the
|
|
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.
|
|
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.
|
|
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",
|