efaimo 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/CHANGELOG.md +128 -5
  2. package/README.md +69 -7
  3. package/SKILL.md +16 -9
  4. package/dist/check/check.js +8 -0
  5. package/dist/cli.js +179 -27
  6. package/dist/clients/introspect.js +10 -2
  7. package/dist/core/engine.d.ts +2 -1
  8. package/dist/core/engine.js +4 -0
  9. package/dist/core/types.d.ts +121 -0
  10. package/dist/find/bm25.d.ts +77 -0
  11. package/dist/find/bm25.js +107 -0
  12. package/dist/find/find.d.ts +120 -0
  13. package/dist/find/find.js +403 -0
  14. package/dist/find/tokenize.d.ts +64 -0
  15. package/dist/find/tokenize.js +116 -0
  16. package/dist/index.d.ts +6 -3
  17. package/dist/index.js +13 -3
  18. package/dist/reporters/json.d.ts +19 -1
  19. package/dist/reporters/json.js +13 -2
  20. package/dist/reporters/markdown.d.ts +2 -1
  21. package/dist/reporters/markdown.js +109 -11
  22. package/dist/reporters/pretty.d.ts +12 -1
  23. package/dist/reporters/pretty.js +177 -10
  24. package/dist/rules/find/index.d.ts +4 -0
  25. package/dist/rules/find/index.js +262 -0
  26. package/dist/rules/injection.d.ts +1 -2
  27. package/dist/rules/injection.js +0 -11
  28. package/dist/rules/mcp/index.js +21 -1
  29. package/dist/rules/skill/index.js +5 -3
  30. package/dist/rules/version.d.ts +26 -0
  31. package/dist/rules/version.js +26 -0
  32. package/dist/targets/repoScan.js +0 -0
  33. package/dist/testing/anthropicRunner.d.ts +30 -0
  34. package/dist/testing/anthropicRunner.js +43 -8
  35. package/dist/testing/harness.d.ts +33 -1
  36. package/dist/testing/harness.js +66 -7
  37. package/dist/testing/openaiRunner.js +15 -8
  38. package/dist/testing/stats.d.ts +16 -2
  39. package/dist/testing/stats.js +26 -2
  40. package/dist/version.d.ts +1 -1
  41. package/dist/version.js +1 -1
  42. package/dist/weigh/anthropicExact.d.ts +12 -1
  43. package/dist/weigh/anthropicExact.js +9 -1
  44. package/dist/weigh/weigh.js +10 -3
  45. package/docs/INTEGRATIONS.md +15 -1
  46. package/docs/METHODOLOGY.md +111 -7
  47. package/docs/RULES.md +42 -5
  48. package/examples/scenario.crm.yaml +8 -0
  49. package/examples/scenario.example.yaml +8 -0
  50. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -4,6 +4,126 @@ All notable changes to efaimo are documented here. Format follows
