pattern-mcp 0.2.0 → 0.4.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
@@ -5,8 +5,9 @@
5
5
  [![npm downloads](https://img.shields.io/npm/dm/pattern-mcp.svg)](https://www.npmjs.com/package/pattern-mcp)
6
6
  [![MIT license](https://img.shields.io/badge/license-MIT-111111.svg)](./LICENSE)
7
7
 
8
- Pattern is an MCP server that helps coding agents make better UI
9
- component decisions.
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.
10
11
 
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)
12
13
 
@@ -31,7 +32,7 @@ whether to:
31
32
 
32
33
  Pattern is designed for agents to use **while they are building**.
33
34
 
34
- It exposes three tools:
35
+ It exposes six tools:
35
36
 
36
37
  - `recommend_component` — evaluates a UI component need and returns a
37
38
  structured recommendation.
@@ -41,6 +42,18 @@ It exposes three tools:
41
42
  - `record_component_decision` — records what the agent actually did so
42
43
  future recommendations in the same project can take that decision into
43
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)); pass
48
+ `feature_id` instead of browsing by keyword to get a full cost rollup for
49
+ one feature (see [Tool: `report_build_cost`](#tool-report_build_cost)).
50
+ - `report_build_cost` — self-reports the end-to-end build cost for one
51
+ feature, so cost incurred after Pattern's own verdict (the actual
52
+ scaffold/install/build) is still attributable back to it.
53
+ - `report_outcome_proxy` — self-reports a value signal (rework, time to
54
+ merge, kept-vs-replaced) for one feature, deliberately independent of
55
+ Pattern's own verdict -- see [Outcome
56
+ proxies](#outcome-proxies).
44
57
 
45
58
  ## How it works
46
59
 
@@ -70,6 +83,9 @@ A result can also be:
70
83
  - `custom_build`
71
84
  - `no_candidates_found`
72
85
  - `skip_list`
86
+ - `ledger_cache_hit` — served from a recent, matching prior judgment
87
+ instead of a fresh search+score (see
88
+ [Per-project judgment ledger](#per-project-judgment-ledger)).
73
89
 
74
90
  `no_candidates_found` is kept separate from a low-coverage result. Not
75
91
  finding a candidate is different from finding candidates that don't cover
@@ -78,7 +94,14 @@ the requirements.
78
94
  If `project_id` is supplied, Pattern also checks for past confirmed
79
95
  decisions on that project and factors them in as a consistency signal —
80
96
  never a rule that overrides a genuinely better match found in the current
81
- search.
97
+ search. Separately, `project_id` also enables the judgment ledger: a
98
+ high-confidence prior judgment matching this exact
99
+ component_need/domain/framework/existing_stack, recorded recently enough,
100
+ can be served directly (`ledger_cache_hit`) instead of running a fresh
101
+ search+score. This is the one deliberate exception to "every recommendation
102
+ searches and scores again" — see
103
+ [Per-project judgment ledger](#per-project-judgment-ledger) for the exact
104
+ rules and why it's safe.
82
105
 
83
106
  Every result includes `computed_at`, because coverage is a snapshot of the
84
107
  search at that point in time, not a permanent fact. Every result also
@@ -395,6 +418,16 @@ Leave `checklist` out to keep today's default behavior: `recommend_component`
395
418
  extracts its own checklist internally, exactly as before this option
396
419
  existed.
397
420
 
421
+ #### `feature_id`
422
+
423
+ `feature_id` is optional -- a stable identifier for the feature this
424
+ component need belongs to (e.g. a ticket id or branch name). Its only use
425
+ is joining this call's cost with a later
426
+ [`report_build_cost`](#tool-report_build_cost) call for the same feature.
427
+ Omit it to have one derived deterministically from `project_id` +
428
+ `component_need`; only meaningful together with `project_id`. See
429
+ [Feature cost attribution](#feature-cost-attribution).
430
+
398
431
  **Is the checklist actually skipped, not just re-derived?** Checked, not
399
432
  assumed. `breakdown_ms.extract` for a `checklist`-provided call is smaller
400
433
  than the default path's, but not near-zero -- which raised the question of
@@ -600,7 +633,8 @@ Anthropic API call.
600
633
  "domain": "Airbnb-style rental marketplace",
601
634
  "action": "custom_built",
602
635
  "source": "custom",
603
- "timestamp": "2026-08-25T14:32:00.000Z"
636
+ "timestamp": "2026-08-25T14:32:00.000Z",
637
+ "time_saved_minutes": 25
604
638
  }
605
639
  ```
606
640
 
@@ -609,6 +643,15 @@ Anthropic API call.
609
643
  - `action` must be `"installed"` or `"custom_built"`.
610
644
  - `source` can be `"shadcn"`, `"21st.dev"`, `"reui"`, or `"custom"`.
611
645
  - `timestamp` is optional. If omitted, Pattern uses the current time.
646
+ - `time_saved_minutes` is optional -- the calling agent's own estimate,
647
+ in minutes, of how much time this decision saved by having Pattern's
648
+ verdict instead of researching candidates and judging fit from scratch.
649
+ This is entirely self-reported. Pattern has no way to measure a
650
+ counterfactual ("how long would this have taken without Pattern?"), so
651
+ unlike `_meta` (Pattern's own real cost/latency for the call that
652
+ produced the verdict), this number is never computed or verified --
653
+ it's just recorded as-given. Omit it rather than guess a number to fill
654
+ the field.
612
655
 
613
656
  ### Output
614
657
 
@@ -620,6 +663,322 @@ Anthropic API call.
620
663
  }
621
664
  ```
622
665
 
666
+ ## Tool: `read_ledger`
667
+
668
+ Lists past `recommend_component` judgments for a `project_id` -- every
669
+ call that reached the API and produced a verdict, not just ones explicitly
670
+ confirmed via `record_component_decision`. Useful for auditing what
671
+ Pattern has already judged for a project, or for understanding why a call
672
+ came back with `served_from_ledger: true`.
673
+
674
+ ### Input
675
+
676
+ ```json
677
+ {
678
+ "project_id": "my-booking-app",
679
+ "component_need": "cancellation",
680
+ "limit": 10
681
+ }
682
+ ```
683
+
684
+ - `project_id` is required.
685
+ - `component_need` is optional -- a simple keyword filter (substring
686
+ match, no embeddings) against stored entries' `component_need`. Omit to
687
+ list everything for the project.
688
+ - `limit` is optional, defaults to 20. Most recent entries first.
689
+ - `feature_id` is optional. When provided, `component_need` and `limit`
690
+ are ignored and the response is a full cost rollup for that one feature
691
+ instead of a keyword listing -- see [Feature cost
692
+ attribution](#feature-cost-attribution).
693
+
694
+ ### Output
695
+
696
+ ```json
697
+ {
698
+ "project_id": "my-booking-app",
699
+ "entries": [
700
+ {
701
+ "id": "a1b2c3d4-...",
702
+ "timestamp": "2026-08-29T19:50:47.073Z",
703
+ "project_id": "my-booking-app",
704
+ "feature_id": "3f9a21c0",
705
+ "component_need": "cancellation policy display with refund tiers by date",
706
+ "domain": "Airbnb-style rental marketplace",
707
+ "framework": "React + Tailwind",
708
+ "checklist": ["...", "..."],
709
+ "checklist_source": "extracted",
710
+ "candidates_evaluated": [
711
+ { "source": "ReUI (reui.io)", "name": "Timeline", "url": "https://reui.io/components/timeline", "coverage_pct": 62.5 }
712
+ ],
713
+ "verdict": "use_existing",
714
+ "chosen_candidate": "Timeline",
715
+ "confidence": "low",
716
+ "reason": "scored",
717
+ "coverage": "5/8 (62.5%)",
718
+ "cost_usd": 0.087,
719
+ "cache_hit": false,
720
+ "project_conventions_snapshot": "9f3a1c7e2b0d4f5a"
721
+ }
722
+ ]
723
+ }
724
+ ```
725
+
726
+ Passing `feature_id` instead returns:
727
+
728
+ ```json
729
+ {
730
+ "project_id": "my-booking-app",
731
+ "feature_id": "3f9a21c0",
732
+ "verdict_entries": [ "...same shape as above, filtered to this feature_id..." ],
733
+ "build_records": [
734
+ { "id": "...", "timestamp": "...", "project_id": "my-booking-app", "feature_id": "3f9a21c0", "tokens_used": 9000, "cost_usd": 1.25, "outcome": "shipped" }
735
+ ],
736
+ "total_cost_usd": 1.34,
737
+ "outcome_proxy": { "time_to_merge_hours": 3.5, "reworked": true, "days_to_rework": 12, "status_at_30d": "kept" },
738
+ "outcome_proxy_history": [ "...every raw report_outcome_proxy record for this feature_id, oldest first..." ]
739
+ }
740
+ ```
741
+
742
+ `outcome_proxy` is `null` (and `outcome_proxy_history` an empty array)
743
+ when no `report_outcome_proxy` calls have been made for this feature yet
744
+ -- see [Outcome proxies](#outcome-proxies).
745
+
746
+ Each entry holds only distilled fields -- `candidates_evaluated` never
747
+ contains raw HTML, full prop tables, or the per-requirement evidence text
748
+ `recommend_component` itself returns. See
749
+ [Data minimization](#data-minimization) below.
750
+
751
+ ## Tool: `report_build_cost`
752
+
753
+ Self-reports the end-to-end build cost for one feature. Pattern only ever
754
+ sees the cost of judging *what* to use (`recommend_component`'s own
755
+ `_meta.estimated_cost_usd`); everything past that -- the actual scaffold,
756
+ install, or custom build -- happens outside Pattern entirely and Pattern
757
+ has no way to observe it. Call this once, after the calling agent's build
758
+ for a feature is actually complete (shipped, abandoned, or replaced), not
759
+ on every verdict.
760
+
761
+ ### Input
762
+
763
+ ```json
764
+ {
765
+ "feature_id": "3f9a21c0",
766
+ "project_id": "my-booking-app",
767
+ "tokens_used": 9000,
768
+ "cost_usd": 1.25,
769
+ "outcome": "shipped"
770
+ }
771
+ ```
772
+
773
+ - `feature_id` is required -- either a value you explicitly passed to an
774
+ earlier `recommend_component` call for this feature, or (if you didn't)
775
+ the same value `recommend_component` derives on its own:
776
+ `sha256(project_id + "::" + component_need, lowercased/trimmed)`
777
+ truncated to 8 hex characters. When in doubt, call `read_ledger` with
778
+ just `project_id` and copy the `feature_id` off the relevant entry
779
+ rather than re-deriving it by hand.
780
+ - `project_id` is optional but recommended -- without it, this record
781
+ still joins to a `recommend_component` entry by `feature_id` alone, but
782
+ `read_ledger`'s rollup can't scope it to one project.
783
+ - `tokens_used` is optional.
784
+ - `cost_usd` is required -- your own real number, not Pattern's.
785
+ - `outcome` is required: `"shipped"`, `"abandoned"`, or
786
+ `"replaced_with_existing"`.
787
+
788
+ ### Output
789
+
790
+ ```json
791
+ {
792
+ "status": "recorded",
793
+ "record": {
794
+ "id": "c5706b47-...",
795
+ "timestamp": "2026-09-02T01:25:29.653Z",
796
+ "project_id": "my-booking-app",
797
+ "feature_id": "3f9a21c0",
798
+ "tokens_used": 9000,
799
+ "cost_usd": 1.25,
800
+ "outcome": "shipped"
801
+ }
802
+ }
803
+ ```
804
+
805
+ This only appends a local record to `~/.pattern/build_ledger.jsonl`
806
+ (override with `PATTERN_BUILD_LEDGER_PATH`) -- it never re-runs any
807
+ judgment and never calls the Anthropic API.
808
+
809
+ ## Tool: `report_outcome_proxy`
810
+
811
+ Self-reports a value signal for one feature, deliberately independent of
812
+ Pattern's own verdict -- the whole point is a signal that could
813
+ *contradict* the verdict, so nothing on this path ever reads
814
+ `coverage_pct`, `confidence`, or any other Pattern-produced field. Compute
815
+ `reworked`/`days_to_rework` and `time_to_merge_hours` from your own repo's
816
+ real git history (e.g. `git log --follow` against the files this
817
+ feature's build touched) -- Pattern has no `process.cwd()`/repo-path
818
+ concept and no filesystem access to your repo at all, so it can't compute
819
+ these itself. Report `status_at_30d` only once a real ~30-day-post-merge
820
+ horizon has actually passed.
821
+
822
+ Safe to call more than once for the same `feature_id` as more signal
823
+ becomes available over time -- e.g. `time_to_merge_hours` right after
824
+ merge, `reworked` on a later re-check, `status_at_30d` at the 30-day mark.
825
+ `read_ledger`'s `feature_id` rollup merges every report into one
826
+ latest-value-per-field view (a later report only overwrites the specific
827
+ fields it includes, never the others).
828
+
829
+ ### Input
830
+
831
+ ```json
832
+ {
833
+ "feature_id": "3f9a21c0",
834
+ "project_id": "my-booking-app",
835
+ "reworked": true,
836
+ "days_to_rework": 12
837
+ }
838
+ ```
839
+
840
+ - `feature_id` is required.
841
+ - `project_id` is optional but recommended, same reasoning as
842
+ `report_build_cost`.
843
+ - `reworked`, `days_to_rework`, `time_to_merge_hours`, `status_at_30d` are
844
+ all individually optional, but **at least one is required** -- an empty
845
+ report is rejected rather than silently recording nothing.
846
+
847
+ ### Output
848
+
849
+ ```json
850
+ {
851
+ "status": "recorded",
852
+ "record": {
853
+ "id": "8a2f1e0c-...",
854
+ "timestamp": "2026-09-16T18:04:12.881Z",
855
+ "project_id": "my-booking-app",
856
+ "feature_id": "3f9a21c0",
857
+ "reworked": true,
858
+ "days_to_rework": 12
859
+ }
860
+ }
861
+ ```
862
+
863
+ This only appends a local record to `~/.pattern/outcome_proxies.jsonl`
864
+ (override with `PATTERN_OUTCOME_PROXY_PATH`) -- it never calls the
865
+ Anthropic API.
866
+
867
+ ## Feature cost attribution
868
+
869
+ Every `recommend_component` call that writes to the ledger -- a fresh
870
+ judgment *or* a $0 [ledger cache hit](#the-cache-hit-exception) -- now
871
+ carries a `feature_id`, plus its own `cost_usd` and `cache_hit`. Pair that
872
+ with `report_build_cost`'s build-time record and `read_ledger`'s
873
+ `feature_id` rollup, and total spend on a feature (judgment + build,
874
+ across however many calls) is queryable end to end, not just the cost of
875
+ one verdict call.
876
+
877
+ `feature_id` defaults to a deterministic derivation --
878
+ `sha256(project_id + "::" + component_need)` truncated to 8 hex chars --
879
+ so repeat calls for the same feature land under the same id automatically,
880
+ with no coordination needed between `recommend_component` and
881
+ `report_build_cost` calls. Pass your own `feature_id` explicitly (e.g. a
882
+ ticket id or branch name) if you'd rather key on something stable on your
883
+ own side.
884
+
885
+ ## Outcome proxies
886
+
887
+ Cost data alone (`feature cost attribution` above) can't answer whether a
888
+ cheaper build was actually *worth it* -- comparing it against Pattern's
889
+ own verdict/`coverage_pct` would be circular, since that's the very thing
890
+ being evaluated. `report_outcome_proxy` attaches a cheap, non-circular
891
+ value signal per `feature_id` instead:
892
+
893
+ - **`reworked` / `days_to_rework`** (primary proxy) -- was any file this
894
+ feature's build touched modified again after the original merge, and if
895
+ so, how soon? Computed from real git history, not Pattern's own data.
896
+ - **`time_to_merge_hours`** (secondary proxy) -- how long the feature
897
+ took from first commit to merge.
898
+ - **`status_at_30d`** (tertiary, longer-horizon proxy) -- at a ~30-day
899
+ horizon, does the component Pattern recommended still exist in the
900
+ codebase, unchanged in kind (`"kept"`), was it swapped for a different
901
+ approach (`"replaced"`), or removed entirely (`"removed"`)?
902
+
903
+ `read_ledger`'s `feature_id` rollup returns both `outcome_proxy` (the
904
+ merged latest-value-per-field view) and `outcome_proxy_history` (every
905
+ raw report, in case the timeline itself matters) alongside the cost
906
+ figures from [Feature cost attribution](#feature-cost-attribution) above
907
+ -- so "what did this feature cost end to end, and did it hold up?" is
908
+ answerable from one `read_ledger` call.
909
+
910
+ ## Per-project judgment ledger
911
+
912
+ Distinct from [per-project decision memory](#per-project-decision-memory)
913
+ below -- that file only gains an entry when `record_component_decision` is
914
+ explicitly called. The ledger instead gains one entry automatically for
915
+ **every** `recommend_component` call with a `project_id` that lands on
916
+ reason `"scored"` or `"no_candidates_found"` -- whether that's a fresh
917
+ call that reached the API, or a $0 [ledger cache
918
+ hit](#the-cache-hit-exception) served without one (`cache_hit: true`,
919
+ `cost_usd: 0`), so a feature's total cost still rolls up correctly even
920
+ once most of its later calls are free. See [Feature cost
921
+ attribution](#feature-cost-attribution).
922
+
923
+ Pattern stores it locally in:
924
+
925
+ ```
926
+ ~/.pattern/ledger.jsonl
927
+ ```
928
+
929
+ Change the location with `PATTERN_LEDGER_PATH`. One JSON object per line
930
+ (append-only, JSONL).
931
+
932
+ ### The cache-hit exception
933
+
934
+ Every other part of Pattern scores fresh every time (see
935
+ [No caching, by design](#no-caching-by-design)). The ledger is the one
936
+ deliberate exception: a later `recommend_component` call with a matching
937
+ `project_id` **can** be served directly from a prior entry, skipping
938
+ search+score entirely, when **all** of the following hold:
939
+
940
+ - `component_need` matches exactly (case-insensitive).
941
+ - `domain` and `framework` match exactly.
942
+ - `existing_stack` hashes to the same value as the stored entry's
943
+ (both omitted counts as a match).
944
+ - The stored entry's `confidence` is `"high"`.
945
+ - The stored entry's `reason` is `"scored"` or `"no_candidates_found"`.
946
+ - The stored entry is no older than `PATTERN_LEDGER_TTL_DAYS` (default
947
+ **30** days, configurable).
948
+
949
+ When served this way, the response has `reason: "ledger_cache_hit"`,
950
+ `served_from_ledger: true`, `ledger_entry_id`, and
951
+ `original_verdict_timestamp` -- so nothing is ever silently passed off as
952
+ freshly verified. `_meta.estimated_cost_usd` and `tokens_used` are
953
+ genuinely `0`: no API call happened. `requirements_checked` is `null` on
954
+ this path -- the ledger never stores per-requirement evidence text (see
955
+ [Data minimization](#data-minimization)), so a cache hit can only replay
956
+ the verdict/confidence/coverage/chosen-candidate, not the original
957
+ per-requirement reasoning.
958
+
959
+ Any mismatch on the criteria above -- a different `domain`, a changed
960
+ `existing_stack`, an entry that's gone stale, or one that wasn't
961
+ high-confidence -- falls through to a normal, fresh search+score call.
962
+
963
+ ### Turning the cache-hit exception off
964
+
965
+ Set `PATTERN_NO_LEDGER_CACHE_HIT` (any truthy value) to restore
966
+ "every `recommend_component` call always scores fresh" without removing
967
+ any ledger code. This disables only the cache-hit short-circuit --
968
+ entries are still written to `ledger.jsonl` and `read_ledger` still works
969
+ either way, so the audit trail keeps growing even with the switch on.
970
+ Unset the variable to re-enable cache hits again at any time.
971
+
972
+ ### Data minimization
973
+
974
+ Nothing written to the ledger ever contains raw search/fetch content.
975
+ Every candidate is reduced to exactly four fields before it's written --
976
+ `source`, `name`, `url`, `coverage_pct` -- enforced at the type level
977
+ (`assertDistilledCandidateShape` in `src/index.ts`), not just by
978
+ convention: a raw or extended object throws rather than silently
979
+ persisting. Run `node scripts/verify-ledger-boundary.mjs` (after
980
+ `npm run build`) to check this boundary directly.
981
+
623
982
  ## Per-project decision memory
624
983
 
625
984
  Pattern stores confirmed decisions locally in:
@@ -644,12 +1003,16 @@ The file is organized by project:
644
1003
  "domain": "Airbnb-style rental marketplace",
645
1004
  "action": "custom_built",
646
1005
  "source": "custom",
647
- "timestamp": "2026-08-25T14:32:00.000Z"
1006
+ "timestamp": "2026-08-25T14:32:00.000Z",
1007
+ "time_saved_minutes": 25
648
1008
  }
649
1009
  ]
650
1010
  }
651
1011
  ```
652
1012
 
1013
+ `time_saved_minutes` is omitted from an entry entirely when the calling
1014
+ agent didn't provide one -- it's never backfilled or estimated by Pattern.
1015
+
653
1016
  Each project keeps its 50 most recent decisions. Older entries are
654
1017
  removed as new ones are added.
655
1018
 
@@ -668,12 +1031,13 @@ sensitive information in them. See [SECURITY.md](./SECURITY.md).
668
1031
  A failure to write the decision file is returned as an error from
669
1032
  `record_component_decision`.
670
1033
 
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.
1034
+ **No caching, by design.** Project memory (this file, `memory.json`) does
1035
+ not cache recommendations. A previous decision is only additional context
1036
+ for a new judgment. This is unrelated to the
1037
+ [judgment ledger](#per-project-judgment-ledger)'s bounded cache-hit
1038
+ exception, which lives in a separate file (`ledger.jsonl`) and is always
1039
+ flagged (`served_from_ledger: true`) when it happens — see
1040
+ [Known limitations](#known-limitations) for more.
677
1041
 
678
1042
  ## Security and privacy
679
1043
 
@@ -683,28 +1047,103 @@ recommendations.
683
1047
  Local project memory and the local call log are stored on the machine
684
1048
  running Pattern. They are not sent anywhere by Pattern itself.
685
1049
 
1050
+ The one exception is opt-in telemetry, off by default -- see
1051
+ [Telemetry](#telemetry) below for exactly what it sends and how to turn
1052
+ it on or off.
1053
+
686
1054
  Review [SECURITY.md](./SECURITY.md) before putting sensitive information
687
1055
  into fields such as `component_need`, `domain`, or project IDs.
688
1056
 
1057
+ ## Telemetry
1058
+
1059
+ Off by default. Nothing is sent anywhere for telemetry purposes unless
1060
+ you explicitly set:
1061
+
1062
+ ```
1063
+ PATTERN_TELEMETRY=1
1064
+ ```
1065
+
1066
+ **The one-time notice.** The first time you run this version of Pattern
1067
+ -- whether it's a brand-new install or an upgrade from a version before
1068
+ telemetry existed -- it prints a short notice to stderr explaining all of
1069
+ this and how to opt in. It prints exactly once, ever (tracked by a marker
1070
+ file at `~/.pattern/telemetry_notice_shown`), then never again, regardless
1071
+ of whether you act on it. There's no interactive y/n prompt: Pattern's
1072
+ stdin is the MCP JSON-RPC channel the client uses to talk to it, so
1073
+ blocking on stdin for a keypress would fight the protocol handshake
1074
+ instead of showing a dialog -- a stderr notice is the safe equivalent for
1075
+ a stdio MCP server.
1076
+
1077
+ **Why it exists.** Two things about real usage can't be answered from
1078
+ this repo alone: whether people actually come back and use Pattern on a
1079
+ second or third project on their own, and how often a BYO Anthropic key
1080
+ actually hits a rate limit or runs out of credit in real sessions, not
1081
+ just the one time that happened during manual testing (see
1082
+ [Known limitations](#known-limitations)). Telemetry answers both without
1083
+ requiring anyone to fill out a survey.
1084
+
1085
+ **What gets sent, when enabled:**
1086
+
1087
+ - An anonymous, randomly generated install ID -- a UUID created once and
1088
+ stored at `~/.pattern/install_id` (overridable via
1089
+ `PATTERN_INSTALL_ID_PATH`), never derived from your machine, username,
1090
+ or any other identifying information. This is the only thing that ties
1091
+ two events together as "the same install."
1092
+ - A one-way SHA-256 hash of `project_id`, truncated to 16 hex characters
1093
+ -- never the raw `project_id` string. The hash lets Pattern count how
1094
+ many *distinct* projects one install has used, without ever seeing what
1095
+ those projects are named.
1096
+ - On every `recommend_component` call that reaches the API or the ledger
1097
+ cache-hit shortcut: `verdict`, `confidence`, `reason`,
1098
+ `ensemble_triggered`, `estimated_cost_usd`, and `served_from_ledger` --
1099
+ the same distilled shape already written to the
1100
+ [local call log](#local-call-log), not new information.
1101
+ - On a failed Anthropic API call specifically: the HTTP status code and a
1102
+ coarse classification (`rate_limit`, `insufficient_credit`, or `other`)
1103
+ -- never the request or response body.
1104
+
1105
+ **What never gets sent, telemetry on or off:** `component_need`,
1106
+ `domain`, `framework`, `existing_stack`, `requirements_checked` evidence,
1107
+ the raw `project_id`, or your Anthropic API key.
1108
+
1109
+ **Where it goes.** Events go to Pattern's PostHog project via its public,
1110
+ write-only project key (safe to ship in source -- it can send events, it
1111
+ cannot read data back). Set `PATTERN_POSTHOG_KEY` /
1112
+ `PATTERN_POSTHOG_HOST` to point at a different project, e.g. for
1113
+ self-hosting.
1114
+
1115
+ **Turning it off** is the default -- just don't set `PATTERN_TELEMETRY`.
1116
+ If you'd previously enabled it, unset the variable (or set it to `0`) to
1117
+ go back to fully local.
1118
+
689
1119
  ## Cost
690
1120
 
691
1121
  Pattern uses the Anthropic API, so `recommend_component` has a cost.
692
1122
 
693
1123
  A typical single pass costs about $0.06–$0.10 with Sonnet 5 at current
694
1124
  pricing. Skip-listed primitives cost $0 because they're handled locally
695
- and never reach the API.
1125
+ and never reach the API. A [ledger cache hit](#the-cache-hit-exception)
1126
+ also costs $0, for the same reason -- no API call happens.
696
1127
 
697
1128
  ### The `_meta` field
698
1129
 
699
1130
  Every `recommend_component` and `extract_requirements` response includes
700
- an internal `_meta` block reporting what that call actually spent:
1131
+ an internal `_meta` block reporting what that call actually spent. This
1132
+ is not shown to the user automatically -- the calling agent has to
1133
+ surface it, the same way it's separately instructed to show
1134
+ `install_command` before running it (see
1135
+ [above](#installation-commands-are-not-trusted)). Both tool descriptions
1136
+ say so explicitly: surface `_meta.estimated_cost_usd` after the call,
1137
+ since it's real spend against the user's own API key, not internal
1138
+ bookkeeping.
701
1139
 
702
1140
  ```json
703
1141
  {
704
1142
  "total_ms": 41516,
705
1143
  "breakdown_ms": { "extract": 5006, "search": 3114, "score": 33396 },
706
1144
  "tokens_used": { "input": 8400, "output": 620 },
707
- "estimated_cost_usd": 0.14
1145
+ "estimated_cost_usd": 0.14,
1146
+ "scoring_fetch": { "attempted": true, "succeeded": true, "url": "https://ui.shadcn.com/docs/components/..." }
708
1147
  }
709
1148
  ```
710
1149
 
@@ -719,6 +1158,14 @@ an internal `_meta` block reporting what that call actually spent:
719
1158
  discounts.
720
1159
  - `breakdown_ms` -- how `total_ms` splits across `recommend_component`'s
721
1160
  three internal phases.
1161
+ - `scoring_fetch` -- whether step 4's single candidate-verification fetch
1162
+ (see [Fetch-grounded scoring](#fetch-grounded-scoring-and-reference-verification)
1163
+ below) actually happened for this response. `url` is `null` when
1164
+ `attempted` is `false` (no real candidate to verify, e.g. `reason:
1165
+ "no_candidates_found"` or `"skip_list"`). This is a diagnostic only --
1166
+ Pattern never uses it to auto-correct `requirements_checked` after the
1167
+ fact, since there's no safe fallback value for an unverified met/not-met
1168
+ call the way there is for a reference URL.
722
1169
 
723
1170
  **How `breakdown_ms` is measured, and its one real caveat.** The bundled
724
1171
  call runs extraction, search, and scoring inside a single model turn
@@ -746,6 +1193,10 @@ not the wall-clock time you waited. The three ensemble passes run with the
746
1193
  2nd and 3rd concurrent, so perceived latency is closer to ~2x one pass,
747
1194
  not the ~3x `total_ms` will show. Cost and token spend are genuinely
748
1195
  additive across reruns, which is what `_meta` is reporting there.
1196
+ `scoring_fetch` is the one exception -- it isn't summed (a fetch either
1197
+ happened for the specific pass whose evidence became the returned
1198
+ `requirements_checked`, or it didn't), so it reports that winning pass's
1199
+ own value, not an aggregate across all three.
749
1200
 
750
1201
  Three things help keep the cost down without changing the decision process.
751
1202
 
@@ -770,18 +1221,33 @@ shadcn/ui, 21st.dev, and ReUI are searched in the same turn rather than
770
1221
  sequentially, which reduces how much conversation context needs to be
771
1222
  sent repeatedly.
772
1223
 
773
- ### Reference verification
774
-
775
- Pattern allows up to 2 `web_fetch` calls, used only to verify reference
776
- URLs.
1224
+ ### Fetch-grounded scoring and reference verification
1225
+
1226
+ Pattern allows up to 3 `web_fetch` calls per pass: 1 reserved for scoring,
1227
+ 2 reserved for reference verification (1 for Mobbin, 1 for Figma
1228
+ Community).
1229
+
1230
+ Before finalizing coverage, Pattern fetches the best-fitting candidate's
1231
+ own real docs/source page once and re-checks the checklist against that
1232
+ page, not just the search-result snippet it started with. This exists
1233
+ because search-result descriptions can both overstate a component's real
1234
+ capabilities and miss real ones it actually has -- both were observed in
1235
+ testing on the same case (an invented feature claim and a missed real
1236
+ one). If the fetch fails, or there's no confirmed URL to fetch, Pattern
1237
+ falls back to search-only evidence and says so in the affected items.
1238
+
1239
+ Each result's `_meta.scoring_fetch` reports whether this fetch actually
1240
+ happened for that response (`{ attempted, succeeded, url }`) -- it's a
1241
+ diagnostic, not something Pattern uses to auto-correct individual
1242
+ requirement judgments. Unlike a reference URL (which has a safe fallback:
1243
+ the category page), there's no safe fallback for an unverified met/not-met
1244
+ call, so nothing is silently corrected -- `scoring_fetch` just tells you
1245
+ whether the grounding actually ran.
777
1246
 
778
1247
  A fetch can read up to 15,000 content tokens. `web_fetch` has no separate
779
1248
  per-call fee; the cost comes from the content added to the model's
780
1249
  context.
781
1250
 
782
- Pattern does not use `web_fetch` during requirement scoring. It's
783
- reserved for verifying reference links.
784
-
785
1251
  ### Choosing a cheaper model
786
1252
 
787
1253
  You can change the model with:
@@ -1047,22 +1513,32 @@ pipeline is still fully bundled; nothing about this evaluation changed.
1047
1513
 
1048
1514
  ### No caching, by design
1049
1515
 
1050
- Every recommendation searches and scores again.
1516
+ Every recommendation searches and scores again -- with one bounded
1517
+ exception (see below).
1051
1518
 
1052
1519
  This means a recommendation can change as component libraries change.
1053
1520
  For example, a later shadcn/ui release can introduce a component that
1054
1521
  changes a previous `custom_build` result.
1055
1522
 
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.
1523
+ Do not build a second, unbounded cache of recommendations at the
1524
+ calling-agent layer on top of Pattern's own. If you add caching there,
1525
+ keep it session-scoped.
1060
1526
 
1061
1527
  [Project decision memory](#per-project-decision-memory) does not change
1062
1528
  this. It provides context from previous decisions, but every
1063
1529
  `recommend_component` call still performs a fresh search and scoring
1064
1530
  pass.
1065
1531
 
1532
+ The one deliberate exception is the
1533
+ [judgment ledger's cache-hit path](#the-cache-hit-exception): a later
1534
+ call matching an exact, recent, high-confidence prior judgment can be
1535
+ served without a fresh search+score. It's bounded (exact
1536
+ component_need/domain/framework/conventions match, a staleness TTL) and
1537
+ always self-identifies via `served_from_ledger: true` and
1538
+ `reason: "ledger_cache_hit"` -- so a calling agent that wants a guaranteed
1539
+ fresh check on every call should look for that flag and treat it the same
1540
+ as any other verdict it wants to double-check.
1541
+
1066
1542
  ### The skip-list is still evolving
1067
1543
 
1068
1544
  The primitive skip-list is a starting point and has not yet been