pattern-mcp 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.
package/README.md CHANGED
@@ -1,54 +1,171 @@
1
1
  # Pattern
2
2
 
3
- MCP server exposing two tools. `recommend_component` judges whether a UI
4
- component need should be met with an existing shadcn/ui or 21st.dev
5
- component, or requires a custom build guided by a real-app reference from
6
- Mobbin and/or Figma Community. Returns a structured verdict, not a list of
7
- search results — built for an agent to consume mid-build, not for a human
8
- to browse. `record_component_decision` records a decision the calling agent
9
- has actually acted on, so a later `recommend_component` call in the same
10
- project can weigh it as a consistency signal — see
11
- [Per-project decision memory](#per-project-decision-memory).
12
-
13
- This implements the judgment layer validated in the product brief: field/
14
- requirement coverage scored against real component evidence, thresholded
15
- into `use_existing` / `custom_build`, with a `no_candidates_found` bucket
16
- kept distinct from low coverage, a static skip-list for trivial primitives,
17
- and a `computed_at` timestamp since coverage is a snapshot, not a permanent
18
- fact.
3
+ [![Publish](https://github.com/donaldrichard19-LVD/pattern-mcp/actions/workflows/publish.yml/badge.svg)](https://github.com/donaldrichard19-LVD/pattern-mcp/actions/workflows/publish.yml)
4
+ [![npm version](https://img.shields.io/npm/v/pattern-mcp.svg)](https://www.npmjs.com/package/pattern-mcp)
5
+ [![npm downloads](https://img.shields.io/npm/dm/pattern-mcp.svg)](https://www.npmjs.com/package/pattern-mcp)
6
+ [![MIT license](https://img.shields.io/badge/license-MIT-111111.svg)](./LICENSE)
7
+
8
+ Pattern is an MCP server that helps coding agents make better UI
9
+ component decisions.
10
+
11
+ [Website](https://usepattern.sh) · [npm](https://www.npmjs.com/package/pattern-mcp) · [Report an issue](https://github.com/donaldrichard19-LVD/pattern-mcp/issues/new/choose)
12
+
13
+ ## Install
14
+
15
+ ```bash
16
+ npm install pattern-mcp
17
+ ```
18
+
19
+ See [Quick Start](#quick-start) below to add your Anthropic API key and connect
20
+ Pattern to your MCP client.
21
+
22
+ ## What Pattern Does
23
+
24
+ Instead of returning a list of search results, Pattern looks at what you
25
+ need, checks real components against that need, and tells the agent
26
+ whether to:
27
+
28
+ - **Use an existing component** from shadcn/ui, 21st.dev, or ReUI
29
+ - **Build a custom component**, using a real product reference from
30
+ Mobbin and/or Figma Community
31
+
32
+ Pattern is designed for agents to use **while they are building**.
33
+
34
+ It exposes three tools:
35
+
36
+ - `recommend_component` — evaluates a UI component need and returns a
37
+ structured recommendation.
38
+ - `extract_requirements` — runs just the requirement-extraction step on
39
+ its own, so you can inspect or hand-edit the checklist before
40
+ `recommend_component` spends its search+score budget on it.
41
+ - `record_component_decision` — records what the agent actually did so
42
+ future recommendations in the same project can take that decision into
43
+ account.
19
44
 
20
45
  ## How it works
21
46
 
22
- The server does not scrape shadcn/21st.dev/Mobbin/Figma itself. Each tool
23
- call makes one or more requests to the Anthropic Messages API
24
- (`claude-sonnet-5` by default) with the server-side `web_search` tool
25
- enabled, and a system prompt that encodes the full process: skip-list
26
- check, requirement extraction, candidate search, real-evidence coverage
27
- scoring, threshold, and — on `custom_build` — reference lookups against
28
- Mobbin and Figma Community. No new credentials are required for the Figma
29
- lookup it uses the same plain `web_search` mechanism as everything else
30
- in the tool, not the Figma API. The model returns structured JSON; the
31
- server recomputes the coverage fraction from the `requirements_checked`
32
- array itself (rather than trusting the model's stated percentage) and
33
- applies the verdict/confidence threshold in code.
34
-
35
- **Boundary-risk ensemble.** Validation found that a single run's coverage
36
- score can vary between calls on the same input — not because search results
37
- differ, but because the model can judge the same piece of evidence
38
- differently run to run (see Known limitations). When a call's recounted
39
- coverage lands close enough to a threshold boundary to plausibly flip the
40
- verdict, the server automatically re-runs the judgment 2 more times and
41
- takes the majority verdict. If the 3 runs disagree (a 2/3 split), the result
42
- ships with `confidence: "low"` and an `ensemble` field so the calling agent
43
- can see it was a close call rather than a confident read. Calls that land
44
- clearly inside a threshold band never trigger this and stay single-run
45
- see [Cost](#cost) below for the measured impact.
46
-
47
- Trivial primitives (button, input, checkbox, label, badge, spinner, tooltip,
48
- avatar, icon) are caught locally before any API call, so they don't spend a
49
- request.
50
-
51
- ## Setup — quickstart
47
+ ![How it works](docs/images/how-it-works.png)
48
+
49
+ For each `recommend_component` call, Pattern:
50
+
51
+ 1. Checks whether the need is a simple primitive that doesn't require a
52
+ search.
53
+ 2. Turns the request into a set of specific requirements, unless a
54
+ checklist was already supplied (see [`checklist`](#checklist)).
55
+ 3. Searches for matching shadcn/ui, 21st.dev, and ReUI components.
56
+ 4. Checks each candidate against the requirements using evidence from the
57
+ actual component.
58
+ 5. Calculates how much of the requirement is covered.
59
+ 6. Decides whether to use an existing component or build a custom one.
60
+ 7. If a custom build is needed, searches Mobbin and Figma Community for
61
+ real product examples.
62
+ 8. Returns the result as structured JSON the calling agent can act on.
63
+
64
+ Coverage is calculated by the server from the individual requirements it
65
+ checked. It does not simply trust the percentage returned by the model.
66
+
67
+ A result can also be:
68
+
69
+ - `use_existing`
70
+ - `custom_build`
71
+ - `no_candidates_found`
72
+ - `skip_list`
73
+
74
+ `no_candidates_found` is kept separate from a low-coverage result. Not
75
+ finding a candidate is different from finding candidates that don't cover
76
+ the requirements.
77
+
78
+ If `project_id` is supplied, Pattern also checks for past confirmed
79
+ decisions on that project and factors them in as a consistency signal —
80
+ never a rule that overrides a genuinely better match found in the current
81
+ search.
82
+
83
+ Every result includes `computed_at`, because coverage is a snapshot of the
84
+ search at that point in time, not a permanent fact. Every result also
85
+ includes `_meta` — the timing and token cost of that specific call (see
86
+ [Cost](#cost)).
87
+
88
+ ### Boundary-risk checks
89
+
90
+ The same evidence can sometimes be judged slightly differently between
91
+ model runs. When a result is close enough to a decision threshold that it
92
+ could change the verdict, Pattern automatically runs the judgment two more
93
+ times and uses the majority result.
94
+
95
+ If the three runs disagree, Pattern returns:
96
+
97
+ ```json
98
+ {
99
+ "confidence": "low",
100
+ "ensemble": {
101
+ "triggered": true,
102
+ "runs": ["use_existing", "custom_build", "use_existing"],
103
+ "agreement": "2/3"
104
+ }
105
+ }
106
+ ```
107
+
108
+ Results that are clearly inside a threshold don't trigger extra runs — see
109
+ [Cost](#cost) below for the measured impact.
110
+
111
+ ### Simple primitives
112
+
113
+ These are handled locally without an API call:
114
+
115
+ | Primitive | Use it for |
116
+ | --- | --- |
117
+ | `button` | A clickable action trigger |
118
+ | `input` | A single-line text entry field |
119
+ | `checkbox` | A binary on/off toggle |
120
+ | `label` | A caption for a field or control |
121
+ | `badge` | A small status or count indicator |
122
+ | `spinner` | An indeterminate loading indicator |
123
+ | `tooltip` | A contextual hover/focus hint |
124
+ | `avatar` | A user or entity image, or initials |
125
+ | `icon` | A single glyph or symbol |
126
+
127
+ This keeps trivial requests fast and avoids unnecessary API usage.
128
+
129
+ ### What powers the search
130
+
131
+ Pattern does not scrape shadcn/ui, 21st.dev, ReUI, Mobbin, or Figma Community
132
+ itself.
133
+
134
+ Each tool call makes one or more requests to the Anthropic Messages API,
135
+ using `claude-sonnet-5` by default. The server enables Anthropic's
136
+ `web_search` tool and provides a system prompt that defines the full
137
+ decision process.
138
+
139
+ That process includes:
140
+
141
+ - Skip-list checks
142
+ - Requirement extraction
143
+ - Component search
144
+ - Evidence-based coverage scoring
145
+ - Decision thresholds
146
+ - Mobbin and Figma Community reference searches when a custom build is
147
+ needed
148
+
149
+ Figma Community does not require a Figma API key. Pattern uses the same
150
+ web search mechanism for Figma Community as it does for the other sources.
151
+
152
+ The model returns structured JSON. Pattern then applies important checks
153
+ itself, including recalculating coverage and applying the decision
154
+ threshold.
155
+
156
+ ## Quick Start
157
+
158
+ ### 1. Install
159
+
160
+ ```bash
161
+ npm install pattern-mcp
162
+ ```
163
+
164
+ This installs the `pattern-mcp` command via `npx` (or your project's
165
+ local `node_modules/.bin`), used in the client configs below.
166
+
167
+ <details>
168
+ <summary>Build from source instead</summary>
52
169
 
53
170
  ```bash
54
171
  git clone <this repo>
@@ -57,104 +174,172 @@ npm install
57
174
  npm run build
58
175
  ```
59
176
 
60
- Requires `ANTHROPIC_API_KEY` the account whose key you use pays for every
61
- call this tool makes (see [Cost](#cost) below). Get one from the
62
- [Anthropic Console](https://console.anthropic.com) (Settings → API Keys);
63
- this requires its own billing setup. **This is not the same thing as a
64
- Claude.ai or Claude Code subscription** — a Pro/Max plan does not cover
65
- API usage, and a subscription login won't get you a key. You need a
66
- separate Console account with credits or a payment method attached.
67
-
68
- **Point your MCP client at it** — this is a standard MCP server, so it works
69
- with any MCP-compatible client, not just one. Drop this into your client's
70
- config (adjusting the path per client), swapping in your own project path
71
- and key:
72
-
73
- - **Claude Code**: either add `"pattern": { ... }` (the
74
- block below) to the `mcpServers` object in `.mcp.json` at your project
75
- root, or run:
76
- ```bash
77
- claude mcp add pattern \
78
- -e ANTHROPIC_API_KEY=sk-ant-... \
79
- -- node /absolute/path/to/pattern-mcp/dist/index.js
80
- ```
81
- This registers under `--scope local` (the default) — tied to the
82
- current project directory only. Add `--scope user` (or `-s user`)
83
- instead to make it available across **all** your projects:
84
- ```bash
85
- claude mcp add pattern \
86
- -e ANTHROPIC_API_KEY=sk-ant-... \
87
- --scope user \
88
- -- node /absolute/path/to/pattern-mcp/dist/index.js
89
- ```
90
- **Flag order matters here.** `-e`/`--env` and `-s`/`--scope` must come
91
- *before* the `--` separator and command — `claude mcp add`'s
92
- `[args...]` capture is variadic, so a flag placed *after* the command
93
- (e.g. `node dist/index.js --scope user`) is liable to be swallowed as
94
- an argument to `node` itself instead of being parsed as a flag for
95
- `claude mcp add`. Keep all your flags on the left of `--`, the command
96
- and its own args on the right.
97
-
98
- `claude mcp add` stores this in `~/.claude.json` (a local- or
99
- user-scoped entry depending on `--scope`), not in a project file —
100
- check with `claude mcp list` (should show
101
- `pattern ... ✔ Connected`). Avoid `claude mcp get
102
- pattern` if you can — it prints your key back to the
103
- terminal in plaintext, so `claude mcp list`'s connection status is
104
- usually enough without that risk.
105
- - Cursor: `.cursor/mcp.json`
106
- - Codex CLI: `~/.codex/config.toml` (global) or `.codex/config.json`
107
- (project-level) same `mcpServers` shape, TOML or JSON depending on file
108
- - Claude Desktop: its MCP settings file
177
+ Use `node /absolute/path/to/pattern-mcp/dist/index.js` as the server
178
+ command in place of `npx pattern-mcp` in the examples below.
179
+
180
+ </details>
181
+
182
+ ### 2. Add your Anthropic API key
183
+
184
+ Pattern requires:
185
+
186
+ ```
187
+ ANTHROPIC_API_KEY
188
+ ```
189
+
190
+ The API account associated with this key pays for the requests Pattern
191
+ makes (see [Cost](#cost) below).
192
+
193
+ You get the key from the Anthropic Console under Settings → API Keys.
194
+ API billing is separate from Claude.ai or Claude Code subscriptions. A
195
+ Claude Pro or Max subscription does not include API usage.
196
+
197
+ ### Connect Pattern to your MCP client
198
+
199
+ Pattern is a standard MCP server, so it works with MCP-compatible
200
+ clients.
201
+
202
+ The server command is:
203
+
204
+ ```
205
+ npx pattern-mcp
206
+ ```
207
+
208
+ #### Claude Code
209
+
210
+ You can add Pattern to your project's `.mcp.json` or register it with the
211
+ CLI.
212
+
213
+ For the current project:
214
+
215
+ ```bash
216
+ claude mcp add pattern \
217
+ -e ANTHROPIC_API_KEY=sk-ant-... \
218
+ -- npx pattern-mcp
219
+ ```
220
+
221
+ This uses the default local scope, so the server is available to the
222
+ current project.
223
+
224
+ To make Pattern available across your projects:
225
+
226
+ ```bash
227
+ claude mcp add pattern \
228
+ -e ANTHROPIC_API_KEY=sk-ant-... \
229
+ --scope user \
230
+ -- npx pattern-mcp
231
+ ```
232
+
233
+ **Important:** put `-e`/`--env` and `--scope` before the `--`. Everything
234
+ after `--` is treated as the command and its arguments.
235
+
236
+ Check the connection with:
237
+
238
+ ```bash
239
+ claude mcp list
240
+ ```
241
+
242
+ You should see Pattern with a `✔ Connected` status.
243
+
244
+ `claude mcp add` stores the configuration in `~/.claude.json`. Avoid
245
+ `claude mcp get pattern` when possible because it can print your API key
246
+ in plaintext.
247
+
248
+ #### Cursor
249
+
250
+ Add Pattern to:
251
+
252
+ ```
253
+ .cursor/mcp.json
254
+ ```
255
+
256
+ #### Codex CLI
257
+
258
+ Pattern can be configured globally in:
259
+
260
+ ```
261
+ ~/.codex/config.toml
262
+ ```
263
+
264
+ or at the project level in:
265
+
266
+ ```
267
+ .codex/config.json
268
+ ```
269
+
270
+ Use the MCP configuration format supported by your Codex CLI version.
271
+
272
+ #### Claude Desktop
273
+
274
+ Add Pattern through Claude Desktop's MCP settings.
275
+
276
+ The configuration looks like:
109
277
 
110
278
  ```json
111
279
  {
112
280
  "mcpServers": {
113
281
  "pattern": {
114
- "command": "node",
115
- "args": ["/absolute/path/to/pattern-mcp/dist/index.js"],
116
- "env": { "ANTHROPIC_API_KEY": "sk-ant-..." }
282
+ "command": "npx",
283
+ "args": ["pattern-mcp"],
284
+ "env": {
285
+ "ANTHROPIC_API_KEY": "sk-ant-..."
286
+ }
117
287
  }
118
288
  }
119
289
  }
120
290
  ```
121
291
 
122
- Restart your MCP client, then confirm it picked up the tool — ask your
123
- agent to list its available MCP tools and look for `recommend_component`.
124
- For Claude Code specifically, `claude mcp list` will show a health-checked
125
- `✔ Connected` status without needing to ask the agent directly.
292
+ Restart your MCP client after adding Pattern.
293
+
294
+ Then ask your agent to list its available MCP tools and look for:
295
+
296
+ ```
297
+ recommend_component
298
+ ```
126
299
 
127
300
  ## Try it
128
301
 
129
- Ask your agent something like: *"Use recommend_component to find me a UI
130
- component for a price breakdown showing nightly rate, cleaning fee, service
131
- fee, and taxes I'm building an Airbnb-style booking checkout in React with
132
- Tailwind."* The agent should call the tool and act on the verdict directly
133
- (install a real component, or start from the returned checklist and
134
- Mobbin/Figma Community reference) rather than just describing what it
135
- found.
136
-
137
- **What you'll actually see:** both verdict paths now include a written,
138
- grounded description, not just a bare link or install command. A
139
- `use_existing` verdict includes `component_description` — what the
140
- recommended component actually does and looks like, described before the
141
- agent installs anything. A `custom_build` verdict includes
142
- `reference_description` for each reference it found — what that Mobbin
143
- screen or Figma Community file actually shows. Either way, testers get a
144
- specific, readable description grounded in what the model actually found
145
- during search, not generic filler.
146
-
147
- If you want to sanity-check the tool itself rather than a real feature,
148
- these five needs are the ones this project's own validation was built
149
- against, spanning the full range of outcomes (clean commodity match,
150
- false-positive-prone case, zero candidates, and boundary/near-tie cases):
151
- price breakdown with fees and taxes, cancellation policy display, host
152
- earnings dashboard, image gallery for a property listing, and a host-guest
153
- messaging inbox all in the same Airbnb-style rental marketplace domain.
302
+ Give your agent a specific UI need, for example:
303
+
304
+ > Use recommend_component to find me a UI component for a price breakdown
305
+ > showing nightly rate, cleaning fee, service fee, and taxes. I'm building
306
+ > an Airbnb-style booking checkout in React with Tailwind.
307
+
308
+ The agent should use the result to make the next decision:
309
+
310
+ - Install or use the recommended component, or
311
+ - Start a custom build using the returned requirements and product
312
+ references.
313
+
314
+ Pattern returns useful descriptions for both paths.
315
+
316
+ - For an existing component, `component_description` explains what the
317
+ component does and looks like before the agent installs it.
318
+ - For a custom build, `reference_description` explains what each Mobbin
319
+ or Figma Community reference actually shows.
320
+
321
+ These descriptions are grounded in what Pattern found during the search
322
+ rather than generic descriptions.
323
+
324
+ ## Validation examples
325
+
326
+ Pattern's validation suite uses five UI needs from an Airbnb-style rental
327
+ marketplace:
328
+
329
+ - Price breakdown with fees and taxes
330
+ - Cancellation policy display
331
+ - Host earnings dashboard
332
+ - Property image gallery
333
+ - Host-guest messaging inbox
334
+
335
+ Together, these cover different outcomes, including clear matches,
336
+ false-positive-prone searches, no candidates, and decisions close to the
337
+ threshold.
154
338
 
155
339
  ## Tool: `recommend_component`
156
340
 
157
- **Input:**
341
+ ### Input
342
+
158
343
  ```json
159
344
  {
160
345
  "component_need": "price breakdown with fees and taxes",
@@ -164,137 +349,250 @@ messaging inbox — all in the same Airbnb-style rental marketplace domain.
164
349
  "project_id": "my-booking-app"
165
350
  }
166
351
  ```
167
- `component_need` should be specific, not a category — "price breakdown with
168
- fees and taxes" not "pricing". Vague category names are what produced
169
- false-positive matches during validation (a generic SaaS pricing-tier
170
- component scoring as a match for a booking checkout).
171
-
172
- `project_id` is optional — a project name or path the calling agent
173
- supplies. When present, past decisions recorded for that same `project_id`
174
- via `record_component_decision` are pulled from
175
- [per-project decision memory](#per-project-decision-memory) and included in
176
- the prompt as a *signal, not a rule*: the model is instructed to weigh
177
- consistency with a highly similar past decision, but never to let it
178
- override a genuinely better match this search finds, and never to skip
179
- searching or scoring because a past decision exists. Coverage is still
180
- computed fresh on every call regardless — see
181
- [No caching, by design](#known-limitations-carried-over-from-validation).
182
- Omit `project_id` to skip memory entirely; there's no shared/global bucket
183
- it falls back to.
184
-
185
- **Output:** JSON matching:
352
+
353
+ `component_need` should describe the actual UI you need, not just a
354
+ category.
355
+
356
+ Good: `price breakdown with fees and taxes`
357
+ Too vague: `pricing`
358
+
359
+ Vague requests can produce misleading matches. For example, a generic
360
+ SaaS pricing table may look like a match for "pricing" even though it
361
+ doesn't work for a booking checkout.
362
+
363
+ #### `project_id`
364
+
365
+ `project_id` is optional.
366
+
367
+ When provided, Pattern can use decisions previously recorded for the
368
+ same project (see [Per-project decision memory](#per-project-decision-memory))
369
+ as a consistency signal.
370
+
371
+ A previous decision can help the model stay consistent with similar UI
372
+ decisions, but it cannot override a better match found in the current
373
+ search.
374
+
375
+ Pattern still searches and scores every request from scratch. Past
376
+ decisions never cause a search to be skipped.
377
+
378
+ If you leave out `project_id`, Pattern does not use project memory.
379
+
380
+ #### `checklist`
381
+
382
+ `checklist` is optional -- an array of requirement strings.
383
+
384
+ When provided, `recommend_component` skips its own internal requirement
385
+ extraction entirely and scores coverage against exactly the items you
386
+ passed, instead of extracting its own checklist. Search and scoring still
387
+ run fresh every call; only the extraction step is skipped.
388
+
389
+ This is meant to be used together with [`extract_requirements`](#tool-extract_requirements):
390
+ call `extract_requirements` first, inspect (or hand-edit) the checklist it
391
+ returns, then pass that checklist here. That gives you a chance to catch a
392
+ misread requirement before Pattern spends its search+score budget.
393
+
394
+ Leave `checklist` out to keep today's default behavior: `recommend_component`
395
+ extracts its own checklist internally, exactly as before this option
396
+ existed.
397
+
398
+ **Is the checklist actually skipped, not just re-derived?** Checked, not
399
+ assumed. `breakdown_ms.extract` for a `checklist`-provided call is smaller
400
+ than the default path's, but not near-zero -- which raised the question of
401
+ whether the model is still doing some of the extraction work in that
402
+ window rather than treating the checklist as fixed input. Reading the
403
+ model's actual reasoning (via `thinking` with `display: "summarized"`,
404
+ 5 runs: 3 with `checklist` provided, 2 default) answered it: the
405
+ `checklist`-provided runs' pre-search reasoning was a short, generic
406
+ "search shadcn/ui and 21st.dev" thought with no mention of the checklist's
407
+ content, e.g. *"I should look for existing image gallery component options
408
+ on shadcn/ui and 21st.dev"* -- consistently ~3-4 seconds. The default
409
+ runs' reasoning, by contrast, explicitly enumerated and derived the
410
+ checklist items (*"...mapping out the checklist: a photo grid with hero
411
+ and thumbnails... a full-screen lightbox with next/prev navigation,
412
+ keyboard support..."*) and took roughly 2x longer (~7-8 seconds). The
413
+ remaining time in the `checklist`-provided path is baseline model latency
414
+ before it decides to search, not re-extraction -- it doesn't scale with or
415
+ reference the checklist's content.
416
+
417
+ ### Output
418
+
186
419
  ```json
187
420
  {
188
421
  "verdict": "use_existing | custom_build",
189
422
  "confidence": "high | medium | low",
190
423
  "reason": "scored | no_candidates_found | skip_list",
191
424
  "computed_at": "2026-08-23",
192
- "requirements_checked": [ { "requirement": "...", "met": true, "evidence": "..." } ],
425
+ "requirements_checked": [
426
+ {
427
+ "requirement": "...",
428
+ "met": true,
429
+ "evidence": "..."
430
+ }
431
+ ],
193
432
  "coverage": "5/7 (71%)",
194
433
  "recommendation": {
195
- "source": "21st.dev | shadcn | null",
434
+ "source": "21st.dev | shadcn | reui | null",
196
435
  "install_command": "string | null",
197
- "component_description": "string (use_existing only) | null",
436
+ "component_description": "string | null",
198
437
  "reference": {
199
438
  "source": "Mobbin | Figma Community",
200
439
  "url": "...",
201
- "flow_name": "... (Mobbin only)",
202
- "file_name": "... (Figma Community only)",
440
+ "flow_name": "...",
441
+ "file_name": "...",
203
442
  "reference_description": "...",
204
443
  "url_type": "deep_link | entry_point"
205
444
  }
206
445
  },
207
- "ensemble": { "triggered": false },
208
- "past_decision_signal": { "considered": true, "note": "..." }
446
+ "ensemble": {
447
+ "triggered": false
448
+ },
449
+ "checklist_source": "extracted | provided",
450
+ "_meta": {
451
+ "total_ms": 41516,
452
+ "breakdown_ms": { "extract": 5006, "search": 3114, "score": 33396 },
453
+ "tokens_used": { "input": 8400, "output": 620 },
454
+ "estimated_cost_usd": 0.14
455
+ }
209
456
  }
210
457
  ```
211
- `ensemble.triggered` is `false` on the normal single-pass path. On a
212
- boundary-risk coverage result it becomes
213
- `{ "triggered": true, "runs": ["use_existing", "custom_build", "use_existing"], "agreement": "2/3" }`
214
- — see [Ensemble cost](#ensemble-cost-boundary-risk-cases-only) below.
215
-
216
- `past_decision_signal` only appears when `project_id` was provided **and**
217
- that project has at least one past decision recorded — omitted entirely
218
- otherwise, never a hollow `{ "considered": false }` on a call with nothing
219
- to consider. `considered` is `true` only when a past decision was
220
- genuinely similar enough to factor into scoring or recommendation, not
221
- just present in the list; `note` names which decision and how, or why none
222
- applied. This is enforced server-side, not just prompted: a
223
- `considered`/`note` pair the model returns on a call that had no
224
- past-decision context in its prompt is discarded rather than trusted — see
225
- [Per-project decision memory](#per-project-decision-memory).
226
-
227
- **`recommendation.reference` shape depends on how many sources actually
228
- grounded**, not just on the verdict. On a `custom_build` verdict:
229
- - Both Mobbin and Figma Community returned a real, grounded result:
230
- `reference` is an **array of both** objects.
231
- - Only one of the two grounded: `reference` is a **single object**, same
232
- shape as before this feature existed — never a one-element array.
233
- - Neither grounded: `reference` is `null`, same as today's
234
- no-fabrication rule for a Mobbin-only lookup that found nothing.
235
-
236
- No new credentials are required for the Figma Community reference — it
237
- uses the same `web_search` mechanism as every other lookup in this tool,
238
- not the Figma API, so there's no separate token to configure.
239
-
240
- **`reference.url_type` tells you whether the URL is a deep link or just a
241
- search entry point.** A Mobbin or Figma Community search result is very
242
- often a category/browse page (e.g.
243
- `mobbin.com/explore/mobile/screens/notifications`), not a direct link to
244
- the specific screen or flow the model actually identified (e.g. "Saturn
245
- Calendar - Notifications List") the original gap this field exists to
246
- disclose. On a `custom_build` verdict:
247
-
248
- - **Mobbin**: the server fetches the search result page (via the
249
- `web_fetch` tool) and looks for a more specific permalink to the
250
- identified screen/flow actually written on that page. Found and
251
- confirmed → `url_type: "deep_link"` and `url` is that permalink. Not
252
- found (including when the fetch itself fails) `url_type:
253
- "entry_point"`, `url` stays the category/search page, and
254
- `reference_description` is guaranteed to say so explicitly (append or
255
- auto-generated server-side, never left to the model alone) — so a
256
- reader always knows whether they're getting the exact screen or a
257
- browse page they'll need to search themselves.
258
- - **Figma Community**: a result URL containing `/community/file/` is
259
- already file-specific by Figma's own URL structure, so it's treated as
260
- `url_type: "deep_link"` without spending a fetch on it. A result that
261
- *isn't* a `/community/file/` URL (an occasional browse/tag page) goes
262
- through the same fetch-and-verify path as Mobbin. In practice a Figma
263
- fetch will almost always fail regardless — `figma.com/robots.txt`
264
- disallows `ClaudeBot` site-wide — so a non-file Figma result reliably
265
- ends up `entry_point`, honestly.
266
-
267
- This is enforced the same way as every other grounding rule in this
268
- project: **server-side, not just prompt instruction.** A claimed deep
269
- link is only kept if it's literally present in the text of a page the
270
- server actually fetched; a claim that fails that check is silently
271
- replaced with a real URL from an actual search/fetch result (never
272
- discarded to a guess), and the entry-point caveat is force-appended to
273
- `reference_description` if the model's own text didn't already disclose
274
- it. `src/index.ts`'s `applyDeepLinkGrounding` is the single place this
275
- happens see its comments for the exact rules, including why a
276
- model-guessed URL-pattern retry (e.g. stripping a path segment after a
277
- fetch fails) is both prompted against and independently rejected by the
278
- `web_fetch` tool itself (`url_not_in_prior_context`).
279
-
280
- **`install_command` is untrusted text.** It's derived from a web search
281
- result the model read, not a verified package registry, and the server
282
- does not execute or validate it. The calling agent is instructed (in the
283
- tool description and system prompt) to always display it to the user for
284
- confirmation before running it, and never execute it automatically or
285
- silently this is expected agent behavior this project depends on, not
286
- something the server enforces. See [SECURITY.md](./SECURITY.md).
458
+
459
+ The `past_decision_signal` field is included only when there is a
460
+ relevant previous decision for the supplied `project_id`.
461
+
462
+ `checklist_source` is always present: `"extracted"` when Pattern derived
463
+ the checklist itself (the default, unchanged behavior), `"provided"` when
464
+ you passed one in via `checklist`.
465
+
466
+ `_meta` is always present. See [Cost](#cost) for what each field means,
467
+ how `breakdown_ms` is measured, and what it means when the ensemble
468
+ triggers.
469
+
470
+ ### Reference links
471
+
472
+ When Pattern recommends a custom build, it may return references from
473
+ Mobbin, Figma Community, or both.
474
+
475
+ The `reference` field can be:
476
+
477
+ - An array when both sources returned useful results.
478
+ - A single object when only one source returned a useful result.
479
+ - `null` when neither source produced a grounded reference.
480
+
481
+ #### Deep links vs. entry points
482
+
483
+ Pattern tells you whether a reference URL points directly to the
484
+ identified screen or flow.
485
+
486
+ `"url_type": "deep_link"` means Pattern verified that the URL points to
487
+ the specific reference.
488
+
489
+ `"url_type": "entry_point"` means the URL is a search or browse page. The
490
+ agent may need to find the specific screen or flow from there.
491
+
492
+ For Mobbin, Pattern fetches the search result page and looks for a more
493
+ specific link to the screen or flow it identified.
494
+
495
+ For Figma Community, URLs containing `/community/file/` are already
496
+ specific to a file and are treated as deep links. Other Figma URLs are
497
+ checked like Mobbin URLs.
498
+
499
+ Pattern never invents a URL. If it cannot verify a specific link, it
500
+ keeps the real search result URL and clearly identifies it as an entry
501
+ point.
502
+
503
+ ### Installation commands are not trusted
504
+
505
+ The `install_command` comes from search results. It is not verified
506
+ against a package registry, and Pattern does not execute it.
507
+
508
+ The calling agent should:
509
+
510
+ 1. Show the command to the user.
511
+ 2. Get confirmation.
512
+ 3. Run it only after confirmation.
513
+
514
+ See [SECURITY.md](./SECURITY.md) for more details.
515
+
516
+ ## Tool: `extract_requirements`
517
+
518
+ Runs only the requirement-extraction step `recommend_component` normally
519
+ does internally, and returns just the checklist -- no search, no scoring,
520
+ no verdict.
521
+
522
+ This is an opt-in, two-call pattern for agents that support tool search or
523
+ code-mode style tool use: call `extract_requirements` first, inspect (or
524
+ hand-edit) the checklist it returns, then pass that checklist to
525
+ `recommend_component`'s optional `checklist` input to score against it
526
+ directly, skipping `recommend_component`'s own internal extraction.
527
+
528
+ The single-call default -- just calling `recommend_component` with no
529
+ `checklist` -- is unchanged and is still the recommended path for most
530
+ callers. Reach for `extract_requirements` when you specifically want to
531
+ catch a misread requirement before Pattern spends its search+score budget,
532
+ not as a routine first step.
533
+
534
+ ### Input
535
+
536
+ ```json
537
+ {
538
+ "component_need": "image gallery for a property listing",
539
+ "domain": "Airbnb-style rental marketplace"
540
+ }
541
+ ```
542
+
543
+ Same fields, same meaning, as `recommend_component`'s `component_need` and
544
+ `domain`. There is no `framework` input here -- extraction is grounded in
545
+ the domain, not the framework, so `framework` doesn't affect the checklist
546
+ in `recommend_component` either.
547
+
548
+ ### Output
549
+
550
+ ```json
551
+ {
552
+ "checklist": ["...", "...", "..."],
553
+ "extraction_confidence": "high | medium | low",
554
+ "_meta": {
555
+ "total_ms": 6798,
556
+ "breakdown_ms": { "extract": 6798, "search": 0, "score": 0 },
557
+ "tokens_used": { "input": 275, "output": 302 },
558
+ "estimated_cost_usd": 0.0036
559
+ }
560
+ }
561
+ ```
562
+
563
+ Typical latency is a few seconds -- one small API call with no tools
564
+ declared, versus `recommend_component`'s full search+score pipeline.
565
+
566
+ **`extraction_confidence` is a placeholder heuristic, not a validated
567
+ signal.** It's currently derived from how specific `component_need` is
568
+ (word count) -- the same "vague category name" problem the rest of this
569
+ README warns about elsewhere. It is not based on any measured correlation
570
+ with actual extraction quality. Treat `"low"` as a prompt to reread your
571
+ `component_need`, not as a calibrated confidence score. This is flagged
572
+ here as a known gap, to revisit once there's real usage data to base a
573
+ better signal on.
574
+
575
+ Trivial primitives (see [Simple primitives](#simple-primitives)) return an
576
+ empty `checklist` with `extraction_confidence: "high"` and no API call, the
577
+ same local skip-list short-circuit `recommend_component` uses.
287
578
 
288
579
  ## Tool: `record_component_decision`
289
580
 
290
- Records a decision the calling agent has actually acted on call it
291
- **after** installing an existing component or finishing a custom build, not
292
- on every `recommend_component` verdict returned. Its only job is appending
293
- one entry to local [per-project decision memory](#per-project-decision-memory);
294
- it runs no judgment logic and makes no Anthropic API call, so it's
295
- effectively free and instant.
581
+ Use this tool after the agent has actually acted on a component
582
+ decision.
583
+
584
+ For example, call it after:
585
+
586
+ - Installing an existing component
587
+ - Completing a custom build
588
+
589
+ Do not call it for every recommendation.
590
+
591
+ The tool only saves the decision. It does not run a judgment or make an
592
+ Anthropic API call.
593
+
594
+ ### Input
296
595
 
297
- **Input:**
298
596
  ```json
299
597
  {
300
598
  "project_id": "my-booking-app",
@@ -305,32 +603,38 @@ effectively free and instant.
305
603
  "timestamp": "2026-08-25T14:32:00.000Z"
306
604
  }
307
605
  ```
308
- - `project_id` (required) — must match the `project_id` you pass to
309
- `recommend_component` for this decision to ever be surfaced there. Use a
310
- stable value, e.g. the project's directory path or name.
311
- - `component_need` (required), `domain` (optional) — same fields as
312
- `recommend_component`'s input; free text, not matched against anything
313
- server-side.
314
- - `action` (required) — `"installed"` or `"custom_built"`.
315
- - `source` (required) — e.g. `"shadcn"`, `"21st.dev"`, or `"custom"` for a
316
- custom build.
317
- - `timestamp` (optional) — ISO 8601; defaults to the current time if
318
- omitted.
319
-
320
- **Output:**
606
+
607
+ - `project_id` is required and should be stable. A project directory
608
+ path or project name works well.
609
+ - `action` must be `"installed"` or `"custom_built"`.
610
+ - `source` can be `"shadcn"`, `"21st.dev"`, `"reui"`, or `"custom"`.
611
+ - `timestamp` is optional. If omitted, Pattern uses the current time.
612
+
613
+ ### Output
614
+
321
615
  ```json
322
- { "status": "recorded", "project_id": "my-booking-app", "entry": { "...": "..." } }
616
+ {
617
+ "status": "recorded",
618
+ "project_id": "my-booking-app",
619
+ "entry": { "..." }
620
+ }
323
621
  ```
324
622
 
325
623
  ## Per-project decision memory
326
624
 
327
- `record_component_decision` appends to a local JSON file, default path
328
- `~/.pattern/memory.json`, overridable via
329
- `PATTERN_MEMORY_PATH` — same override pattern as
330
- [`PATTERN_LOG_PATH`](#local-call-log). It's a flat object keyed by
331
- `project_id`, each value an array of decision entries in the same shape as
332
- `record_component_decision`'s input (minus `project_id` itself, since
333
- that's the key):
625
+ Pattern stores confirmed decisions locally in:
626
+
627
+ ```
628
+ ~/.pattern/memory.json
629
+ ```
630
+
631
+ You can change the location with:
632
+
633
+ ```
634
+ PATTERN_MEMORY_PATH
635
+ ```
636
+
637
+ The file is organized by project:
334
638
 
335
639
  ```json
336
640
  {
@@ -346,198 +650,460 @@ that's the key):
346
650
  }
347
651
  ```
348
652
 
349
- Each project's array is capped at the **50 most recent entries** once a
350
- project hits the cap, the oldest entry is dropped as a new one is added, so
351
- the file stays bounded for a long-lived project without manual cleanup.
352
-
353
- **Only explicitly confirmed decisions are stored here not every verdict
354
- `recommend_component` returns.** The server never writes to this file on
355
- its own; `recommend_component` only ever *reads* it (when `project_id` is
356
- provided) and never writes to it. A verdict you don't act on, or act on
357
- differently than recommended, leaves no trace here unless you call
358
- `record_component_decision` yourself to say what you actually did.
359
-
360
- **This is local-only plaintext**, same caveat pattern as the
361
- [local call log](#local-call-log): nothing in this file is sent anywhere by
362
- this server. `component_need` and `domain` are written here the same way
363
- they're written to `calls.log` — see
364
- [SECURITY.md](./SECURITY.md#what-actually-leaves-your-machine) before
365
- putting anything sensitive in those fields. A write failure (disk full,
366
- read-only filesystem, permissions) surfaces as a tool error on
367
- `record_component_decision` itself, since unlike the best-effort call
368
- log writing the decision *is* that tool's entire job, not a side effect
369
- of it.
370
-
371
- **This does not weaken the no-verdict-caching rule.** Memory only ever adds
372
- past-decision context to the prompt for a fresh judgment pass — see
373
- [No caching, by design](#known-limitations-carried-over-from-validation)
374
- and the `project_id` note under
375
- [Tool: `recommend_component`](#tool-recommend_component). Coverage is
376
- recomputed from a real search every single call, with or without a
377
- `project_id`.
653
+ Each project keeps its 50 most recent decisions. Older entries are
654
+ removed as new ones are added.
655
+
656
+ Only decisions explicitly recorded through `record_component_decision`
657
+ are saved. Pattern does not automatically save recommendations.
658
+
659
+ If an agent ignores or changes a recommendation, nothing is recorded
660
+ unless the agent explicitly calls `record_component_decision` with what
661
+ it actually did.
662
+
663
+ The memory file is local plaintext. Pattern does not send it anywhere.
664
+
665
+ `component_need` and `domain` are stored in this file, so avoid putting
666
+ sensitive information in them. See [SECURITY.md](./SECURITY.md).
667
+
668
+ A failure to write the decision file is returned as an error from
669
+ `record_component_decision`.
670
+
671
+ **No caching, by design.** Project memory does not cache recommendations.
672
+ A previous decision is only additional context for a new judgment. Every
673
+ `recommend_component` call performs a fresh search and recalculates
674
+ coverage. This means Pattern can use past decisions to improve
675
+ consistency without letting stale decisions replace current evidence
676
+ see [Known limitations](#known-limitations) for more.
677
+
678
+ ## Security and privacy
679
+
680
+ Pattern uses the Anthropic API and web search to make its
681
+ recommendations.
682
+
683
+ Local project memory and the local call log are stored on the machine
684
+ running Pattern. They are not sent anywhere by Pattern itself.
685
+
686
+ Review [SECURITY.md](./SECURITY.md) before putting sensitive information
687
+ into fields such as `component_need`, `domain`, or project IDs.
378
688
 
379
689
  ## Cost
380
690
 
381
- A single pass (search score respond) costs roughly $0.06–$0.10 with
382
- Sonnet 5 at current pricing ($2/M input, $10/M output, $0.01 per
383
- web_search call) skip-listed primitives cost $0 since they never reach
384
- the API. Three things keep a single pass down without touching quality:
385
-
386
- - **Prompt caching** on the system block (`cache_control: ephemeral`) —
387
- the instructions are identical every call, so repeated turns and repeated
388
- invocations read from cache instead of re-billing full price.
389
- - **A 2-search budget** for candidate discovery, plus 2 more reserved
390
- specifically for the `custom_build` reference lookups (one each for
391
- Mobbin and Figma Community) so neither has to compete with discovery
392
- for the same cap — shadcn and 21st.dev are searched in the same turn
393
- rather than sequentially, so the growing conversation gets re-sent
394
- fewer times per call.
395
- - **A separate 2-call `web_fetch` budget**, used only for the step-6
396
- deep-link check described above (`max_content_tokens: 15000` caps what
397
- a single category-page fetch can cost). `web_fetch` itself has no
398
- per-call charge beyond the tokens the fetched page adds to context, and
399
- the system prompt explicitly reserves this tool for step 6 only — the
400
- model is instructed not to reach for it during requirement scoring
401
- (step 4), so it doesn't compete with the reference lookups it exists
402
- for.
403
- - **`PATTERN_MODEL` env var** (defaults to `claude-sonnet-5`) lets you
404
- swap in a cheaper model (e.g. Haiku 4.5) without a code change. Before
405
- trusting a cheaper model in production, re-run the 5 validated test cases
406
- from the product brief (price breakdown, cancellation policy, earnings
407
- dashboard, gallery, messaging) and diff the verdicts against Sonnet's
408
- this hasn't been tested, only reasoned about.
691
+ Pattern uses the Anthropic API, so `recommend_component` has a cost.
692
+
693
+ A typical single pass costs about $0.06–$0.10 with Sonnet 5 at current
694
+ pricing. Skip-listed primitives cost $0 because they're handled locally
695
+ and never reach the API.
696
+
697
+ ### The `_meta` field
698
+
699
+ Every `recommend_component` and `extract_requirements` response includes
700
+ an internal `_meta` block reporting what that call actually spent:
701
+
702
+ ```json
703
+ {
704
+ "total_ms": 41516,
705
+ "breakdown_ms": { "extract": 5006, "search": 3114, "score": 33396 },
706
+ "tokens_used": { "input": 8400, "output": 620 },
707
+ "estimated_cost_usd": 0.14
708
+ }
709
+ ```
710
+
711
+ - `total_ms` -- wall-clock time for the call.
712
+ - `tokens_used` -- total input tokens (fresh + cache write + cache read,
713
+ summed) and output tokens, read directly from the API response's own
714
+ usage data.
715
+ - `estimated_cost_usd` -- computed from `tokens_used` at Pattern's
716
+ configured model's current per-token rate (checked against Anthropic's
717
+ pricing, not assumed). This is an estimate: it doesn't account for
718
+ pricing changes Pattern hasn't been updated for, or any account-specific
719
+ discounts.
720
+ - `breakdown_ms` -- how `total_ms` splits across `recommend_component`'s
721
+ three internal phases.
722
+
723
+ **How `breakdown_ms` is measured, and its one real caveat.** The bundled
724
+ call runs extraction, search, and scoring inside a single model turn
725
+ (search/fetch happen server-side, not as separate requests this code
726
+ makes), so there's no natural place for three separate stopwatches.
727
+ Pattern gets a real per-phase split by streaming the response and timing
728
+ content-block boundaries instead: `extract` ends the moment the first
729
+ search call starts, and `search` ends when that first wave of search
730
+ calls and results finishes. This was checked against real traces (not
731
+ assumed) across both `use_existing` and `custom_build` cases before
732
+ shipping, and both boundaries land cleanly and consistently.
733
+
734
+ The one place this needs a caveat: for a `custom_build` verdict, step 6's
735
+ Mobbin/Figma reference search and its deep-link verification fetch happen
736
+ *after* the coverage-scoring reasoning that decided `custom_build` in the
737
+ first place -- so `breakdown_ms.score`, for those cases, covers coverage
738
+ scoring **and** reference-finding **and** the final write-up, not just
739
+ "scoring" in the narrow step-4 sense. It's still a real, measured number;
740
+ it's just a wider bucket for `custom_build` than for `use_existing`. This
741
+ is disclosed here rather than presented as a narrower number than it is.
742
+
743
+ **When the ensemble triggers** (see below), `_meta` reports the sum
744
+ across all reruns that actually happened -- total tokens and cost spent,
745
+ not the wall-clock time you waited. The three ensemble passes run with the
746
+ 2nd and 3rd concurrent, so perceived latency is closer to ~2x one pass,
747
+ not the ~3x `total_ms` will show. Cost and token spend are genuinely
748
+ additive across reruns, which is what `_meta` is reporting there.
749
+
750
+ Three things help keep the cost down without changing the decision process.
751
+
752
+ ### Prompt caching
753
+
754
+ Pattern caches its system instructions using `cache_control: ephemeral`.
755
+
756
+ The instructions are the same across calls, so repeated requests don't
757
+ pay the full input cost for that block.
758
+
759
+ ### Search limits
760
+
761
+ Pattern limits candidate discovery to 3 web searches -- one per source.
762
+
763
+ If a custom build is needed, it reserves 2 additional searches for
764
+ references:
765
+
766
+ - 1 for Mobbin
767
+ - 1 for Figma Community
768
+
769
+ shadcn/ui, 21st.dev, and ReUI are searched in the same turn rather than
770
+ sequentially, which reduces how much conversation context needs to be
771
+ sent repeatedly.
772
+
773
+ ### Reference verification
774
+
775
+ Pattern allows up to 2 `web_fetch` calls, used only to verify reference
776
+ URLs.
777
+
778
+ A fetch can read up to 15,000 content tokens. `web_fetch` has no separate
779
+ per-call fee; the cost comes from the content added to the model's
780
+ context.
781
+
782
+ Pattern does not use `web_fetch` during requirement scoring. It's
783
+ reserved for verifying reference links.
784
+
785
+ ### Choosing a cheaper model
786
+
787
+ You can change the model with:
788
+
789
+ ```
790
+ PATTERN_MODEL
791
+ ```
792
+
793
+ It defaults to:
794
+
795
+ ```
796
+ claude-sonnet-5
797
+ ```
798
+
799
+ You could use a cheaper model such as Haiku 4.5 without changing the code.
800
+
801
+ Before using a cheaper model in production, run the five validation cases
802
+ and compare its results with Sonnet's:
803
+
804
+ - Price breakdown
805
+ - Cancellation policy
806
+ - Earnings dashboard
807
+ - Image gallery
808
+ - Messaging inbox
809
+
810
+ The cheaper model hasn't been validated yet, so these results should be
811
+ treated as an open question rather than an established performance claim.
409
812
 
410
813
  ### Ensemble cost (boundary-risk cases only)
411
814
 
412
- Testing found that a single pass isn't reliable near the verdict
413
- thresholds: with the requirement checklist fixed at exactly 8 items,
414
- coverage can only land on one of 9 discrete values (0, 12.5, 25, 37.5,
415
- 50, 62.5, 75, 87.5, 100%), and the 40%/80% thresholds sit *between* two
416
- of those values (37.5↔50, and 75↔87.5). For met-counts of 3, 4, 6, or 7,
417
- a single item's met/unmet judgment flipping is enough to change the
418
- verdict — and it does, run to run, on identical input.
419
-
420
- To catch that, the server runs a **targeted ensemble**: every pass still
421
- runs once as normal, but if the result lands on one of those four risky
422
- met-counts (`isBoundaryRisk` in `src/index.ts`), it triggers 2 additional
423
- full passes (3 total) and takes the majority verdict. Confidence is
424
- forced to `"low"` on a genuine 2/3 split, regardless of what any
425
- individual pass reported — a real disagreement across identical inputs
426
- is uncertainty the tool should surface, not paper over. Everything else
427
- (0, 1, 2, 5, 8 met — far enough from both thresholds that a 1-item swing
428
- can't flip the verdict) returns the single pass as-is, at 1x cost. An
429
- earlier version also triggered on `reason: "no_candidates_found"`
430
- (a separate source of run-to-run inconsistency); that trigger was removed
431
- after testing showed it never actually changed a verdict in this
432
- session and was pure added cost.
433
-
434
- The output includes an `ensemble` field so callers can see whether this
435
- happened: `{ "triggered": false }` on the fast path, or
436
- `{ "triggered": true, "runs": ["use_existing", "custom_build", "use_existing"], "agreement": "2/3" }`
437
- when it fired.
438
-
439
- **Measured cost, not just worst case:** across the last 5-case × 3-run
440
- test batch (15 outer calls), 8 stayed single-run and 7 triggered the
441
- ensemble (21 calls), for **29 total API calls — a ~1.9x blended average
442
- multiplier**, not the 3x a naive "ensemble triggered" framing implies.
443
- Worst case is still 3x per call when it triggers; most calls don't.
444
-
445
- Ensembling does *not* fully eliminate the underlying variance for the
446
- hardest cases. When a case's true coverage sits close enough to a
447
- threshold that per-item judgment is close to a coin flip, majority-of-3
448
- is a noisy estimator: it protects any single call against one unlucky
449
- draw, but a *different* set of 3 draws on the next invocation can still
450
- land on the other side. One case (image gallery) kept flipping across
451
- outer runs even with the ensemble active, always with a 2/3 split and
452
- `confidence: "low"` — the tool is correctly reporting low confidence on
453
- a genuinely ambiguous case rather than a bug to fix with a bigger N.
815
+ Pattern uses extra model calls only when a result is close enough to a
816
+ decision threshold that a small change in judgment could change the
817
+ verdict.
818
+
819
+ The requirement checklist has eight items, so coverage can only land on
820
+ these values:
821
+
822
+ ```
823
+ 0%
824
+ 12.5%
825
+ 25%
826
+ 37.5%
827
+ 50%
828
+ 62.5%
829
+ 75%
830
+ 87.5%
831
+ 100%
832
+ ```
833
+
834
+ The decision thresholds are 40% and 80%.
835
+
836
+ That means results at 37.5%, 50%, 75%, and 87.5% are the cases where
837
+ changing the judgment on one requirement can flip the verdict.
838
+
839
+ For those cases, Pattern runs the full judgment three times and takes
840
+ the majority result.
841
+
842
+ For example:
843
+
844
+ ```json
845
+ {
846
+ "ensemble": {
847
+ "triggered": true,
848
+ "runs": ["use_existing", "custom_build", "use_existing"],
849
+ "agreement": "2/3"
850
+ }
851
+ }
852
+ ```
853
+
854
+ If all three runs agree, the majority verdict is returned normally.
855
+
856
+ If they split 2/3, Pattern sets confidence to `"low"`. The disagreement
857
+ is surfaced rather than hidden.
858
+
859
+ Results at 0, 12.5, 25, 62.5, and 100% stay single-pass because one
860
+ changed requirement can't move them across either threshold.
861
+
862
+ ### Measured ensemble cost
863
+
864
+ The ensemble doesn't mean every call costs 3x.
865
+
866
+ In the latest five-case validation, Pattern made 15 outer calls:
867
+
868
+ - 8 stayed single-pass
869
+ - 7 triggered the ensemble
870
+ - 21 model passes were used for those 7 ensemble calls
871
+ - 29 total model calls across the test
872
+
873
+ That works out to about a 1.9x average multiplier across that test set.
874
+
875
+ The worst case is still 3x for an individual call when the ensemble is
876
+ triggered.
877
+
878
+ ### What the ensemble can and cannot solve
879
+
880
+ The ensemble reduces the chance that one unlucky model judgment
881
+ determines the result. It doesn't eliminate uncertainty.
882
+
883
+ If the underlying evidence is genuinely ambiguous, three runs can still
884
+ disagree.
885
+
886
+ For example, the image-gallery validation case continued to flip between
887
+ outer runs. When that happened, the ensemble consistently reported a 2/3
888
+ split with `confidence: "low"`.
889
+
890
+ That's expected behavior: the tool is exposing uncertainty instead of
891
+ presenting an ambiguous result as certain.
454
892
 
455
893
  ### Session call cap
456
894
 
457
- The server caps itself at **40 calls per process lifetime** by default,
458
- configurable via `PATTERN_SESSION_CAP`. This protects against a
459
- *buggy calling agent* looping on the tool — a retry loop, a stuck agent
460
- re-calling the same need repeatedly — not against normal project usage.
461
- The number is grounded in real usage, not arbitrary: a full pass through
462
- a realistic ~25-component project (scaled up from this project's own
463
- 5-case Airbnb-style validation list) costs 25 calls, so 40 leaves
464
- headroom for iteration on top of that without being so high it fails to
465
- catch an actual runaway loop before it gets expensive. Skip-listed
466
- primitives don't count toward the cap, since they never reach the API.
467
- The counter is in-memory and resets when the server process restarts —
468
- raise the cap via the env var if 40 is genuinely too low for your
469
- project, don't just restart repeatedly to reset it.
895
+ Pattern limits the number of API calls to 40 per server process by
896
+ default.
897
+
898
+ You can change this with:
899
+
900
+ ```
901
+ PATTERN_SESSION_CAP
902
+ ```
903
+
904
+ The cap protects against runaway agents, such as an agent stuck in a
905
+ retry loop or repeatedly asking for the same recommendation.
906
+
907
+ The 40-call default is based on the project's validation work. A
908
+ realistic project with roughly 25 components would use about 25 calls
909
+ for a full pass, leaving room for iteration.
910
+
911
+ Skip-listed primitives don't count because they never reach the API.
912
+
913
+ The counter lives in memory and resets when the server restarts.
914
+
915
+ If 40 calls is too low for your project, increase `PATTERN_SESSION_CAP`
916
+ rather than repeatedly restarting the server.
470
917
 
471
918
  ## Local call log
472
919
 
473
- Every call that reaches the API (skip-list hits excluded, same exclusion
474
- as the session cap) appends one JSON line to a local log file — default
475
- path `~/.pattern/calls.log`, overridable via
476
- `PATTERN_LOG_PATH`. This is **local-only**: nothing here is sent
477
- anywhere by this server, it's purely for your own debugging/usage
478
- visibility.
920
+ Every API call is recorded in a local log.
921
+
922
+ By default:
923
+
924
+ ```
925
+ ~/.pattern/calls.log
926
+ ```
927
+
928
+ You can change the location with:
929
+
930
+ ```
931
+ PATTERN_LOG_PATH
932
+ ```
933
+
934
+ The log is local. Pattern does not send it anywhere.
935
+
936
+ Each API call adds one JSON line, for example:
479
937
 
480
- Each line looks like:
481
938
  ```json
482
- {"timestamp":"2026-08-24T21:12:43.882Z","component_need":"cancellation policy display","domain":"Airbnb-style rental marketplace","framework":"React + Tailwind","verdict":"custom_build","confidence":"high","reason":"scored","coverage":"2/8 (25%)","ensemble_triggered":false,"reference_sources_grounded":["Mobbin","Figma Community"]}
483
- ```
484
- `ensemble_agreement` is only present when `ensemble_triggered` is `true`.
485
- `reference_sources_grounded` is only present on `custom_build` verdicts,
486
- and only lists sources (`"Mobbin"`, `"Figma Community"`) that actually
487
- grounded — matches whatever `recommendation.reference` ended up being
488
- after grounding is enforced (see the
489
- [Tool](#tool-recommend_component) section above for the full shape
490
- rules).
491
-
492
- **Deliberately excluded**: full `requirements_checked` evidence text, and
493
- the API key — never written here. **Included in plaintext**:
494
- `component_need` and `domain` — see
495
- [SECURITY.md](./SECURITY.md#what-actually-leaves-your-machine) before
496
- putting anything sensitive in those fields. The log directory is created
497
- automatically if it doesn't exist, and a write failure (disk full,
498
- read-only filesystem, permissions) is caught and reported to stderr —
499
- it never breaks the tool call itself.
500
-
501
- **Reviewing a log file** including a tester's, if they send you
502
- theirs (there's no automatic collection; this project doesn't phone
503
- home): run `node summarize-log.js [path]`, defaulting to the same
504
- location the server itself uses. It prints a verdict/confidence/reason
505
- breakdown, ensemble trigger and agreement rates, reference-source
506
- grounding rates on `custom_build` verdicts, and flags any
507
- `component_need` called more than once a signal worth checking
508
- against the [session cap](#session-call-cap) if you see it.
509
-
510
- ## Known limitations (carried over from validation)
511
-
512
- - **Evidence judgment varies run to run, independent of search results.**
513
- Validation traced a real case where two runs found the exact same named
514
- candidate components via the exact same search queries, but the model
515
- judged the same evidence differently e.g. reading one candidate's
516
- "Export" action as present in one run and absent in another, for the
517
- identical component. This isn't a search-consistency or code bug; it's
518
- inherent to how the model reads natural-language evidence, and it's what
519
- the boundary-risk ensemble exists to catch and disclose (as a 2/3
520
- `agreement` split) rather than eliminate. If you see a verdict flip
521
- between your own runs on the same input, this is almost certainly why.
522
- - **No caching, by design.** Every call re-searches and re-scores from
523
- scratch. A `custom_build` verdict can go stale as libraries ship new
524
- components (validated: shadcn's June 2026 chat primitives turned a likely
525
- custom-build messaging component into a near-perfect match). If you add
526
- caching at the calling-agent layer, keep it session-scoped only — never
527
- persist a verdict across sessions or builds. This still holds with
528
- [per-project decision memory](#per-project-decision-memory) in the
529
- picture: memory only ever adds context to the prompt for a fresh
530
- judgment pass, it never substitutes for one — a `recommend_component`
531
- call with a `project_id` still always re-searches and re-scores.
532
- - **Skip-list is a starting point, not validated against real usage yet.**
533
- Log every call and whether it hit the skip-list; watch for agents calling
534
- the tool anyway on skip-listed items (list too narrow) or shipping generic
535
- UI for something that should've been skipped (list missing an entry).
536
- - **Not testable end-to-end in a fully sandboxed environment.** This server
537
- needs outbound network access to `api.anthropic.com` plus whatever the
538
- model's web_search tool reaches it won't run somewhere that blocks
539
- general internet access.
540
- - **Requirement extraction and coverage scoring are judgment calls made by
541
- the model**, not deterministic lookups, even with the ensemble and
542
- server-side recount in place. Spot-check early outputs against real
543
- components before trusting the pipeline unattended.
939
+ {
940
+ "timestamp": "2026-08-24T21:12:43.882Z",
941
+ "component_need": "cancellation policy display",
942
+ "domain": "Airbnb-style rental marketplace",
943
+ "framework": "React + Tailwind",
944
+ "verdict": "custom_build",
945
+ "confidence": "high",
946
+ "reason": "scored",
947
+ "coverage": "2/8 (25%)",
948
+ "ensemble_triggered": false,
949
+ "reference_sources_grounded": ["Mobbin", "Figma Community"],
950
+ "checklist_source": "extracted",
951
+ "total_ms": 44834,
952
+ "estimated_cost_usd": 0.15
953
+ }
954
+ ```
955
+
956
+ Additional fields appear when relevant:
957
+
958
+ - `ensemble_agreement` appears when the ensemble runs.
959
+ - `reference_sources_grounded` appears for `custom_build` results and
960
+ lists only sources that produced a grounded reference.
961
+
962
+ `checklist_source`, `total_ms`, and `estimated_cost_usd` mirror the
963
+ call's `_meta` block (see [Cost](#cost)) -- `total_ms` and
964
+ `estimated_cost_usd` are the same aggregated-across-reruns numbers when
965
+ the ensemble triggers, not per-pass figures.
966
+
967
+ The log deliberately does not contain:
968
+
969
+ - The full `requirements_checked` evidence
970
+ - Your Anthropic API key
971
+
972
+ It does contain `component_need` and `domain`, so avoid putting sensitive
973
+ information in those fields. See [SECURITY.md](./SECURITY.md).
974
+
975
+ The log directory is created automatically.
976
+
977
+ If Pattern cannot write to the log because of permissions, a read-only
978
+ filesystem, or a full disk, it reports the problem to stderr but does not
979
+ fail the tool call.
980
+
981
+ ### Review a log
982
+
983
+ You can summarize a log with:
984
+
985
+ ```
986
+ node summarize-log.js [path]
987
+ ```
988
+
989
+ If no path is provided, it uses the same default location as the server.
990
+
991
+ The summary includes:
992
+
993
+ - Verdict and confidence breakdown
994
+ - Reason breakdown
995
+ - Ensemble trigger and agreement rates
996
+ - Reference-source grounding rates for custom builds
997
+ - Component needs that were requested more than once
998
+
999
+ Repeated component needs can be useful to investigate alongside the
1000
+ [session call cap](#session-call-cap).
1001
+
1002
+ ## Known limitations
1003
+
1004
+ ### Model judgment can vary
1005
+
1006
+ Pattern's search results can stay the same while the model's
1007
+ interpretation of those results changes between runs.
1008
+
1009
+ Validation found cases where two runs found the same named components
1010
+ using the same search queries but judged the same evidence differently.
1011
+
1012
+ For example, the model interpreted an Export action as present in one
1013
+ run and absent in another.
1014
+
1015
+ This is a limitation of model-based evidence judgment, not necessarily a
1016
+ search or code problem.
1017
+
1018
+ The boundary-risk ensemble exists to detect and surface this uncertainty.
1019
+
1020
+ ### A staged pipeline was evaluated and not adopted
1021
+
1022
+ To address the variance above, an alternative architecture was built and
1023
+ tested: splitting the single bundled judgment call into separate stages
1024
+ (extract requirements, search evidence, score coverage), on the theory
1025
+ that isolating each step would make results more consistent and easier
1026
+ to diagnose.
1027
+
1028
+ A pilot comparison (5 cases, 3 repeated runs per case, per
1029
+ architecture) found no consistent benefit. The staged pipeline improved
1030
+ consistency on one boundary-risk case but was less consistent than the
1031
+ bundled pipeline on another, including one run that failed outright.
1032
+ Net accuracy against hand-graded gold answers was statistically
1033
+ indistinguishable between the two architectures, and the staged
1034
+ pipeline cost roughly **2x** the bundled pipeline's call volume across
1035
+ the board, not only on the boundary-risk cases it was expected to help
1036
+ most.
1037
+
1038
+ Pattern ships the bundled pipeline. The staged implementation remains
1039
+ in the repo (`src/staged/`) as an evaluated, unshipped experiment, not
1040
+ a supported alternative.
1041
+
1042
+ **`extract_requirements` is not a revival of this.** It's a standalone
1043
+ tool for inspecting the extraction step's output before an agent commits
1044
+ to `recommend_component`'s search+score budget -- an opt-in visibility
1045
+ tool, not an internal re-architecture. `recommend_component`'s own
1046
+ pipeline is still fully bundled; nothing about this evaluation changed.
1047
+
1048
+ ### No caching, by design
1049
+
1050
+ Every recommendation searches and scores again.
1051
+
1052
+ This means a recommendation can change as component libraries change.
1053
+ For example, a later shadcn/ui release can introduce a component that
1054
+ changes a previous `custom_build` result.
1055
+
1056
+ Do not persist a recommendation across sessions or builds at the
1057
+ calling-agent layer.
1058
+
1059
+ If you add caching, keep it session-scoped.
1060
+
1061
+ [Project decision memory](#per-project-decision-memory) does not change
1062
+ this. It provides context from previous decisions, but every
1063
+ `recommend_component` call still performs a fresh search and scoring
1064
+ pass.
1065
+
1066
+ ### The skip-list is still evolving
1067
+
1068
+ The primitive skip-list is a starting point and has not yet been
1069
+ validated against broad real-world usage.
1070
+
1071
+ Watch for two failure modes:
1072
+
1073
+ - Agents calling Pattern for things that should have been skipped.
1074
+ - Agents building generic UI for something that should have been on the
1075
+ skip-list.
1076
+
1077
+ The local call log can help identify both patterns.
1078
+
1079
+ ### Pattern needs internet access
1080
+
1081
+ Pattern requires outbound access to:
1082
+
1083
+ ```
1084
+ api.anthropic.com
1085
+ ```
1086
+
1087
+ It also depends on whatever external sites the model's `web_search` tool
1088
+ can reach.
1089
+
1090
+ It will not work in an environment that blocks general outbound internet
1091
+ access.
1092
+
1093
+ ### Requirements and coverage are judgment calls
1094
+
1095
+ Requirement extraction and evidence scoring are performed by the model.
1096
+
1097
+ Pattern adds safeguards such as:
1098
+
1099
+ - Structured requirements
1100
+ - Server-side coverage recalculation
1101
+ - Decision thresholds
1102
+ - Boundary-risk ensembling
1103
+ - Grounding checks for reference URLs
1104
+
1105
+ But the underlying interpretation of whether evidence satisfies a
1106
+ requirement is still model judgment.
1107
+
1108
+ When introducing Pattern into a new workflow, spot-check early results
1109
+ against the actual components before relying on it unattended.