4
4
  [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this project uses
5
5
  [Semantic Versioning](https://semver.org/).
6
6
 
7
+ ## [0.2.0] - 2026-09-02
8
+
9
+ ### Added
10
+
11
+ - **`efaimo find`**: does anything actually surface these tools? A host can
12
+ mark tools `defer_loading: true`, which keeps their definitions out of the
13
+ context window until a search returns them, and Anthropic recommends that
14
+ once definitions pass ~10k tokens. Under deferral a tool nothing surfaces
15
+ costs no context and provides no capability. Two numbers, offline and
16
+ deterministic, no API key:
17
+ - `distinct`, the headline: how many tools own a term no other tool in the
18
+ catalog has. A tool with none cannot be matched by any query that does not
19
+ also match a competitor, which follows from the index rather than from a
20
+ model of how anyone searches. Measured on `@playwright/mcp` 0.0.78: 22 of
21
+ 24, with `browser_close` and `browser_navigate` owning nothing.
22
+ - `probe`, secondary and labelled: a simulated BM25 search over the four
23
+ fields the tool search documentation names as searchable. It reads 100% on
24
+ both the official reference server and playwright, so the output says on
25
+ the page that it is a floor test and not a ranking.
26
+ - New ungraded rule family E141-E145, `--min-distinct` as the CI gate,
27
+ `--top` to change the simulated result window, `--json` / `--md`.
28
+ Reasoning in ADR-030; method and its limits in docs/METHODOLOGY.md.
29
+ - `rulesVersion` in every JSON envelope, because the tool version does not
30
+ identify a ruleset: a patch release can change what a rule fires on, and a
31
+ published grade is only reproducible next to the rules that produced it. The
32
+ rule inventory is pinned by a test (ADR-032).
33
+ - `--judge-model` and scenario key `judge_model` for `efaimo test`, so the
34
+ model under test no longer has to grade its own answers. Subject and judge
35
+ are routed independently and may be different providers.
36
+ - `--no-timestamp` on `weigh`, `check` and `find`.
37
+
38
+ ### Fixed
39
+
40
+ - **`efaimo test --live` could not run on its own default model.** The runner
41
+ sent `temperature` on every request, and Claude removed sampling parameters
42
+ from the 4.7 line onward, so `claude-sonnet-5` rejected the first call with a
43
+ 400 that is correctly not retried. Sampling parameters now go only to models
44
+ that still accept them, the request body is a pure function with a test
45
+ against it, and where the judge cannot be pinned the report says so instead
46
+ of implying a determinism it does not have. ADR-031.
47
+ - `efaimo test` reports a Fisher exact p on the **unparseable** counts as well
48
+ as on the passes, and calls a run `inconclusive` when the excluded trials are
49
+ skewed across the arms: two pass rates computed over different populations
50
+ cannot be subtracted.
51
+ - `--anthropic` names the model it measured against, in the output and in the
52
+ JSON. Claude model lines do not share a tokenizer, so a count without its
53
+ model cannot be reproduced. This is the rule `--window` already followed for
54
+ the context-window denominator.
55
+ - `weigh --out` no longer writes `generatedAt` into the baseline. A baseline
56
+ exists to be compared against, and a field that differs on every write is the
57
+ one field a comparison must ignore.
58
+
59
+ ### Changed
60
+
61
+ - docs/RULES.md now cites Anthropic's tool search documentation for E125's
62
+ lower threshold, which previously carried no source at all (the MCP quality
63
+ table has never had a source column). The thresholds themselves are
64
+ unchanged, and the citation covers the 30 boundary only: Anthropic writes
65
+ that tool selection "degrades once you exceed 30-50 available tools", which
66
+ says nothing about the 60 boundary, and the table says so.
67
+
68
+ ## [0.1.2] - 2026-08-03
69
+
70
+ The 0.1.2 code backlog: edge-case fixes surfaced by the five-agent review, none
71
+ of which changes a published number or the documented command surface, plus the
72
+ security and CI hardening already staged.
73
+
74
+ ### Fixed
75
+
76
+ - `check --mcp <directory>` with no source files and no MCP SDK dependency now
77
+ fails instead of returning a clean A(100): an empty scan is a failure, never a
78
+ pass.
79
+ - S101: a skill with no frontmatter reports its missing `name` and `description`
80
+ as errors, so a total absence no longer grades higher than a broken one.
81
+ - E105: a server that exits on a bare stateless `tools/list` is flagged. The
82
+ crash outcome was unhandled, so a crashing server looked more migrated than
83
+ one that answers with a not-initialized error.
84
+ - E124: the "N of M parameters lack descriptions" summary is no longer dropped
85
+ by the display cap on schemas with five or more oversized enums.
86
+ - S106: the escape check is lexical (the resolved path), not a `..` substring,
87
+ so a file merely named `notes..md` is not flagged as escaping.
88
+ - A Poetry `mcp = "^2.0"` constraint in `pyproject.toml` is read as the 2.x
89
+ line, not legacy; the version regex had captured the `=` assignment.
90
+ - `weigh --diff` with a skill baseline fails with a clear message instead of a
91
+ raw TypeError; a server baseline is required.
92
+ - `--max-tokens` and `--allow-increase` reject non-positive / negative values
93
+ instead of turning the gate into an always-fail with a nonsense message.
94
+ - `efaimo test --live` against an OpenAI o-series reasoning model no longer
95
+ sends an unsupported `temperature` (a 400) and gives it token headroom.
96
+ - The live test harness retries a transient 429 or 5xx with backoff instead of
97
+ discarding every already-paid trial on the first blip.
98
+ - The test report prints a clean note instead of "95% interval NaN to NaN" when
99
+ an arm produces no scoreable trial.
100
+ - A dead stdio target fails in about one `--timeout`, not two: the legacy
101
+ handshake and the stateless fallback now share the budget.
102
+ - Internal: dropped a dead exported constant and the unused per-pattern
103
+ `severity` on the injection heuristics (every hit is info + ungraded by
104
+ design).
105
+
106
+ ### Security
107
+
108
+ - The lockfile was re-resolved so the dev/CI tree carries
109
+ `@hono/node-server` 2.0.12 instead of the 1.19.x line that
110
+ GHSA-frvp-7c67-39w9 covers (moderate, path traversal in `serve-static`, in
111
+ the half of the MCP SDK this CLI never executes). A fresh `npx efaimo`
112
+ install already resolved the patched line; this aligns the committed tree
113
+ with what users actually get, and `pnpm audit --prod` is clean again.
114
+
115
+ ### Changed
116
+
117
+ - `@modelcontextprotocol/sdk` floor moved within-range from ^1.29.0 to ^1.30.0
118
+ (what a fresh install resolves anyway). The full suite (136 tests) is green.
119
+ - CI: the reference-server smoke is pinned to
120
+ `@modelcontextprotocol/server-everything@2026.7.4`. Per-commit CI should be
121
+ deterministic; news about the reference server's latest belongs to a
122
+ scheduled run, not to a push.
123
+ - CI: `spec-drift.yml` and the example workflow now pin actions by commit SHA
124
+ like the other workflows, and the example declares least-privilege
125
+ `permissions`.
126
+
7
127
  ## [0.1.1] - 2026-08-02
8
128
 
9
129
  Everything below was on `main` and unpublished. That gap was itself the
@@ -32,7 +152,7 @@ that notices the next revision by itself.
32
152
  hostile server could put ESC[1A ESC[2K in a tool name and rewrite the grade
33
153
  line printed above its own finding, so the reader saw "grade A (100), 0
34
154
  errors" while the contradiction was erased. `--no-color` did not help; that
35
- only suppresses colours efaimo adds.
155
+ only suppresses colors efaimo adds.
36
156
  - `.env` loading is restricted to `ANTHROPIC_API_KEY` and `OPENAI_API_KEY`.
37
157
  It previously set ANY key from a working-directory `.env`, so a cloned
38
158
  repository could set `NODE_TLS_REJECT_UNAUTHORIZED=0` and silently disable
@@ -40,8 +160,8 @@ that notices the next revision by itself.
40
160
  key.
41
161
  - Skill references that resolve outside the skill directory are no longer read
42
162
  by `weigh`. S106 already warned about them, but only after the read.
43
- - `SKILL.md` is read through the same 512KB-class cap as referenced files
44
- (2MB), instead of an uncapped read.
163
+ - `SKILL.md` is read through a capped read (2MB) on the same guarded path that
164
+ caps referenced files at 512KB, instead of an uncapped read.
45
165
  - The published composite Action no longer interpolates its inputs into a bash
46
166
  `run:` body, which was script injection in the consumer's runner.
47
167
 
@@ -101,7 +221,7 @@ that notices the next revision by itself.
101
221
  extend `CacheableResult`, so that surface is required now, is measured now,
102
222
  and the message names whichever surface failed.
103
223
 
104
- ## [0.1.0]
224
+ ## [0.1.0] - 2026-07-18
105
225
 
106
226
  First release.
107
227
 
@@ -114,7 +234,7 @@ First release.
114
234
  broken or auth-gated server is skipped with a reason, not fatal.
115
235
  `--out`/`--diff`/`--max-tokens`/`--allow-increase` for CI budget gates,
116
236
  `--badge` for a shields endpoint.
117
- - `efaimo check --mcp`: a quality grade (E121-E130: descriptions, schemas,
237
+ - `efaimo check --mcp`: a quality grade (E121-E128, E130: descriptions, schemas,
118
238
  annotations, cost) plus a separate, ungraded 2026-07-28 migration diff
119
239
  (E101-E118: what the stateless spec breaks and how to fix it, each item naming
120
240
  its SEP). Speaks both the legacy handshake and bare stateless requests, so
@@ -138,5 +258,8 @@ First release.
138
258
  Documented rule set (`docs/RULES.md`), token methodology (`docs/METHODOLOGY.md`),
139
259
  and integration guide (`docs/INTEGRATIONS.md`).
140
260
 
261
+ [unreleased]: https://github.com/efaimo-ai/efaimo/compare/v0.2.0...HEAD
262
+ [0.2.0]: https://github.com/efaimo-ai/efaimo/releases/tag/v0.2.0
263
+ [0.1.2]: https://github.com/efaimo-ai/efaimo/releases/tag/v0.1.2
141
264
  [0.1.1]: https://github.com/efaimo-ai/efaimo/releases/tag/v0.1.1
142
265
  [0.1.0]: https://github.com/efaimo-ai/efaimo/releases/tag/v0.1.0
package/README.md CHANGED
@@ -53,7 +53,7 @@ hygiene. Run it on a folder and every skill gets its own grade, not one aggregat
53
53
  **We graded 36 public Agent Skills**: every skill in `anthropics/skills`,
54
54
  `anthropics/claude-cookbooks`, and `obra/superpowers`, at pinned commits. 97%
55
55
  score an A, but even this curated set has real issues: Anthropic's own
56
- `claude-api` scores a C with an over-limit description and ~18k-token
56
+ `claude-api` scores a C (73) with an over-limit description and ~18k-token
57
57
  instructions, and the median skill's instructions run ~1,632 tokens loaded on
58
58
  every trigger. Full report, the corpus manifest, and the two commands that
59
59
  reproduce it: [the Skills Quality Index](./research/skills-index/REPORT.md).
@@ -176,6 +176,56 @@ grade A (95) quality: 0 errors 1 warning 0 info
176
176
 
177
177
  </details>
178
178
 
179
+ ### Can a search even single your tools out?
180
+
181
+ A host can mark tools `defer_loading: true`, which keeps their definitions out of
182
+ the context window until a search returns them; Anthropic recommends turning that
183
+ on once tool definitions pass ~10k tokens. Under deferral, a tool nothing
184
+ surfaces costs no context and provides no capability, and neither `weigh` nor
185
+ `check` looks at that.
186
+
187
+ ```bash
188
+ npx efaimo find "npx -y my-mcp-server"
189
+ ```
190
+
191
+ Two numbers come back, and they are different kinds of claim.
192
+
193
+ **`distinct`** is a property of your catalog: how many tools own a term no other
194
+ tool has. A tool that owns none cannot be matched by any query that does not also
195
+ match a competitor. That follows from the index, not from a model of how anyone
196
+ searches, and it is the number `--min-distinct` gates on in CI.
197
+
198
+ ```text
199
+ distinct 22/24 (91.7%) tools that own a word no other tool has
200
+ probe 24/24 (100%) returned by a simulated search for their own description
201
+
202
+ words tool vocabulary no other tool has
203
+ 0 browser_close none: every word also on browser_click, browser_drag, browser_drop
204
+ 0 browser_navigate none: every word also on browser_tabs, browser_navigate_back, browser_network_requests
205
+ 1 browser_evaluate evaluate
206
+ 1 browser_hover hover
207
+ 2 browser_resize window, resize
208
+ ```
209
+
210
+ *(That is `@playwright/mcp` 0.0.78, a well-maintained server, verbatim. Two of
211
+ its twenty-four tools have no word of their own, and two more own only the word
212
+ in their own name, which nobody describing a task would type.)*
213
+
214
+ Four public servers measured this way, with the commands that reproduce each
215
+ row, are in [the findability report](./research/findability/REPORT.md). The
216
+ weakest was Notion's official server at 16 of 24, where `API-get-user` and
217
+ `API-get-users` are different tools and nothing in their vocabulary separates
218
+ them.
219
+
220
+ **`probe`** is a simulated BM25 search over the four fields tool search matches
221
+ on, and it is labelled a simulation because it is one. It reads 100% on both the
222
+ official reference server and playwright, so it is a floor test that catches an
223
+ empty description or a literal duplicate, not a ranking. `find` says that on
224
+ every run rather than letting a saturated number look like a good score.
225
+
226
+ There is no findability grade, on purpose:
227
+ [docs/METHODOLOGY.md](./docs/METHODOLOGY.md) has the method, its limits, and why.
228
+
179
229
  ## From an agent
180
230
 
181
231
  `efaimo mcp` runs efaimo as a small, read-only MCP server, so an agent can lint or
@@ -252,8 +302,16 @@ And a mitigation you cannot measure is a mitigation you are trusting on faith:
252
302
  `weigh` reports the definitions your server actually ships, which is the input
253
303
  every mitigation operates on.
254
304
 
255
- If deferred loading covers your whole setup, you may not need this tool. Run
256
- `weigh` once and find out; that answer is worth more than our opinion.
305
+ **And there is a fourth reason we did not have when this section was written.** Deferring a tool does not remove the question, it replaces
306
+ it: a deferred tool that no search returns costs nothing and does nothing, so
307
+ the thing worth measuring becomes whether each tool can be picked out of the
308
+ catalog at all. `efaimo find` measures that, and on four public servers it
309
+ found two to eight tools apiece that own no word their neighbours lack.
310
+ Deferral is what makes findability matter, not what makes it moot.
311
+
312
+ If deferred loading covers your whole setup, run `weigh` and `find` once and
313
+ see what it costs you and what it hides; those answers are worth more than our
314
+ opinion.
257
315
 
258
316
  ## Install
259
317
 
@@ -296,6 +354,7 @@ More recipes (pre-commit, GitLab, editor audit, programmatic use):
296
354
  | **S101 to S106** | skills: frontmatter and trigger quality, trigger collisions, context budget, reference integrity, injection hygiene |
297
355
  | **E101 to E118** | MCP 2026-07-28 readiness: deprecated primitives, statelessness, `server/discover`, `resultType`, cache fields, transport |
298
356
  | **E121 to E128, E130** | MCP quality: description quality, annotations, schema hygiene, tool-count and token-cost budgets |
357
+ | **E141 to E146** | MCP findability: exclusive vocabulary, whether a search returns the tool, indistinguishable pairs, names that carry no domain word |
299
358
 
300
359
  `E000` is not a finding about your server: it means one of efaimo's own rules
301
360
  threw and was skipped, so the report is incomplete. It is never scored.
@@ -303,15 +362,18 @@ threw and was skipped, so the report is incomplete. It is never scored.
303
362
  Quality (E12x-E13x) and skill (S) findings set the letter grade; readiness
304
363
  findings (E101-E118) are reported as an ungraded migration diff, which
305
364
  `--strict-readiness` can turn into a non-zero exit
306
- without touching the grade.
365
+ without touching the grade. Findability findings (E14x) belong to `efaimo find`
366
+ and never touch a grade either.
307
367
  Every finding carries a stable id you can suppress or link. See
308
368
  [docs/RULES.md](./docs/RULES.md).
309
369
 
310
370
  ## Roadmap
311
371
 
312
- - Harden `efaimo test`: a separately chosen judge model, confidence intervals on
313
- the delta, multi-turn tool-use trials, and judge calibration, so the
314
- experimental harness earns unqualified trust.
372
+ - Harden `efaimo test`: a length-matched placebo arm (the control currently gets
373
+ no system prompt at all, which confounds the skill's content with having any
374
+ system prompt), multi-turn tool-use trials, and judge calibration, so the
375
+ experimental harness earns unqualified trust. A separately chosen judge model
376
+ and confidence intervals on the delta are done.
315
377
  - Readiness rules for the two 2026-07-28 changes that have none yet: the
316
378
  `Mcp-Method` / `Mcp-Name` request headers (SEP-2243), and Tasks moving out of
317
379
  core into the `io.modelcontextprotocol/tasks` extension (SEP-2663).
package/SKILL.md CHANGED
@@ -1,9 +1,9 @@
1
1
  ---
2
2
  name: efaimo
3
- description: Audit MCP servers and Agent Skills for quality and context-window cost. Use when the user wants to check an MCP server's token cost, lint a skill, verify 2026-07-28 MCP spec readiness, or add an efaimo badge or CI gate.
3
+ description: Audit MCP servers and Agent Skills for quality and context-window cost. Use when the user wants to check an MCP server's token cost, lint a skill, verify 2026-07-28 MCP spec readiness, find out whether a search can single out a server's tools under deferred tool loading, or add an efaimo badge or CI gate.
4
4
  license: Apache-2.0
5
5
  metadata:
6
- version: "0.1.1"
6
+ version: "0.2.0"
7
7
  homepage: "https://efaimo.ai"
8
8
  ---
9
9
 
@@ -26,8 +26,8 @@ you would be willing to run yourself. A remote target (`https://...`) is not
26
26
  executed and is the safe option for something you do not trust. `--client`
27
27
  reads the MCP config files in the current directory as well as your user
28
28
  config, so running it inside a repository you did not write means starting the
29
- servers that repository configured. This warning lives in `SECURITY.md` and did
30
- not live here, which is the wrong way round: the policy is where humans look,
29
+ servers that repository configured. Until 2026-08-02 this warning lived only in
30
+ `SECURITY.md`, which was the wrong way round: the policy is where humans look,
31
31
  and this file is where agents take orders.
32
32
 
33
33
  - **Weigh context cost of an MCP server**
@@ -49,6 +49,11 @@ and this file is where agents take orders.
49
49
  - **Lint a skill**
50
50
  `npx efaimo check --skill ./skills/` validates frontmatter, trigger quality,
51
51
  context budget, file references, and injection patterns.
52
+ - **Check whether a search can single a tool out**
53
+ `npx efaimo find "npx -y <server>"` reports how many tools own a term no
54
+ other tool in the catalog has. A tool that owns none cannot be matched by any
55
+ query that does not also match a competitor, which matters once a host defers
56
+ tool loading. `--min-distinct 100` is the CI gate.
52
57
  - **Gate a pull request on context cost**
53
58
  Save a baseline with `npx efaimo weigh "<server>" --out base.json`, then in CI
54
59
  run `npx efaimo weigh "<server>" --diff base.json --allow-increase 10` to fail
@@ -56,11 +61,13 @@ and this file is where agents take orders.
56
61
 
57
62
  ## Reading the output
58
63
 
59
- Findings carry a stable rule id (E1xx MCP readiness, E12x-E13x MCP quality, S1xx
60
- skills) and a severity. The letter grade covers quality and skill findings only;
61
- 2026-07-28 readiness items appear separately as an ungraded migration diff. Token
62
- numbers are o200k estimates unless `--anthropic` is used; see the methodology doc
63
- for the full method.
64
+ Findings carry a stable rule id (E1xx MCP readiness, E12x-E13x MCP quality,
65
+ E14x findability, S1xx skills) and a severity. The letter grade
66
+ covers quality and skill findings only; 2026-07-28 readiness items appear
67
+ separately as an ungraded migration diff, and findability is a
68
+ separate command with no grade at all. Token numbers are o200k estimates unless
69
+ `--anthropic` is used, which names the model it measured against; see the
70
+ methodology doc for the full method.
64
71
 
65
72
  ## Notes
66
73
 
@@ -50,6 +50,14 @@ export async function checkMcpTarget(target, opts = {}) {
50
50
  }
51
51
  export function checkMcpRepoOnly(repoPath, label) {
52
52
  const repo = scanRepo(repoPath);
53
+ // An empty result is a failure, never a pass. With no source files read and no
54
+ // dependency manifest naming an MCP SDK, there was nothing to examine here, and
55
+ // a clean A(100) would be a grade for a directory we never looked inside. Fail
56
+ // loudly, the way checkSkillSet does for a missing SKILL.md.
57
+ if (repo.filesScanned === 0 && !repo.sdk) {
58
+ throw new Error(`no MCP server source found under "${repoPath}": no code files and no SDK dependency. ` +
59
+ `Point --mcp at the server's run command, or at a directory that contains its source.`);
60
+ }
53
61
  const emptyIntro = {
54
62
  targetLabel: label,
55
63
  transport: "stdio",