pattern-mcp 0.1.1 → 0.3.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,185 @@
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 checks a UI component need against real,
9
+ current evidence before your agent commits to it, so a wrong decision
10
+ gets caught before it's built, not after.
11
+
12
+ [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)
13
+
14
+ ## Install
15
+
16
+ ```bash
17
+ npm install pattern-mcp
18
+ ```
19
+
20
+ See [Quick Start](#quick-start) below to add your Anthropic API key and connect
21
+ Pattern to your MCP client.
22
+
23
+ ## What Pattern Does
24
+
25
+ Instead of returning a list of search results, Pattern looks at what you
26
+ need, checks real components against that need, and tells the agent
27
+ whether to:
28
+
29
+ - **Use an existing component** from shadcn/ui, 21st.dev, or ReUI
30
+ - **Build a custom component**, using a real product reference from
31
+ Mobbin and/or Figma Community
32
+
33
+ Pattern is designed for agents to use **while they are building**.
34
+
35
+ It exposes four tools:
36
+
37
+ - `recommend_component` — evaluates a UI component need and returns a
38
+ structured recommendation.
39
+ - `extract_requirements` — runs just the requirement-extraction step on
40
+ its own, so you can inspect or hand-edit the checklist before
41
+ `recommend_component` spends its search+score budget on it.
42
+ - `record_component_decision` — records what the agent actually did so
43
+ future recommendations in the same project can take that decision into
44
+ account.
45
+ - `read_ledger` — lists past `recommend_component` judgments for a
46
+ `project_id`, including any that were served from the ledger cache (see
47
+ [Per-project judgment ledger](#per-project-judgment-ledger)).
19
48
 
20
49
  ## How it works
21
50
 
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
51
+ ![How it works](docs/images/how-it-works.png)
52
+
53
+ For each `recommend_component` call, Pattern:
54
+
55
+ 1. Checks whether the need is a simple primitive that doesn't require a
56
+ search.
57
+ 2. Turns the request into a set of specific requirements, unless a
58
+ checklist was already supplied (see [`checklist`](#checklist)).
59
+ 3. Searches for matching shadcn/ui, 21st.dev, and ReUI components.
60
+ 4. Checks each candidate against the requirements using evidence from the
61
+ actual component.
62
+ 5. Calculates how much of the requirement is covered.
63
+ 6. Decides whether to use an existing component or build a custom one.
64
+ 7. If a custom build is needed, searches Mobbin and Figma Community for
65
+ real product examples.
66
+ 8. Returns the result as structured JSON the calling agent can act on.
67
+
68
+ Coverage is calculated by the server from the individual requirements it
69
+ checked. It does not simply trust the percentage returned by the model.
70
+
71
+ A result can also be:
72
+
73
+ - `use_existing`
74
+ - `custom_build`
75
+ - `no_candidates_found`
76
+ - `skip_list`
77
+ - `ledger_cache_hit` served from a recent, matching prior judgment
78
+ instead of a fresh search+score (see
79
+ [Per-project judgment ledger](#per-project-judgment-ledger)).
80
+
81
+ `no_candidates_found` is kept separate from a low-coverage result. Not
82
+ finding a candidate is different from finding candidates that don't cover
83
+ the requirements.
84
+
85
+ If `project_id` is supplied, Pattern also checks for past confirmed
86
+ decisions on that project and factors them in as a consistency signal —
87
+ never a rule that overrides a genuinely better match found in the current
88
+ search. Separately, `project_id` also enables the judgment ledger: a
89
+ high-confidence prior judgment matching this exact
90
+ component_need/domain/framework/existing_stack, recorded recently enough,
91
+ can be served directly (`ledger_cache_hit`) instead of running a fresh
92
+ search+score. This is the one deliberate exception to "every recommendation
93
+ searches and scores again" — see
94
+ [Per-project judgment ledger](#per-project-judgment-ledger) for the exact
95
+ rules and why it's safe.
96
+
97
+ Every result includes `computed_at`, because coverage is a snapshot of the
98
+ search at that point in time, not a permanent fact. Every result also
99
+ includes `_meta` — the timing and token cost of that specific call (see
100
+ [Cost](#cost)).
101
+
102
+ ### Boundary-risk checks
103
+
104
+ The same evidence can sometimes be judged slightly differently between
105
+ model runs. When a result is close enough to a decision threshold that it
106
+ could change the verdict, Pattern automatically runs the judgment two more
107
+ times and uses the majority result.
108
+
109
+ If the three runs disagree, Pattern returns:
110
+
111
+ ```json
112
+ {
113
+ "confidence": "low",
114
+ "ensemble": {
115
+ "triggered": true,
116
+ "runs": ["use_existing", "custom_build", "use_existing"],
117
+ "agreement": "2/3"
118
+ }
119
+ }
120
+ ```
121
+
122
+ Results that are clearly inside a threshold don't trigger extra runs — see
123
+ [Cost](#cost) below for the measured impact.
124
+
125
+ ### Simple primitives
126
+
127
+ These are handled locally without an API call:
128
+
129
+ | Primitive | Use it for |
130
+ | --- | --- |
131
+ | `button` | A clickable action trigger |
132
+ | `input` | A single-line text entry field |
133
+ | `checkbox` | A binary on/off toggle |
134
+ | `label` | A caption for a field or control |
135
+ | `badge` | A small status or count indicator |
136
+ | `spinner` | An indeterminate loading indicator |
137
+ | `tooltip` | A contextual hover/focus hint |
138
+ | `avatar` | A user or entity image, or initials |
139
+ | `icon` | A single glyph or symbol |
140
+
141
+ This keeps trivial requests fast and avoids unnecessary API usage.
142
+
143
+ ### What powers the search
144
+
145
+ Pattern does not scrape shadcn/ui, 21st.dev, ReUI, Mobbin, or Figma Community
146
+ itself.
147
+
148
+ Each tool call makes one or more requests to the Anthropic Messages API,
149
+ using `claude-sonnet-5` by default. The server enables Anthropic's
150
+ `web_search` tool and provides a system prompt that defines the full
151
+ decision process.
152
+
153
+ That process includes:
154
+
155
+ - Skip-list checks
156
+ - Requirement extraction
157
+ - Component search
158
+ - Evidence-based coverage scoring
159
+ - Decision thresholds
160
+ - Mobbin and Figma Community reference searches when a custom build is
161
+ needed
162
+
163
+ Figma Community does not require a Figma API key. Pattern uses the same
164
+ web search mechanism for Figma Community as it does for the other sources.
165
+
166
+ The model returns structured JSON. Pattern then applies important checks
167
+ itself, including recalculating coverage and applying the decision
168
+ threshold.
169
+
170
+ ## Quick Start
171
+
172
+ ### 1. Install
173
+
174
+ ```bash
175
+ npm install pattern-mcp
176
+ ```
177
+
178
+ This installs the `pattern-mcp` command via `npx` (or your project's
179
+ local `node_modules/.bin`), used in the client configs below.
180
+
181
+ <details>
182
+ <summary>Build from source instead</summary>
52
183
 
53
184
  ```bash
54
185
  git clone <this repo>
@@ -57,104 +188,172 @@ npm install
57
188
  npm run build
58
189
  ```
59
190
 
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
191
+ Use `node /absolute/path/to/pattern-mcp/dist/index.js` as the server
192
+ command in place of `npx pattern-mcp` in the examples below.
193
+
194
+ </details>
195
+
196
+ ### 2. Add your Anthropic API key
197
+
198
+ Pattern requires:
199
+
200
+ ```
201
+ ANTHROPIC_API_KEY
202
+ ```
203
+
204
+ The API account associated with this key pays for the requests Pattern
205
+ makes (see [Cost](#cost) below).
206
+
207
+ You get the key from the Anthropic Console under Settings → API Keys.
208
+ API billing is separate from Claude.ai or Claude Code subscriptions. A
209
+ Claude Pro or Max subscription does not include API usage.
210
+
211
+ ### Connect Pattern to your MCP client
212
+
213
+ Pattern is a standard MCP server, so it works with MCP-compatible
214
+ clients.
215
+
216
+ The server command is:
217
+
218
+ ```
219
+ npx pattern-mcp
220
+ ```
221
+
222
+ #### Claude Code
223
+
224
+ You can add Pattern to your project's `.mcp.json` or register it with the
225
+ CLI.
226
+
227
+ For the current project:
228
+
229
+ ```bash
230
+ claude mcp add pattern \
231
+ -e ANTHROPIC_API_KEY=sk-ant-... \
232
+ -- npx pattern-mcp
233
+ ```
234
+
235
+ This uses the default local scope, so the server is available to the
236
+ current project.
237
+
238
+ To make Pattern available across your projects:
239
+
240
+ ```bash
241
+ claude mcp add pattern \
242
+ -e ANTHROPIC_API_KEY=sk-ant-... \
243
+ --scope user \
244
+ -- npx pattern-mcp
245
+ ```
246
+
247
+ **Important:** put `-e`/`--env` and `--scope` before the `--`. Everything
248
+ after `--` is treated as the command and its arguments.
249
+
250
+ Check the connection with:
251
+
252
+ ```bash
253
+ claude mcp list
254
+ ```
255
+
256
+ You should see Pattern with a `✔ Connected` status.
257
+
258
+ `claude mcp add` stores the configuration in `~/.claude.json`. Avoid
259
+ `claude mcp get pattern` when possible because it can print your API key
260
+ in plaintext.
261
+
262
+ #### Cursor
263
+
264
+ Add Pattern to:
265
+
266
+ ```
267
+ .cursor/mcp.json
268
+ ```
269
+
270
+ #### Codex CLI
271
+
272
+ Pattern can be configured globally in:
273
+
274
+ ```
275
+ ~/.codex/config.toml
276
+ ```
277
+
278
+ or at the project level in:
279
+
280
+ ```
281
+ .codex/config.json
282
+ ```
283
+
284
+ Use the MCP configuration format supported by your Codex CLI version.
285
+
286
+ #### Claude Desktop
287
+
288
+ Add Pattern through Claude Desktop's MCP settings.
289
+
290
+ The configuration looks like:
109
291
 
110
292
  ```json
111
293
  {
112
294
  "mcpServers": {
113
295
  "pattern": {
114
- "command": "node",
115
- "args": ["/absolute/path/to/pattern-mcp/dist/index.js"],
116
- "env": { "ANTHROPIC_API_KEY": "sk-ant-..." }
296
+ "command": "npx",
297
+ "args": ["pattern-mcp"],
298
+ "env": {
299
+ "ANTHROPIC_API_KEY": "sk-ant-..."
300
+ }
117
301
  }
118
302
  }
119
303
  }
120
304
  ```
121
305
 
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.
306
+ Restart your MCP client after adding Pattern.
307
+
308
+ Then ask your agent to list its available MCP tools and look for:
309
+
310
+ ```
311
+ recommend_component
312
+ ```
126
313
 
127
314
  ## Try it
128
315
 
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.
316
+ Give your agent a specific UI need, for example:
317
+
318
+ > Use recommend_component to find me a UI component for a price breakdown
319
+ > showing nightly rate, cleaning fee, service fee, and taxes. I'm building
320
+ > an Airbnb-style booking checkout in React with Tailwind.
321
+
322
+ The agent should use the result to make the next decision:
323
+
324
+ - Install or use the recommended component, or
325
+ - Start a custom build using the returned requirements and product
326
+ references.
327
+
328
+ Pattern returns useful descriptions for both paths.
329
+
330
+ - For an existing component, `component_description` explains what the
331
+ component does and looks like before the agent installs it.
332
+ - For a custom build, `reference_description` explains what each Mobbin
333
+ or Figma Community reference actually shows.
334
+
335
+ These descriptions are grounded in what Pattern found during the search
336
+ rather than generic descriptions.
337
+
338
+ ## Validation examples
339
+
340
+ Pattern's validation suite uses five UI needs from an Airbnb-style rental
341
+ marketplace:
342
+
343
+ - Price breakdown with fees and taxes
344
+ - Cancellation policy display
345
+ - Host earnings dashboard
346
+ - Property image gallery
347
+ - Host-guest messaging inbox
348
+
349
+ Together, these cover different outcomes, including clear matches,
350
+ false-positive-prone searches, no candidates, and decisions close to the
351
+ threshold.
154
352
 
155
353
  ## Tool: `recommend_component`
156
354
 
157
- **Input:**
355
+ ### Input
356
+
158
357
  ```json
159
358
  {
160
359
  "component_need": "price breakdown with fees and taxes",
@@ -164,137 +363,250 @@ messaging inbox — all in the same Airbnb-style rental marketplace domain.
164
363
  "project_id": "my-booking-app"
165
364
  }
166
365
  ```
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:
366
+
367
+ `component_need` should describe the actual UI you need, not just a
368
+ category.
369
+
370
+ Good: `price breakdown with fees and taxes`
371
+ Too vague: `pricing`
372
+
373
+ Vague requests can produce misleading matches. For example, a generic
374
+ SaaS pricing table may look like a match for "pricing" even though it
375
+ doesn't work for a booking checkout.
376
+
377
+ #### `project_id`
378
+
379
+ `project_id` is optional.
380
+
381
+ When provided, Pattern can use decisions previously recorded for the
382
+ same project (see [Per-project decision memory](#per-project-decision-memory))
383
+ as a consistency signal.
384
+
385
+ A previous decision can help the model stay consistent with similar UI
386
+ decisions, but it cannot override a better match found in the current
387
+ search.
388
+
389
+ Pattern still searches and scores every request from scratch. Past
390
+ decisions never cause a search to be skipped.
391
+
392
+ If you leave out `project_id`, Pattern does not use project memory.
393
+
394
+ #### `checklist`
395
+
396
+ `checklist` is optional -- an array of requirement strings.
397
+
398
+ When provided, `recommend_component` skips its own internal requirement
399
+ extraction entirely and scores coverage against exactly the items you
400
+ passed, instead of extracting its own checklist. Search and scoring still
401
+ run fresh every call; only the extraction step is skipped.
402
+
403
+ This is meant to be used together with [`extract_requirements`](#tool-extract_requirements):
404
+ call `extract_requirements` first, inspect (or hand-edit) the checklist it
405
+ returns, then pass that checklist here. That gives you a chance to catch a
406
+ misread requirement before Pattern spends its search+score budget.
407
+
408
+ Leave `checklist` out to keep today's default behavior: `recommend_component`
409
+ extracts its own checklist internally, exactly as before this option
410
+ existed.
411
+
412
+ **Is the checklist actually skipped, not just re-derived?** Checked, not
413
+ assumed. `breakdown_ms.extract` for a `checklist`-provided call is smaller
414
+ than the default path's, but not near-zero -- which raised the question of
415
+ whether the model is still doing some of the extraction work in that
416
+ window rather than treating the checklist as fixed input. Reading the
417
+ model's actual reasoning (via `thinking` with `display: "summarized"`,
418
+ 5 runs: 3 with `checklist` provided, 2 default) answered it: the
419
+ `checklist`-provided runs' pre-search reasoning was a short, generic
420
+ "search shadcn/ui and 21st.dev" thought with no mention of the checklist's
421
+ content, e.g. *"I should look for existing image gallery component options
422
+ on shadcn/ui and 21st.dev"* -- consistently ~3-4 seconds. The default
423
+ runs' reasoning, by contrast, explicitly enumerated and derived the
424
+ checklist items (*"...mapping out the checklist: a photo grid with hero
425
+ and thumbnails... a full-screen lightbox with next/prev navigation,
426
+ keyboard support..."*) and took roughly 2x longer (~7-8 seconds). The
427
+ remaining time in the `checklist`-provided path is baseline model latency
428
+ before it decides to search, not re-extraction -- it doesn't scale with or
429
+ reference the checklist's content.
430
+
431
+ ### Output
432
+
186
433
  ```json
187
434
  {
188
435
  "verdict": "use_existing | custom_build",
189
436
  "confidence": "high | medium | low",
190
437
  "reason": "scored | no_candidates_found | skip_list",
191
438
  "computed_at": "2026-08-23",
192
- "requirements_checked": [ { "requirement": "...", "met": true, "evidence": "..." } ],
439
+ "requirements_checked": [
440
+ {
441
+ "requirement": "...",
442
+ "met": true,
443
+ "evidence": "..."
444
+ }
445
+ ],
193
446
  "coverage": "5/7 (71%)",
194
447
  "recommendation": {
195
- "source": "21st.dev | shadcn | null",
448
+ "source": "21st.dev | shadcn | reui | null",
196
449
  "install_command": "string | null",
197
- "component_description": "string (use_existing only) | null",
450
+ "component_description": "string | null",
198
451
  "reference": {
199
452
  "source": "Mobbin | Figma Community",
200
453
  "url": "...",
201
- "flow_name": "... (Mobbin only)",
202
- "file_name": "... (Figma Community only)",
454
+ "flow_name": "...",
455
+ "file_name": "...",
203
456
  "reference_description": "...",
204
457
  "url_type": "deep_link | entry_point"
205
458
  }
206
459
  },
207
- "ensemble": { "triggered": false },
208
- "past_decision_signal": { "considered": true, "note": "..." }
460
+ "ensemble": {
461
+ "triggered": false
462
+ },
463
+ "checklist_source": "extracted | provided",
464
+ "_meta": {
465
+ "total_ms": 41516,
466
+ "breakdown_ms": { "extract": 5006, "search": 3114, "score": 33396 },
467
+ "tokens_used": { "input": 8400, "output": 620 },
468
+ "estimated_cost_usd": 0.14
469
+ }
209
470
  }
210
471
  ```
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).
472
+
473
+ The `past_decision_signal` field is included only when there is a
474
+ relevant previous decision for the supplied `project_id`.
475
+
476
+ `checklist_source` is always present: `"extracted"` when Pattern derived
477
+ the checklist itself (the default, unchanged behavior), `"provided"` when
478
+ you passed one in via `checklist`.
479
+
480
+ `_meta` is always present. See [Cost](#cost) for what each field means,
481
+ how `breakdown_ms` is measured, and what it means when the ensemble
482
+ triggers.
483
+
484
+ ### Reference links
485
+
486
+ When Pattern recommends a custom build, it may return references from
487
+ Mobbin, Figma Community, or both.
488
+
489
+ The `reference` field can be:
490
+
491
+ - An array when both sources returned useful results.
492
+ - A single object when only one source returned a useful result.
493
+ - `null` when neither source produced a grounded reference.
494
+
495
+ #### Deep links vs. entry points
496
+
497
+ Pattern tells you whether a reference URL points directly to the
498
+ identified screen or flow.
499
+
500
+ `"url_type": "deep_link"` means Pattern verified that the URL points to
501
+ the specific reference.
502
+
503
+ `"url_type": "entry_point"` means the URL is a search or browse page. The
504
+ agent may need to find the specific screen or flow from there.
505
+
506
+ For Mobbin, Pattern fetches the search result page and looks for a more
507
+ specific link to the screen or flow it identified.
508
+
509
+ For Figma Community, URLs containing `/community/file/` are already
510
+ specific to a file and are treated as deep links. Other Figma URLs are
511
+ checked like Mobbin URLs.
512
+
513
+ Pattern never invents a URL. If it cannot verify a specific link, it
514
+ keeps the real search result URL and clearly identifies it as an entry
515
+ point.
516
+
517
+ ### Installation commands are not trusted
518
+
519
+ The `install_command` comes from search results. It is not verified
520
+ against a package registry, and Pattern does not execute it.
521
+
522
+ The calling agent should:
523
+
524
+ 1. Show the command to the user.
525
+ 2. Get confirmation.
526
+ 3. Run it only after confirmation.
527
+
528
+ See [SECURITY.md](./SECURITY.md) for more details.
529
+
530
+ ## Tool: `extract_requirements`
531
+
532
+ Runs only the requirement-extraction step `recommend_component` normally
533
+ does internally, and returns just the checklist -- no search, no scoring,
534
+ no verdict.
535
+
536
+ This is an opt-in, two-call pattern for agents that support tool search or
537
+ code-mode style tool use: call `extract_requirements` first, inspect (or
538
+ hand-edit) the checklist it returns, then pass that checklist to
539
+ `recommend_component`'s optional `checklist` input to score against it
540
+ directly, skipping `recommend_component`'s own internal extraction.
541
+
542
+ The single-call default -- just calling `recommend_component` with no
543
+ `checklist` -- is unchanged and is still the recommended path for most
544
+ callers. Reach for `extract_requirements` when you specifically want to
545
+ catch a misread requirement before Pattern spends its search+score budget,
546
+ not as a routine first step.
547
+
548
+ ### Input
549
+
550
+ ```json
551
+ {
552
+ "component_need": "image gallery for a property listing",
553
+ "domain": "Airbnb-style rental marketplace"
554
+ }
555
+ ```
556
+
557
+ Same fields, same meaning, as `recommend_component`'s `component_need` and
558
+ `domain`. There is no `framework` input here -- extraction is grounded in
559
+ the domain, not the framework, so `framework` doesn't affect the checklist
560
+ in `recommend_component` either.
561
+
562
+ ### Output
563
+
564
+ ```json
565
+ {
566
+ "checklist": ["...", "...", "..."],
567
+ "extraction_confidence": "high | medium | low",
568
+ "_meta": {
569
+ "total_ms": 6798,
570
+ "breakdown_ms": { "extract": 6798, "search": 0, "score": 0 },
571
+ "tokens_used": { "input": 275, "output": 302 },
572
+ "estimated_cost_usd": 0.0036
573
+ }
574
+ }
575
+ ```
576
+
577
+ Typical latency is a few seconds -- one small API call with no tools
578
+ declared, versus `recommend_component`'s full search+score pipeline.
579
+
580
+ **`extraction_confidence` is a placeholder heuristic, not a validated
581
+ signal.** It's currently derived from how specific `component_need` is
582
+ (word count) -- the same "vague category name" problem the rest of this
583
+ README warns about elsewhere. It is not based on any measured correlation
584
+ with actual extraction quality. Treat `"low"` as a prompt to reread your
585
+ `component_need`, not as a calibrated confidence score. This is flagged
586
+ here as a known gap, to revisit once there's real usage data to base a
587
+ better signal on.
588
+
589
+ Trivial primitives (see [Simple primitives](#simple-primitives)) return an
590
+ empty `checklist` with `extraction_confidence: "high"` and no API call, the
591
+ same local skip-list short-circuit `recommend_component` uses.
287
592
 
288
593
  ## Tool: `record_component_decision`
289
594
 
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.
595
+ Use this tool after the agent has actually acted on a component
596
+ decision.
597
+
598
+ For example, call it after:
599
+
600
+ - Installing an existing component
601
+ - Completing a custom build
602
+
603
+ Do not call it for every recommendation.
604
+
605
+ The tool only saves the decision. It does not run a judgment or make an
606
+ Anthropic API call.
607
+
608
+ ### Input
296
609
 
297
- **Input:**
298
610
  ```json
299
611
  {
300
612
  "project_id": "my-booking-app",
@@ -302,35 +614,176 @@ effectively free and instant.
302
614
  "domain": "Airbnb-style rental marketplace",
303
615
  "action": "custom_built",
304
616
  "source": "custom",
305
- "timestamp": "2026-08-25T14:32:00.000Z"
617
+ "timestamp": "2026-08-25T14:32:00.000Z",
618
+ "time_saved_minutes": 25
306
619
  }
307
620
  ```
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:**
621
+
622
+ - `project_id` is required and should be stable. A project directory
623
+ path or project name works well.
624
+ - `action` must be `"installed"` or `"custom_built"`.
625
+ - `source` can be `"shadcn"`, `"21st.dev"`, `"reui"`, or `"custom"`.
626
+ - `timestamp` is optional. If omitted, Pattern uses the current time.
627
+ - `time_saved_minutes` is optional -- the calling agent's own estimate,
628
+ in minutes, of how much time this decision saved by having Pattern's
629
+ verdict instead of researching candidates and judging fit from scratch.
630
+ This is entirely self-reported. Pattern has no way to measure a
631
+ counterfactual ("how long would this have taken without Pattern?"), so
632
+ unlike `_meta` (Pattern's own real cost/latency for the call that
633
+ produced the verdict), this number is never computed or verified --
634
+ it's just recorded as-given. Omit it rather than guess a number to fill
635
+ the field.
636
+
637
+ ### Output
638
+
321
639
  ```json
322
- { "status": "recorded", "project_id": "my-booking-app", "entry": { "...": "..." } }
640
+ {
641
+ "status": "recorded",
642
+ "project_id": "my-booking-app",
643
+ "entry": { "..." }
644
+ }
323
645
  ```
324
646
 
647
+ ## Tool: `read_ledger`
648
+
649
+ Lists past `recommend_component` judgments for a `project_id` -- every
650
+ call that reached the API and produced a verdict, not just ones explicitly
651
+ confirmed via `record_component_decision`. Useful for auditing what
652
+ Pattern has already judged for a project, or for understanding why a call
653
+ came back with `served_from_ledger: true`.
654
+
655
+ ### Input
656
+
657
+ ```json
658
+ {
659
+ "project_id": "my-booking-app",
660
+ "component_need": "cancellation",
661
+ "limit": 10
662
+ }
663
+ ```
664
+
665
+ - `project_id` is required.
666
+ - `component_need` is optional -- a simple keyword filter (substring
667
+ match, no embeddings) against stored entries' `component_need`. Omit to
668
+ list everything for the project.
669
+ - `limit` is optional, defaults to 20. Most recent entries first.
670
+
671
+ ### Output
672
+
673
+ ```json
674
+ {
675
+ "project_id": "my-booking-app",
676
+ "entries": [
677
+ {
678
+ "id": "a1b2c3d4-...",
679
+ "timestamp": "2026-08-29T19:50:47.073Z",
680
+ "project_id": "my-booking-app",
681
+ "component_need": "cancellation policy display with refund tiers by date",
682
+ "domain": "Airbnb-style rental marketplace",
683
+ "framework": "React + Tailwind",
684
+ "checklist": ["...", "..."],
685
+ "checklist_source": "extracted",
686
+ "candidates_evaluated": [
687
+ { "source": "ReUI (reui.io)", "name": "Timeline", "url": "https://reui.io/components/timeline", "coverage_pct": 62.5 }
688
+ ],
689
+ "verdict": "use_existing",
690
+ "chosen_candidate": "Timeline",
691
+ "confidence": "low",
692
+ "reason": "scored",
693
+ "coverage": "5/8 (62.5%)",
694
+ "project_conventions_snapshot": "9f3a1c7e2b0d4f5a"
695
+ }
696
+ ]
697
+ }
698
+ ```
699
+
700
+ Each entry holds only distilled fields -- `candidates_evaluated` never
701
+ contains raw HTML, full prop tables, or the per-requirement evidence text
702
+ `recommend_component` itself returns. See
703
+ [Data minimization](#data-minimization) below.
704
+
705
+ ## Per-project judgment ledger
706
+
707
+ Distinct from [per-project decision memory](#per-project-decision-memory)
708
+ below -- that file only gains an entry when `record_component_decision` is
709
+ explicitly called. The ledger instead gains one entry automatically for
710
+ **every** `recommend_component` call that reaches the API with a
711
+ `project_id` and lands on reason `"scored"` or `"no_candidates_found"`.
712
+
713
+ Pattern stores it locally in:
714
+
715
+ ```
716
+ ~/.pattern/ledger.jsonl
717
+ ```
718
+
719
+ Change the location with `PATTERN_LEDGER_PATH`. One JSON object per line
720
+ (append-only, JSONL).
721
+
722
+ ### The cache-hit exception
723
+
724
+ Every other part of Pattern scores fresh every time (see
725
+ [No caching, by design](#no-caching-by-design)). The ledger is the one
726
+ deliberate exception: a later `recommend_component` call with a matching
727
+ `project_id` **can** be served directly from a prior entry, skipping
728
+ search+score entirely, when **all** of the following hold:
729
+
730
+ - `component_need` matches exactly (case-insensitive).
731
+ - `domain` and `framework` match exactly.
732
+ - `existing_stack` hashes to the same value as the stored entry's
733
+ (both omitted counts as a match).
734
+ - The stored entry's `confidence` is `"high"`.
735
+ - The stored entry's `reason` is `"scored"` or `"no_candidates_found"`.
736
+ - The stored entry is no older than `PATTERN_LEDGER_TTL_DAYS` (default
737
+ **30** days, configurable).
738
+
739
+ When served this way, the response has `reason: "ledger_cache_hit"`,
740
+ `served_from_ledger: true`, `ledger_entry_id`, and
741
+ `original_verdict_timestamp` -- so nothing is ever silently passed off as
742
+ freshly verified. `_meta.estimated_cost_usd` and `tokens_used` are
743
+ genuinely `0`: no API call happened. `requirements_checked` is `null` on
744
+ this path -- the ledger never stores per-requirement evidence text (see
745
+ [Data minimization](#data-minimization)), so a cache hit can only replay
746
+ the verdict/confidence/coverage/chosen-candidate, not the original
747
+ per-requirement reasoning.
748
+
749
+ Any mismatch on the criteria above -- a different `domain`, a changed
750
+ `existing_stack`, an entry that's gone stale, or one that wasn't
751
+ high-confidence -- falls through to a normal, fresh search+score call.
752
+
753
+ ### Turning the cache-hit exception off
754
+
755
+ Set `PATTERN_NO_LEDGER_CACHE_HIT` (any truthy value) to restore
756
+ "every `recommend_component` call always scores fresh" without removing
757
+ any ledger code. This disables only the cache-hit short-circuit --
758
+ entries are still written to `ledger.jsonl` and `read_ledger` still works
759
+ either way, so the audit trail keeps growing even with the switch on.
760
+ Unset the variable to re-enable cache hits again at any time.
761
+
762
+ ### Data minimization
763
+
764
+ Nothing written to the ledger ever contains raw search/fetch content.
765
+ Every candidate is reduced to exactly four fields before it's written --
766
+ `source`, `name`, `url`, `coverage_pct` -- enforced at the type level
767
+ (`assertDistilledCandidateShape` in `src/index.ts`), not just by
768
+ convention: a raw or extended object throws rather than silently
769
+ persisting. Run `node scripts/verify-ledger-boundary.mjs` (after
770
+ `npm run build`) to check this boundary directly.
771
+
325
772
  ## Per-project decision memory
326
773
 
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):
774
+ Pattern stores confirmed decisions locally in:
775
+
776
+ ```
777
+ ~/.pattern/memory.json
778
+ ```
779
+
780
+ You can change the location with:
781
+
782
+ ```
783
+ PATTERN_MEMORY_PATH
784
+ ```
785
+
786
+ The file is organized by project:
334
787
 
335
788
  ```json
336
789
  {
@@ -340,204 +793,517 @@ that's the key):
340
793
  "domain": "Airbnb-style rental marketplace",
341
794
  "action": "custom_built",
342
795
  "source": "custom",
343
- "timestamp": "2026-08-25T14:32:00.000Z"
796
+ "timestamp": "2026-08-25T14:32:00.000Z",
797
+ "time_saved_minutes": 25
344
798
  }
345
799
  ]
346
800
  }
347
801
  ```
348
802
 
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`.
803
+ `time_saved_minutes` is omitted from an entry entirely when the calling
804
+ agent didn't provide one -- it's never backfilled or estimated by Pattern.
805
+
806
+ Each project keeps its 50 most recent decisions. Older entries are
807
+ removed as new ones are added.
808
+
809
+ Only decisions explicitly recorded through `record_component_decision`
810
+ are saved. Pattern does not automatically save recommendations.
811
+
812
+ If an agent ignores or changes a recommendation, nothing is recorded
813
+ unless the agent explicitly calls `record_component_decision` with what
814
+ it actually did.
815
+
816
+ The memory file is local plaintext. Pattern does not send it anywhere.
817
+
818
+ `component_need` and `domain` are stored in this file, so avoid putting
819
+ sensitive information in them. See [SECURITY.md](./SECURITY.md).
820
+
821
+ A failure to write the decision file is returned as an error from
822
+ `record_component_decision`.
823
+
824
+ **No caching, by design.** Project memory (this file, `memory.json`) does
825
+ not cache recommendations. A previous decision is only additional context
826
+ for a new judgment. This is unrelated to the
827
+ [judgment ledger](#per-project-judgment-ledger)'s bounded cache-hit
828
+ exception, which lives in a separate file (`ledger.jsonl`) and is always
829
+ flagged (`served_from_ledger: true`) when it happens — see
830
+ [Known limitations](#known-limitations) for more.
831
+
832
+ ## Security and privacy
833
+
834
+ Pattern uses the Anthropic API and web search to make its
835
+ recommendations.
836
+
837
+ Local project memory and the local call log are stored on the machine
838
+ running Pattern. They are not sent anywhere by Pattern itself.
839
+
840
+ Review [SECURITY.md](./SECURITY.md) before putting sensitive information
841
+ into fields such as `component_need`, `domain`, or project IDs.
378
842
 
379
843
  ## Cost
380
844
 
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.
845
+ Pattern uses the Anthropic API, so `recommend_component` has a cost.
846
+
847
+ A typical single pass costs about $0.06–$0.10 with Sonnet 5 at current
848
+ pricing. Skip-listed primitives cost $0 because they're handled locally
849
+ and never reach the API. A [ledger cache hit](#the-cache-hit-exception)
850
+ also costs $0, for the same reason -- no API call happens.
851
+
852
+ ### The `_meta` field
853
+
854
+ Every `recommend_component` and `extract_requirements` response includes
855
+ an internal `_meta` block reporting what that call actually spent. This
856
+ is not shown to the user automatically -- the calling agent has to
857
+ surface it, the same way it's separately instructed to show
858
+ `install_command` before running it (see
859
+ [above](#installation-commands-are-not-trusted)). Both tool descriptions
860
+ say so explicitly: surface `_meta.estimated_cost_usd` after the call,
861
+ since it's real spend against the user's own API key, not internal
862
+ bookkeeping.
863
+
864
+ ```json
865
+ {
866
+ "total_ms": 41516,
867
+ "breakdown_ms": { "extract": 5006, "search": 3114, "score": 33396 },
868
+ "tokens_used": { "input": 8400, "output": 620 },
869
+ "estimated_cost_usd": 0.14,
870
+ "scoring_fetch": { "attempted": true, "succeeded": true, "url": "https://ui.shadcn.com/docs/components/..." }
871
+ }
872
+ ```
873
+
874
+ - `total_ms` -- wall-clock time for the call.
875
+ - `tokens_used` -- total input tokens (fresh + cache write + cache read,
876
+ summed) and output tokens, read directly from the API response's own
877
+ usage data.
878
+ - `estimated_cost_usd` -- computed from `tokens_used` at Pattern's
879
+ configured model's current per-token rate (checked against Anthropic's
880
+ pricing, not assumed). This is an estimate: it doesn't account for
881
+ pricing changes Pattern hasn't been updated for, or any account-specific
882
+ discounts.
883
+ - `breakdown_ms` -- how `total_ms` splits across `recommend_component`'s
884
+ three internal phases.
885
+ - `scoring_fetch` -- whether step 4's single candidate-verification fetch
886
+ (see [Fetch-grounded scoring](#fetch-grounded-scoring-and-reference-verification)
887
+ below) actually happened for this response. `url` is `null` when
888
+ `attempted` is `false` (no real candidate to verify, e.g. `reason:
889
+ "no_candidates_found"` or `"skip_list"`). This is a diagnostic only --
890
+ Pattern never uses it to auto-correct `requirements_checked` after the
891
+ fact, since there's no safe fallback value for an unverified met/not-met
892
+ call the way there is for a reference URL.
893
+
894
+ **How `breakdown_ms` is measured, and its one real caveat.** The bundled
895
+ call runs extraction, search, and scoring inside a single model turn
896
+ (search/fetch happen server-side, not as separate requests this code
897
+ makes), so there's no natural place for three separate stopwatches.
898
+ Pattern gets a real per-phase split by streaming the response and timing
899
+ content-block boundaries instead: `extract` ends the moment the first
900
+ search call starts, and `search` ends when that first wave of search
901
+ calls and results finishes. This was checked against real traces (not
902
+ assumed) across both `use_existing` and `custom_build` cases before
903
+ shipping, and both boundaries land cleanly and consistently.
904
+
905
+ The one place this needs a caveat: for a `custom_build` verdict, step 6's
906
+ Mobbin/Figma reference search and its deep-link verification fetch happen
907
+ *after* the coverage-scoring reasoning that decided `custom_build` in the
908
+ first place -- so `breakdown_ms.score`, for those cases, covers coverage
909
+ scoring **and** reference-finding **and** the final write-up, not just
910
+ "scoring" in the narrow step-4 sense. It's still a real, measured number;
911
+ it's just a wider bucket for `custom_build` than for `use_existing`. This
912
+ is disclosed here rather than presented as a narrower number than it is.
913
+
914
+ **When the ensemble triggers** (see below), `_meta` reports the sum
915
+ across all reruns that actually happened -- total tokens and cost spent,
916
+ not the wall-clock time you waited. The three ensemble passes run with the
917
+ 2nd and 3rd concurrent, so perceived latency is closer to ~2x one pass,
918
+ not the ~3x `total_ms` will show. Cost and token spend are genuinely
919
+ additive across reruns, which is what `_meta` is reporting there.
920
+ `scoring_fetch` is the one exception -- it isn't summed (a fetch either
921
+ happened for the specific pass whose evidence became the returned
922
+ `requirements_checked`, or it didn't), so it reports that winning pass's
923
+ own value, not an aggregate across all three.
924
+
925
+ Three things help keep the cost down without changing the decision process.
926
+
927
+ ### Prompt caching
928
+
929
+ Pattern caches its system instructions using `cache_control: ephemeral`.
930
+
931
+ The instructions are the same across calls, so repeated requests don't
932
+ pay the full input cost for that block.
933
+
934
+ ### Search limits
935
+
936
+ Pattern limits candidate discovery to 3 web searches -- one per source.
937
+
938
+ If a custom build is needed, it reserves 2 additional searches for
939
+ references:
940
+
941
+ - 1 for Mobbin
942
+ - 1 for Figma Community
943
+
944
+ shadcn/ui, 21st.dev, and ReUI are searched in the same turn rather than
945
+ sequentially, which reduces how much conversation context needs to be
946
+ sent repeatedly.
947
+
948
+ ### Fetch-grounded scoring and reference verification
949
+
950
+ Pattern allows up to 3 `web_fetch` calls per pass: 1 reserved for scoring,
951
+ 2 reserved for reference verification (1 for Mobbin, 1 for Figma
952
+ Community).
953
+
954
+ Before finalizing coverage, Pattern fetches the best-fitting candidate's
955
+ own real docs/source page once and re-checks the checklist against that
956
+ page, not just the search-result snippet it started with. This exists
957
+ because search-result descriptions can both overstate a component's real
958
+ capabilities and miss real ones it actually has -- both were observed in
959
+ testing on the same case (an invented feature claim and a missed real
960
+ one). If the fetch fails, or there's no confirmed URL to fetch, Pattern
961
+ falls back to search-only evidence and says so in the affected items.
962
+
963
+ Each result's `_meta.scoring_fetch` reports whether this fetch actually
964
+ happened for that response (`{ attempted, succeeded, url }`) -- it's a
965
+ diagnostic, not something Pattern uses to auto-correct individual
966
+ requirement judgments. Unlike a reference URL (which has a safe fallback:
967
+ the category page), there's no safe fallback for an unverified met/not-met
968
+ call, so nothing is silently corrected -- `scoring_fetch` just tells you
969
+ whether the grounding actually ran.
970
+
971
+ A fetch can read up to 15,000 content tokens. `web_fetch` has no separate
972
+ per-call fee; the cost comes from the content added to the model's
973
+ context.
974
+
975
+ ### Choosing a cheaper model
976
+
977
+ You can change the model with:
978
+
979
+ ```
980
+ PATTERN_MODEL
981
+ ```
982
+
983
+ It defaults to:
984
+
985
+ ```
986
+ claude-sonnet-5
987
+ ```
988
+
989
+ You could use a cheaper model such as Haiku 4.5 without changing the code.
990
+
991
+ Before using a cheaper model in production, run the five validation cases
992
+ and compare its results with Sonnet's:
993
+
994
+ - Price breakdown
995
+ - Cancellation policy
996
+ - Earnings dashboard
997
+ - Image gallery
998
+ - Messaging inbox
999
+
1000
+ The cheaper model hasn't been validated yet, so these results should be
1001
+ treated as an open question rather than an established performance claim.
409
1002
 
410
1003
  ### Ensemble cost (boundary-risk cases only)
411
1004
 
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.
1005
+ Pattern uses extra model calls only when a result is close enough to a
1006
+ decision threshold that a small change in judgment could change the
1007
+ verdict.
1008
+
1009
+ The requirement checklist has eight items, so coverage can only land on
1010
+ these values:
1011
+
1012
+ ```
1013
+ 0%
1014
+ 12.5%
1015
+ 25%
1016
+ 37.5%
1017
+ 50%
1018
+ 62.5%
1019
+ 75%
1020
+ 87.5%
1021
+ 100%
1022
+ ```
1023
+
1024
+ The decision thresholds are 40% and 80%.
1025
+
1026
+ That means results at 37.5%, 50%, 75%, and 87.5% are the cases where
1027
+ changing the judgment on one requirement can flip the verdict.
1028
+
1029
+ For those cases, Pattern runs the full judgment three times and takes
1030
+ the majority result.
1031
+
1032
+ For example:
1033
+
1034
+ ```json
1035
+ {
1036
+ "ensemble": {
1037
+ "triggered": true,
1038
+ "runs": ["use_existing", "custom_build", "use_existing"],
1039
+ "agreement": "2/3"
1040
+ }
1041
+ }
1042
+ ```
1043
+
1044
+ If all three runs agree, the majority verdict is returned normally.
1045
+
1046
+ If they split 2/3, Pattern sets confidence to `"low"`. The disagreement
1047
+ is surfaced rather than hidden.
1048
+
1049
+ Results at 0, 12.5, 25, 62.5, and 100% stay single-pass because one
1050
+ changed requirement can't move them across either threshold.
1051
+
1052
+ ### Measured ensemble cost
1053
+
1054
+ The ensemble doesn't mean every call costs 3x.
1055
+
1056
+ In the latest five-case validation, Pattern made 15 outer calls:
1057
+
1058
+ - 8 stayed single-pass
1059
+ - 7 triggered the ensemble
1060
+ - 21 model passes were used for those 7 ensemble calls
1061
+ - 29 total model calls across the test
1062
+
1063
+ That works out to about a 1.9x average multiplier across that test set.
1064
+
1065
+ The worst case is still 3x for an individual call when the ensemble is
1066
+ triggered.
1067
+
1068
+ ### What the ensemble can and cannot solve
1069
+
1070
+ The ensemble reduces the chance that one unlucky model judgment
1071
+ determines the result. It doesn't eliminate uncertainty.
1072
+
1073
+ If the underlying evidence is genuinely ambiguous, three runs can still
1074
+ disagree.
1075
+
1076
+ For example, the image-gallery validation case continued to flip between
1077
+ outer runs. When that happened, the ensemble consistently reported a 2/3
1078
+ split with `confidence: "low"`.
1079
+
1080
+ That's expected behavior: the tool is exposing uncertainty instead of
1081
+ presenting an ambiguous result as certain.
454
1082
 
455
1083
  ### Session call cap
456
1084
 
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.
1085
+ Pattern limits the number of API calls to 40 per server process by
1086
+ default.
1087
+
1088
+ You can change this with:
1089
+
1090
+ ```
1091
+ PATTERN_SESSION_CAP
1092
+ ```
1093
+
1094
+ The cap protects against runaway agents, such as an agent stuck in a
1095
+ retry loop or repeatedly asking for the same recommendation.
1096
+
1097
+ The 40-call default is based on the project's validation work. A
1098
+ realistic project with roughly 25 components would use about 25 calls
1099
+ for a full pass, leaving room for iteration.
1100
+
1101
+ Skip-listed primitives don't count because they never reach the API.
1102
+
1103
+ The counter lives in memory and resets when the server restarts.
1104
+
1105
+ If 40 calls is too low for your project, increase `PATTERN_SESSION_CAP`
1106
+ rather than repeatedly restarting the server.
470
1107
 
471
1108
  ## Local call log
472
1109
 
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.
1110
+ Every API call is recorded in a local log.
1111
+
1112
+ By default:
1113
+
1114
+ ```
1115
+ ~/.pattern/calls.log
1116
+ ```
1117
+
1118
+ You can change the location with:
1119
+
1120
+ ```
1121
+ PATTERN_LOG_PATH
1122
+ ```
1123
+
1124
+ The log is local. Pattern does not send it anywhere.
1125
+
1126
+ Each API call adds one JSON line, for example:
479
1127
 
480
- Each line looks like:
481
1128
  ```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.
1129
+ {
1130
+ "timestamp": "2026-08-24T21:12:43.882Z",
1131
+ "component_need": "cancellation policy display",
1132
+ "domain": "Airbnb-style rental marketplace",
1133
+ "framework": "React + Tailwind",
1134
+ "verdict": "custom_build",
1135
+ "confidence": "high",
1136
+ "reason": "scored",
1137
+ "coverage": "2/8 (25%)",
1138
+ "ensemble_triggered": false,
1139
+ "reference_sources_grounded": ["Mobbin", "Figma Community"],
1140
+ "checklist_source": "extracted",
1141
+ "total_ms": 44834,
1142
+ "estimated_cost_usd": 0.15
1143
+ }
1144
+ ```
1145
+
1146
+ Additional fields appear when relevant:
1147
+
1148
+ - `ensemble_agreement` appears when the ensemble runs.
1149
+ - `reference_sources_grounded` appears for `custom_build` results and
1150
+ lists only sources that produced a grounded reference.
1151
+
1152
+ `checklist_source`, `total_ms`, and `estimated_cost_usd` mirror the
1153
+ call's `_meta` block (see [Cost](#cost)) -- `total_ms` and
1154
+ `estimated_cost_usd` are the same aggregated-across-reruns numbers when
1155
+ the ensemble triggers, not per-pass figures.
1156
+
1157
+ The log deliberately does not contain:
1158
+
1159
+ - The full `requirements_checked` evidence
1160
+ - Your Anthropic API key
1161
+
1162
+ It does contain `component_need` and `domain`, so avoid putting sensitive
1163
+ information in those fields. See [SECURITY.md](./SECURITY.md).
1164
+
1165
+ The log directory is created automatically.
1166
+
1167
+ If Pattern cannot write to the log because of permissions, a read-only
1168
+ filesystem, or a full disk, it reports the problem to stderr but does not
1169
+ fail the tool call.
1170
+
1171
+ ### Review a log
1172
+
1173
+ You can summarize a log with:
1174
+
1175
+ ```
1176
+ node summarize-log.js [path]
1177
+ ```
1178
+
1179
+ If no path is provided, it uses the same default location as the server.
1180
+
1181
+ The summary includes:
1182
+
1183
+ - Verdict and confidence breakdown
1184
+ - Reason breakdown
1185
+ - Ensemble trigger and agreement rates
1186
+ - Reference-source grounding rates for custom builds
1187
+ - Component needs that were requested more than once
1188
+
1189
+ Repeated component needs can be useful to investigate alongside the
1190
+ [session call cap](#session-call-cap).
1191
+
1192
+ ## Known limitations
1193
+
1194
+ ### Model judgment can vary
1195
+
1196
+ Pattern's search results can stay the same while the model's
1197
+ interpretation of those results changes between runs.
1198
+
1199
+ Validation found cases where two runs found the same named components
1200
+ using the same search queries but judged the same evidence differently.
1201
+
1202
+ For example, the model interpreted an Export action as present in one
1203
+ run and absent in another.
1204
+
1205
+ This is a limitation of model-based evidence judgment, not necessarily a
1206
+ search or code problem.
1207
+
1208
+ The boundary-risk ensemble exists to detect and surface this uncertainty.
1209
+
1210
+ ### A staged pipeline was evaluated and not adopted
1211
+
1212
+ To address the variance above, an alternative architecture was built and
1213
+ tested: splitting the single bundled judgment call into separate stages
1214
+ (extract requirements, search evidence, score coverage), on the theory
1215
+ that isolating each step would make results more consistent and easier
1216
+ to diagnose.
1217
+
1218
+ A pilot comparison (5 cases, 3 repeated runs per case, per
1219
+ architecture) found no consistent benefit. The staged pipeline improved
1220
+ consistency on one boundary-risk case but was less consistent than the
1221
+ bundled pipeline on another, including one run that failed outright.
1222
+ Net accuracy against hand-graded gold answers was statistically
1223
+ indistinguishable between the two architectures, and the staged
1224
+ pipeline cost roughly **2x** the bundled pipeline's call volume across
1225
+ the board, not only on the boundary-risk cases it was expected to help
1226
+ most.
1227
+
1228
+ Pattern ships the bundled pipeline. The staged implementation remains
1229
+ in the repo (`src/staged/`) as an evaluated, unshipped experiment, not
1230
+ a supported alternative.
1231
+
1232
+ **`extract_requirements` is not a revival of this.** It's a standalone
1233
+ tool for inspecting the extraction step's output before an agent commits
1234
+ to `recommend_component`'s search+score budget -- an opt-in visibility
1235
+ tool, not an internal re-architecture. `recommend_component`'s own
1236
+ pipeline is still fully bundled; nothing about this evaluation changed.
1237
+
1238
+ ### No caching, by design
1239
+
1240
+ Every recommendation searches and scores again -- with one bounded
1241
+ exception (see below).
1242
+
1243
+ This means a recommendation can change as component libraries change.
1244
+ For example, a later shadcn/ui release can introduce a component that
1245
+ changes a previous `custom_build` result.
1246
+
1247
+ Do not build a second, unbounded cache of recommendations at the
1248
+ calling-agent layer on top of Pattern's own. If you add caching there,
1249
+ keep it session-scoped.
1250
+
1251
+ [Project decision memory](#per-project-decision-memory) does not change
1252
+ this. It provides context from previous decisions, but every
1253
+ `recommend_component` call still performs a fresh search and scoring
1254
+ pass.
1255
+
1256
+ The one deliberate exception is the
1257
+ [judgment ledger's cache-hit path](#the-cache-hit-exception): a later
1258
+ call matching an exact, recent, high-confidence prior judgment can be
1259
+ served without a fresh search+score. It's bounded (exact
1260
+ component_need/domain/framework/conventions match, a staleness TTL) and
1261
+ always self-identifies via `served_from_ledger: true` and
1262
+ `reason: "ledger_cache_hit"` -- so a calling agent that wants a guaranteed
1263
+ fresh check on every call should look for that flag and treat it the same
1264
+ as any other verdict it wants to double-check.
1265
+
1266
+ ### The skip-list is still evolving
1267
+
1268
+ The primitive skip-list is a starting point and has not yet been
1269
+ validated against broad real-world usage.
1270
+
1271
+ Watch for two failure modes:
1272
+
1273
+ - Agents calling Pattern for things that should have been skipped.
1274
+ - Agents building generic UI for something that should have been on the
1275
+ skip-list.
1276
+
1277
+ The local call log can help identify both patterns.
1278
+
1279
+ ### Pattern needs internet access
1280
+
1281
+ Pattern requires outbound access to:
1282
+
1283
+ ```
1284
+ api.anthropic.com
1285
+ ```
1286
+
1287
+ It also depends on whatever external sites the model's `web_search` tool
1288
+ can reach.
1289
+
1290
+ It will not work in an environment that blocks general outbound internet
1291
+ access.
1292
+
1293
+ ### Requirements and coverage are judgment calls
1294
+
1295
+ Requirement extraction and evidence scoring are performed by the model.
1296
+
1297
+ Pattern adds safeguards such as:
1298
+
1299
+ - Structured requirements
1300
+ - Server-side coverage recalculation
1301
+ - Decision thresholds
1302
+ - Boundary-risk ensembling
1303
+ - Grounding checks for reference URLs
1304
+
1305
+ But the underlying interpretation of whether evidence satisfies a
1306
+ requirement is still model judgment.
1307
+
1308
+ When introducing Pattern into a new workflow, spot-check early results
1309
+ against the actual components before relying on it unattended.