pattern-mcp 0.12.1 → 0.14.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
@@ -15,22 +15,27 @@ design reference.
15
15
 
16
16
  [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)
17
17
 
18
- **Current release: v0.12.0** — When you run `npx pattern-mcp` for the
19
- first time, Pattern now shows the optional enforcement boundary
20
- alongside the telemetry notice. This boundary includes a `PreToolUse`
21
- hook and a matching CI check. Together, they can require a decision for
22
- each new component instead of only recording it. See
23
- [Enforcement boundary: hook + CI gate](#enforcement-boundary-hook--ci-gate)
18
+ **Current release: v0.14.0** — a crash on startup is now diagnosable
19
+ instead of silent (a new, coarse `pattern_cli_exited` telemetry event),
20
+ Pattern warns at startup if `ANTHROPIC_API_KEY` is missing or clearly
21
+ malformed instead of only surfacing a raw 401 mid-call, and a 429 from
22
+ the Anthropic API now gets one respectful retry (honoring `Retry-After`)
23
+ before it's raised. Previously: v0.13.0 added `npx pattern-mcp init`,
24
+ which sets up the connection to your MCP client for you (Claude Code,
25
+ Claude Desktop, Cursor detected and configured automatically; Codex CLI
26
+ gets manual instructions). Running `npx pattern-mcp` bare in your own
27
+ terminal also tells you it needs a client connected, instead of silently
28
+ sitting there. See
29
+ [Connect Pattern to your MCP client](#connect-pattern-to-your-mcp-client)
24
30
  for more details.
25
31
 
26
32
  <details>
27
33
  <summary><strong>Contents</strong> (click to expand)</summary>
28
34
 
29
35
  - [Install](#install) · [What Pattern Does](#what-pattern-does) · [How it works](#how-it-works) · [Quick Start](#quick-start) · [Try it](#try-it) · [Validation examples](#validation-examples)
30
- - **Make the judgment call:** [`recommend_component`](#tool-recommend_component) · [`extract_requirements`](#tool-extract_requirements)
31
- - **Track cost and outcome:** [`record_component_decision`](#tool-record_component_decision) · [`read_ledger`](#tool-read_ledger) · [`report_build_cost`](#tool-report_build_cost) · [`report_outcome_proxy`](#tool-report_outcome_proxy) · [Feature cost attribution](#feature-cost-attribution) · [Outcome proxies](#outcome-proxies) · [Per-project judgment ledger](#per-project-judgment-ledger)
32
- - **Verify and export old decisions:** [`check_ledger_liveness`](#tool-check_ledger_liveness) · [`sweep_ledger_liveness`](#tool-sweep_ledger_liveness) · [`export_ledger_provenance`](#tool-export_ledger_provenance) · [`backfill_ledger_snapshot_ref`](#tool-backfill_ledger_snapshot_ref) · [`post_ledger_provenance_to_github`](#tool-post_ledger_provenance_to_github) · [Ledger integrity and decision provenance](#ledger-integrity-and-decision-provenance) (design overview — start here for how the five fit together)
33
- - [Enforcement boundary: hook + CI gate](#enforcement-boundary-hook--ci-gate) (new in v0.10.0 — require the call, don't just log it)
36
+ - [Enforcement boundary: hook + CI gate](#enforcement-boundary-hook--ci-gate) (require the call, don't just log it)
37
+ - **Core tools** (on by default -- see [Tool tiers](#tool-tiers)): [`recommend_component`](#tool-recommend_component) · [`extract_requirements`](#tool-extract_requirements) · [`record_component_decision`](#tool-record_component_decision)
38
+ - **[Advanced tools](#advanced-tools)** (`PATTERN_TOOLS=full`): [`register_design_system`](#tool-register_design_system) · [`read_ledger`](#tool-read_ledger) · [`report_build_cost`](#tool-report_build_cost) · [`report_outcome_proxy`](#tool-report_outcome_proxy) · [Feature cost attribution](#feature-cost-attribution) · [Outcome proxies](#outcome-proxies) · [Per-project judgment ledger](#per-project-judgment-ledger) · [`check_ledger_liveness`](#tool-check_ledger_liveness) · [`sweep_ledger_liveness`](#tool-sweep_ledger_liveness) · [`export_ledger_provenance`](#tool-export_ledger_provenance) · [`backfill_ledger_snapshot_ref`](#tool-backfill_ledger_snapshot_ref) · [`post_ledger_provenance_to_github`](#tool-post_ledger_provenance_to_github) · [Ledger integrity and decision provenance](#ledger-integrity-and-decision-provenance)
34
39
  - [Per-project decision memory](#per-project-decision-memory) · [Security and privacy](#security-and-privacy) · [Telemetry](#telemetry)
35
40
  - **Cost:** [The `_meta` field](#the-_meta-field) · [Prompt caching](#prompt-caching) · [Measured cache and fetch behavior](#measured-cache-and-fetch-behavior) · [Search limits](#search-limits) · [Ensemble cost](#ensemble-cost-boundary-risk-cases-only) · [Session call cap](#session-call-cap)
36
41
  - [Local call log](#local-call-log) · [Known limitations](#known-limitations)
@@ -58,54 +63,38 @@ whether to:
58
63
 
59
64
  Pattern is designed for agents to use **while they are building**.
60
65
 
61
- It exposes eleven tools, in three groups:
66
+ It exposes twelve tools. Three are on by default -- the ones the
67
+ install → recommend → enforce → build path actually needs -- and the
68
+ rest reveal themselves once you need them. See [Tool
69
+ tiers](#tool-tiers).
62
70
 
63
- **Make the judgment call.**
71
+ **Core, on by default.**
64
72
 
65
73
  - `recommend_component` — evaluates a UI component need and returns a
66
74
  structured recommendation.
67
75
  - `extract_requirements` — runs just the requirement-extraction step on
68
76
  its own, so you can inspect or hand-edit the checklist before
69
77
  `recommend_component` spends its search+score budget on it.
70
-
71
- **Track what it cost and what actually happened.**
72
-
73
78
  - `record_component_decision` — records what the agent actually did so
74
79
  future recommendations in the same project can take that decision into
75
80
  account.
76
- - `read_ledger` — lists past `recommend_component` judgments for a
77
- `project_id`, including any that were served from the ledger cache (see
78
- [Per-project judgment ledger](#per-project-judgment-ledger)); pass
79
- `feature_id` instead of browsing by keyword to get a full cost rollup for
80
- one feature (see [Tool: `report_build_cost`](#tool-report_build_cost)).
81
- - `report_build_cost` — self-reports the end-to-end build cost for one
82
- feature, so cost incurred after Pattern's own verdict (the actual
83
- scaffold/install/build) is still attributable back to it.
84
- - `report_outcome_proxy` self-reports a value signal (rework, time to
85
- merge, kept-vs-replaced) for one feature, deliberately independent of
86
- Pattern's own verdict -- see [Outcome
87
- proxies](#outcome-proxies).
88
-
89
- **Verify old decisions still hold up, and export a record of them.** See
90
- [Ledger integrity and decision
91
- provenance](#ledger-integrity-and-decision-provenance) for how these five
92
- fit together.
93
-
94
- - `check_ledger_liveness` — checks whether a ledger entry's recorded
95
- `file_path` still exists and still references its `chosen_candidate`.
96
- - `sweep_ledger_liveness` — batch version of `check_ledger_liveness`
97
- across a whole project (or every project in the ledger), plus
98
- dangling-cluster detection. Meant to be invoked by your own cron/CI, not
99
- something Pattern schedules itself.
100
- - `export_ledger_provenance` — formats one ledger entry as a stable
101
- markdown block (checklist, candidates, verdict, `snapshot_ref`) you can
102
- paste into a PR or issue by hand.
103
- - `backfill_ledger_snapshot_ref` — best-effort `snapshot_ref`
104
- reconstruction for entries written before that field existed.
105
- - `post_ledger_provenance_to_github` — posts an `export_ledger_provenance`
106
- artifact as a real comment on a GitHub PR/issue, idempotently. The one
107
- tool here with a real, visible side effect outside your own machine;
108
- confirm with the user before calling it.
81
+
82
+ **[Advanced](#advanced-tools), behind `PATTERN_TOOLS=full`.** Pointing
83
+ Pattern at your own design system, cost/outcome tracking, and ledger
84
+ provenance/liveness. See [Advanced tools](#advanced-tools) for the full
85
+ list.
86
+
87
+ ### Tool tiers
88
+
89
+ By default Pattern's `tools/list` response advertises only the three
90
+ core tools above, so a first-time agent sees a small, obvious surface
91
+ instead of all twelve at once. Every tool still works when called
92
+ directly, tiering only changes what gets *advertised* -- so a script or
93
+ an agent that already knows a tool's name (e.g. from this README) can
94
+ still call `register_design_system` or `read_ledger` without setting
95
+ anything. Set `PATTERN_TOOLS=full` in the server's environment to
96
+ advertise all twelve tools immediately, e.g. for the "verify and export
97
+ old decisions" or cost-tracking workflows described below.
109
98
 
110
99
  ## How it works
111
100
 
@@ -280,6 +269,32 @@ The server command is:
280
269
  npx pattern-mcp
281
270
  ```
282
271
 
272
+ #### Automatic setup
273
+
274
+ ```bash
275
+ npx pattern-mcp init
276
+ ```
277
+
278
+ Detects which clients are installed and offers to connect each one:
279
+
280
+ - **Claude Code** -- runs `claude mcp add` for you (asks whether to make
281
+ Pattern available in every project or just this one); skips if already
282
+ connected (`claude mcp list` already shows it).
283
+ - **Claude Desktop** and **Cursor** -- merges a `pattern` entry into the
284
+ client's own config file, showing the exact change before writing it
285
+ and never touching any other server already configured there.
286
+ - **Codex CLI** -- prints the config snippet to add by hand (Codex's
287
+ config is TOML; this doesn't auto-edit it).
288
+
289
+ Optionally pastes your `ANTHROPIC_API_KEY` into whichever configs you set
290
+ up (visible in plain text as you type it, and in the files it writes) --
291
+ press Enter to skip and add it yourself later instead. Run
292
+ non-interactively with `--yes` (skips the API key prompt entirely,
293
+ accepts every detected client).
294
+
295
+ If you'd rather do it by hand, or `init` didn't detect your client, the
296
+ per-client instructions below cover the same configs manually.
297
+
283
298
  #### Claude Code
284
299
 
285
300
  You can add Pattern to your project's `.mcp.json` or register it with the
@@ -411,6 +426,117 @@ Together, these cover different outcomes, including clear matches,
411
426
  false-positive-prone searches, no candidates, and decisions close to the
412
427
  threshold.
413
428
 
429
+ ## Enforcement boundary: hook + CI gate
430
+
431
+ **Decisions can be enforced, not just tracked.** An opt-in `PreToolUse`
432
+ hook can block a new component from being written until a matching
433
+ ledger entry exists; a paired GitHub Action can also fail the PR if that
434
+ decision record isn't committed alongside the code.
435
+
436
+ **The gap this closes:** SKILL.md instructs the calling agent to call
437
+ `recommend_component` before scaffolding a new, non-trivial UI component,
438
+ but nothing before this feature *enforced* that -- an agent could simply
439
+ skip the call, and nothing server-side would know. This is opt-in and
440
+ Claude-Code-specific for the hook half; a consuming repo that never wires
441
+ either piece up gets Pattern exactly as it worked before, and any other
442
+ MCP host (Cursor, Codex, etc.) is entirely unaffected either way.
443
+
444
+ **Set it up with one command:**
445
+
446
+ ```bash
447
+ npx pattern-check-gate init
448
+ ```
449
+
450
+ Confirms each step independently rather than one blanket "proceed?", and
451
+ never auto-commits -- review with `git status`/`git diff` and commit
452
+ yourself when ready:
453
+
454
+ 1. Confirms a project id (pre-filled from `package.json`'s `name`, or
455
+ your git remote/directory name -- accept it or type your own).
456
+ 2. Writes or merges `.claude/settings.json` -- if one already exists, it
457
+ parses it, leaves any unrelated hooks untouched, and only appends the
458
+ `PreToolUse` entry if it isn't already there (safe to rerun).
459
+ 3. Writes `.github/workflows/pattern-gate.yml`, if a GitHub remote is
460
+ detected and the file doesn't already exist with different content
461
+ (never silently overwritten).
462
+ 4. Asks, as its own explicit yes/no: **mark the check required in branch
463
+ protection?** Needs `gh` installed and authenticated with admin rights
464
+ on the repo; skips with clear next steps otherwise. Deliberately only
465
+ offered when no branch protection exists yet on the default branch --
466
+ GitHub's branch-protection API replaces the *entire* configuration on
467
+ write, not just the required-checks list, so this refuses to guess at
468
+ merging into whatever you already have rather than risk silently
469
+ dropping an unrelated setting (e.g. required PR reviews). If
470
+ protection already exists, add `pattern-gate` to it by hand instead.
471
+
472
+ Run non-interactively with `--yes` (accepts every safe default; branch
473
+ protection is never auto-confirmed even then -- it's the one step that
474
+ reaches outside your local filesystem into real, shared GitHub config).
475
+
476
+ **You don't have to find this section to learn this exists.** Every
477
+ `npx pattern-mcp` run surfaces it at the same first-run moment as the
478
+ [telemetry notice](#telemetry):
479
+
480
+ - **Always**, in every context, including when a real MCP client has
481
+ spawned this as a subprocess: a one-time, non-blocking stderr mention
482
+ that the enforcement boundary exists and the command above sets it up.
483
+ Same "prints once, gated by a marker file" discipline as the telemetry
484
+ notice -- tracked at `~/.pattern/enforcement_notice_shown`
485
+ (`PATTERN_ENFORCEMENT_NOTICE_PATH` to override), never repeats after
486
+ that regardless of whether you act on it.
487
+ - **Only when stdin is a real terminal** (`process.stdin.isTTY`) --
488
+ meaning a human ran `npx pattern-mcp` bare in their own shell, never
489
+ true for a real MCP client's spawned subprocess -- it also offers a
490
+ genuine interactive prompt right there: *"Set it up now?"* A yes runs
491
+ the exact same `init` flow described above. The same JSON-RPC-channel
492
+ constraint that rules out an interactive telemetry prompt (see
493
+ [Telemetry](#telemetry)) applies here too, which is why this only ever
494
+ asks when nothing is piping protocol messages into stdin to begin
495
+ with.
496
+
497
+ Set `PATTERN_NO_ENFORCEMENT_NOTICE` to suppress both halves. See
498
+ `offerEnforcementSetupOnce` in `src/init-enforcement.ts` for the
499
+ implementation.
500
+
501
+ **Or set it up by hand**, two pieces, neither installed automatically:
502
+
503
+ - **`.claude/settings.json`** wired to run `npx --yes
504
+ pattern-check-gate-hook` on `PreToolUse` (see
505
+ `templates/claude-settings/settings.json` for the exact shape) -- a
506
+ Claude Code hook that runs on `Write`/`Edit` calls. For a genuinely new
507
+ `.tsx`/`.jsx` file that exports a non-trivial component, it looks up a
508
+ ledger entry (via `~/.pattern/ledger.jsonl`, same as everywhere else in
509
+ Pattern) whose `file_path` matches the file being written. A match
510
+ writes a receipt and allows the write; no match blocks it with a reason
511
+ fed back to the model as retryable guidance, not a hard failure.
512
+ **This is the one new exception where Pattern writes into your repo**
513
+ (`.pattern/receipts/<feature_id>.json`) -- everything else described in
514
+ this README is read-only. `project_id` no longer needs to be set by
515
+ hand either -- it's derived the same way `init` pre-fills it (see
516
+ `src/project-id.ts`); set `PATTERN_PROJECT_ID` only to override that.
517
+ - **`templates/github-workflows/pattern-gate.yml`** -- a required PR
518
+ check that reads the same receipt files back out of the diff. It never
519
+ touches `~/.pattern/` (not reachable from a CI runner) and needs no
520
+ `GITHUB_TOKEN` -- it trusts the committed receipt as the artifact of
521
+ record, the same way it would trust a committed test fixture.
522
+
523
+ The join between the two depends on `file_path` being passed to
524
+ `recommend_component`/`record_component_decision` -- if it's omitted, the
525
+ gate has nothing to match against and fails closed (blocks) rather than
526
+ guessing. Pass `file_path` whenever you know it.
527
+
528
+ An escape hatch exists for both a whole-hook kill switch
529
+ (`PATTERN_NO_ENFORCEMENT_HOOK`, local only -- does not affect the CI
530
+ check) and a per-file override (a `// pattern-mcp:override reason="..."`
531
+ comment) -- the override still writes a receipt recording
532
+ `manual_override: true` and the reason, so it stays visible rather than
533
+ silent. See `src/component-gate.ts`, `src/gate-receipt.ts`,
534
+ `src/check-gate.ts` (the `pattern-check-gate` CLI, this project's first
535
+ entry point separate from the stdio MCP server), `src/check-gate-hook.ts`,
536
+ and `src/init-enforcement.ts` for the implementation, and BACKLOG.md's
537
+ "Enforcement boundary" entries for the fuller design writeup.
538
+
539
+
414
540
  ## Tool: `recommend_component`
415
541
 
416
542
  ### Input
@@ -609,114 +735,6 @@ The calling agent should:
609
735
 
610
736
  See [SECURITY.md](./SECURITY.md) for more details.
611
737
 
612
- ## Tool: `register_design_system`
613
-
614
- Points `recommend_component` at *this project's own* design system instead
615
- of shadcn/ui, 21st.dev, and ReUI -- for a solo dev with their own component
616
- library or design spec who wants Pattern's coverage scoring against
617
- candidates they'll actually use, not external libraries they won't. This is
618
- the Solo Dev architecture from `pattern-solo-design-system-architecture.md`:
619
- local, per-project, one-or-the-other -- registering a design system for a
620
- `project_id` **replaces** external-library scoring for that project
621
- entirely, it does not add to it. There's no shared/remote ledger, no
622
- multi-user attribution, and no team auth in this scope -- those are
623
- deliberately deferred to a team phase, only if this use case proves out.
624
-
625
- ### Input
626
-
627
- Exactly one of `manifest_path` or `directory_path` is required, both
628
- relative to the project root (`PATTERN_PROJECT_ROOT`, defaults to this
629
- server's working directory) -- never an absolute path.
630
-
631
- ```json
632
- {
633
- "project_id": "my-booking-app",
634
- "directory_path": "src/components"
635
- }
636
- ```
637
-
638
- - **`manifest_path`** -- a components manifest. Two recognized shapes:
639
- - A hand-authored JSON array of `{name, props, description,
640
- usage_example}` objects, optionally wrapped in `{"components": [...]}`.
641
- - A Storybook-exported `stories.json`/`index.json` file (an object with a
642
- top-level `entries` or `stories` map). Component names only in this
643
- case -- Storybook's basic export doesn't carry prop data, so candidates
644
- from this path start with an empty `props` list.
645
- - **`directory_path`** -- a directory of real component source files,
646
- scanned recursively for `.jsx`/`.tsx`/`.js`/`.ts` files (excluding
647
- `node_modules`/`dist`/`build`/`.git` and `.test.`/`.spec.`/`.stories.`
648
- files). Each exported, uppercase-named function or const component found
649
- becomes a candidate, with props read in priority order from a
650
- `<Name>Props` interface/type, a `.propTypes` block, or (last resort) the
651
- component's own destructured parameters. This is a heuristic scan, not a
652
- full parser -- a sparse or partial props list for some components is
653
- expected, not a bug, especially on plain JS with no prop typing at all.
654
-
655
- ### Output
656
-
657
- ```json
658
- {
659
- "status": "registered",
660
- "registration": {
661
- "project_id": "my-booking-app",
662
- "source_kind": "directory_scan",
663
- "source_path": "src/components",
664
- "registered_at": "2026-09-03T18:04:11.201Z",
665
- "candidate_count": 29,
666
- "candidates": [
667
- { "name": "ReferralBanner", "props": ["code", "bonusAmount"], "description": null, "usage_example": null, "file_path": "rewards/ReferralBanner.jsx" }
668
- ]
669
- }
670
- }
671
- ```
672
-
673
- Registering overwrites (does not merge with) any prior registration for the
674
- same `project_id`. Once registered, `recommend_component` scores ONLY
675
- against these candidates for calls with this `project_id` -- no separate
676
- flag needed, it's automatic based on `project_id` alone, and step 3's live
677
- web search is skipped entirely (`web_search` is still available, but
678
- reserved for a `custom_build` verdict's Mobbin/Figma Community reference
679
- grounding, same as the external-library path). A `use_existing` verdict
680
- scored this way always carries `"source": "design_system"` on the
681
- resulting ledger entry, set server-side regardless of what the model wrote,
682
- so `read_ledger` and `export_ledger_provenance` can match on it reliably.
683
-
684
- This only writes local config to `~/.pattern/design_systems.json` (override
685
- with `PATTERN_DESIGN_SYSTEMS_PATH`) -- it never calls the Anthropic API.
686
- Registration is a point-in-time snapshot, not a live link: re-run this
687
- whenever the design system's own components change meaningfully.
688
-
689
- ### A safety net for a missed match
690
-
691
- The model can occasionally say `custom_build`/`no_candidates_found`
692
- against a registered design system even when a real match is sitting
693
- right there in its own prompt -- a reading-comprehension miss over its own
694
- known-complete candidate list, not evidence the list was actually empty.
695
- When this happens, `recommend_component`'s response may carry a
696
- `design_system_recall_check` field: a deterministic, zero-cost, local
697
- keyword-overlap check (component name, props, description/usage_example
698
- vs. `component_need`/`domain`) run automatically whenever reason is
699
- `no_candidates_found` in this mode.
700
-
701
- ```json
702
- {
703
- "verdict": "custom_build",
704
- "reason": "no_candidates_found",
705
- "design_system_recall_check": {
706
- "possible_missed_candidates": [
707
- { "name": "ReferralBanner", "shared_keywords": ["referral", "bonus"] }
708
- ],
709
- "note": "These registered design-system candidates share keywords with this component_need but were not selected as a match -- the verdict may have missed a real one. This is a weak, keyword-only signal, not proof of an actual match: double-check these candidates yourself (or re-run this call) before trusting custom_build here."
710
- }
711
- }
712
- ```
713
-
714
- This never overrides the verdict -- a shared keyword is weak evidence, not
715
- proof of a real match -- it only surfaces the risk so you (or the calling
716
- agent) know to double-check before accepting a `custom_build` verdict at
717
- face value. Absent entirely when there's no overlap, or outside
718
- design-system mode.
719
-
720
738
  ## Tool: `extract_requirements`
721
739
 
722
740
  Runs only the requirement-extraction step `recommend_component` normally
@@ -834,6 +852,118 @@ Anthropic API call.
834
852
  }
835
853
  ```
836
854
 
855
+ ## Advanced tools
856
+
857
+ Not advertised by default -- set `PATTERN_TOOLS=full` to see these in `tools/list`, or call them directly by name at any time (see [Tool tiers](#tool-tiers)).
858
+
859
+ ## Tool: `register_design_system`
860
+
861
+ Points `recommend_component` at *this project's own* design system instead
862
+ of shadcn/ui, 21st.dev, and ReUI -- for a solo dev with their own component
863
+ library or design spec who wants Pattern's coverage scoring against
864
+ candidates they'll actually use, not external libraries they won't. This is
865
+ the Solo Dev architecture from `pattern-solo-design-system-architecture.md`:
866
+ local, per-project, one-or-the-other -- registering a design system for a
867
+ `project_id` **replaces** external-library scoring for that project
868
+ entirely, it does not add to it. There's no shared/remote ledger, no
869
+ multi-user attribution, and no team auth in this scope -- those are
870
+ deliberately deferred to a team phase, only if this use case proves out.
871
+
872
+ ### Input
873
+
874
+ Exactly one of `manifest_path` or `directory_path` is required, both
875
+ relative to the project root (`PATTERN_PROJECT_ROOT`, defaults to this
876
+ server's working directory) -- never an absolute path.
877
+
878
+ ```json
879
+ {
880
+ "project_id": "my-booking-app",
881
+ "directory_path": "src/components"
882
+ }
883
+ ```
884
+
885
+ - **`manifest_path`** -- a components manifest. Two recognized shapes:
886
+ - A hand-authored JSON array of `{name, props, description,
887
+ usage_example}` objects, optionally wrapped in `{"components": [...]}`.
888
+ - A Storybook-exported `stories.json`/`index.json` file (an object with a
889
+ top-level `entries` or `stories` map). Component names only in this
890
+ case -- Storybook's basic export doesn't carry prop data, so candidates
891
+ from this path start with an empty `props` list.
892
+ - **`directory_path`** -- a directory of real component source files,
893
+ scanned recursively for `.jsx`/`.tsx`/`.js`/`.ts` files (excluding
894
+ `node_modules`/`dist`/`build`/`.git` and `.test.`/`.spec.`/`.stories.`
895
+ files). Each exported, uppercase-named function or const component found
896
+ becomes a candidate, with props read in priority order from a
897
+ `<Name>Props` interface/type, a `.propTypes` block, or (last resort) the
898
+ component's own destructured parameters. This is a heuristic scan, not a
899
+ full parser -- a sparse or partial props list for some components is
900
+ expected, not a bug, especially on plain JS with no prop typing at all.
901
+
902
+ ### Output
903
+
904
+ ```json
905
+ {
906
+ "status": "registered",
907
+ "registration": {
908
+ "project_id": "my-booking-app",
909
+ "source_kind": "directory_scan",
910
+ "source_path": "src/components",
911
+ "registered_at": "2026-09-03T18:04:11.201Z",
912
+ "candidate_count": 29,
913
+ "candidates": [
914
+ { "name": "ReferralBanner", "props": ["code", "bonusAmount"], "description": null, "usage_example": null, "file_path": "rewards/ReferralBanner.jsx" }
915
+ ]
916
+ }
917
+ }
918
+ ```
919
+
920
+ Registering overwrites (does not merge with) any prior registration for the
921
+ same `project_id`. Once registered, `recommend_component` scores ONLY
922
+ against these candidates for calls with this `project_id` -- no separate
923
+ flag needed, it's automatic based on `project_id` alone, and step 3's live
924
+ web search is skipped entirely (`web_search` is still available, but
925
+ reserved for a `custom_build` verdict's Mobbin/Figma Community reference
926
+ grounding, same as the external-library path). A `use_existing` verdict
927
+ scored this way always carries `"source": "design_system"` on the
928
+ resulting ledger entry, set server-side regardless of what the model wrote,
929
+ so `read_ledger` and `export_ledger_provenance` can match on it reliably.
930
+
931
+ This only writes local config to `~/.pattern/design_systems.json` (override
932
+ with `PATTERN_DESIGN_SYSTEMS_PATH`) -- it never calls the Anthropic API.
933
+ Registration is a point-in-time snapshot, not a live link: re-run this
934
+ whenever the design system's own components change meaningfully.
935
+
936
+ ### A safety net for a missed match
937
+
938
+ The model can occasionally say `custom_build`/`no_candidates_found`
939
+ against a registered design system even when a real match is sitting
940
+ right there in its own prompt -- a reading-comprehension miss over its own
941
+ known-complete candidate list, not evidence the list was actually empty.
942
+ When this happens, `recommend_component`'s response may carry a
943
+ `design_system_recall_check` field: a deterministic, zero-cost, local
944
+ keyword-overlap check (component name, props, description/usage_example
945
+ vs. `component_need`/`domain`) run automatically whenever reason is
946
+ `no_candidates_found` in this mode.
947
+
948
+ ```json
949
+ {
950
+ "verdict": "custom_build",
951
+ "reason": "no_candidates_found",
952
+ "design_system_recall_check": {
953
+ "possible_missed_candidates": [
954
+ { "name": "ReferralBanner", "shared_keywords": ["referral", "bonus"] }
955
+ ],
956
+ "note": "These registered design-system candidates share keywords with this component_need but were not selected as a match -- the verdict may have missed a real one. This is a weak, keyword-only signal, not proof of an actual match: double-check these candidates yourself (or re-run this call) before trusting custom_build here."
957
+ }
958
+ }
959
+ ```
960
+
961
+ This never overrides the verdict -- a shared keyword is weak evidence, not
962
+ proof of a real match -- it only surfaces the risk so you (or the calling
963
+ agent) know to double-check before accepting a `custom_build` verdict at
964
+ face value. Absent entirely when there's no overlap, or outside
965
+ design-system mode.
966
+
837
967
  ## Tool: `read_ledger`
838
968
 
839
969
  Lists past `recommend_component` judgments for a `project_id` -- every
@@ -1592,116 +1722,6 @@ the source line, most recent record wins at read time" convention as
1592
1722
  layered onto `ledger.jsonl`'s own entries at read time -- the ledger line
1593
1723
  itself is never rewritten.
1594
1724
 
1595
- ## Enforcement boundary: hook + CI gate
1596
-
1597
- **Decisions can be enforced, not just tracked.** An opt-in `PreToolUse`
1598
- hook can block a new component from being written until a matching
1599
- ledger entry exists; a paired GitHub Action can also fail the PR if that
1600
- decision record isn't committed alongside the code.
1601
-
1602
- **The gap this closes:** SKILL.md instructs the calling agent to call
1603
- `recommend_component` before scaffolding a new, non-trivial UI component,
1604
- but nothing before this feature *enforced* that -- an agent could simply
1605
- skip the call, and nothing server-side would know. This is opt-in and
1606
- Claude-Code-specific for the hook half; a consuming repo that never wires
1607
- either piece up gets Pattern exactly as it worked before, and any other
1608
- MCP host (Cursor, Codex, etc.) is entirely unaffected either way.
1609
-
1610
- **Set it up with one command:**
1611
-
1612
- ```bash
1613
- npx pattern-check-gate init
1614
- ```
1615
-
1616
- Confirms each step independently rather than one blanket "proceed?", and
1617
- never auto-commits -- review with `git status`/`git diff` and commit
1618
- yourself when ready:
1619
-
1620
- 1. Confirms a project id (pre-filled from `package.json`'s `name`, or
1621
- your git remote/directory name -- accept it or type your own).
1622
- 2. Writes or merges `.claude/settings.json` -- if one already exists, it
1623
- parses it, leaves any unrelated hooks untouched, and only appends the
1624
- `PreToolUse` entry if it isn't already there (safe to rerun).
1625
- 3. Writes `.github/workflows/pattern-gate.yml`, if a GitHub remote is
1626
- detected and the file doesn't already exist with different content
1627
- (never silently overwritten).
1628
- 4. Asks, as its own explicit yes/no: **mark the check required in branch
1629
- protection?** Needs `gh` installed and authenticated with admin rights
1630
- on the repo; skips with clear next steps otherwise. Deliberately only
1631
- offered when no branch protection exists yet on the default branch --
1632
- GitHub's branch-protection API replaces the *entire* configuration on
1633
- write, not just the required-checks list, so this refuses to guess at
1634
- merging into whatever you already have rather than risk silently
1635
- dropping an unrelated setting (e.g. required PR reviews). If
1636
- protection already exists, add `pattern-gate` to it by hand instead.
1637
-
1638
- Run non-interactively with `--yes` (accepts every safe default; branch
1639
- protection is never auto-confirmed even then -- it's the one step that
1640
- reaches outside your local filesystem into real, shared GitHub config).
1641
-
1642
- **You don't have to find this section to learn this exists.** Every
1643
- `npx pattern-mcp` run surfaces it at the same first-run moment as the
1644
- [telemetry notice](#telemetry):
1645
-
1646
- - **Always**, in every context, including when a real MCP client has
1647
- spawned this as a subprocess: a one-time, non-blocking stderr mention
1648
- that the enforcement boundary exists and the command above sets it up.
1649
- Same "prints once, gated by a marker file" discipline as the telemetry
1650
- notice -- tracked at `~/.pattern/enforcement_notice_shown`
1651
- (`PATTERN_ENFORCEMENT_NOTICE_PATH` to override), never repeats after
1652
- that regardless of whether you act on it.
1653
- - **Only when stdin is a real terminal** (`process.stdin.isTTY`) --
1654
- meaning a human ran `npx pattern-mcp` bare in their own shell, never
1655
- true for a real MCP client's spawned subprocess -- it also offers a
1656
- genuine interactive prompt right there: *"Set it up now?"* A yes runs
1657
- the exact same `init` flow described above. The same JSON-RPC-channel
1658
- constraint that rules out an interactive telemetry prompt (see
1659
- [Telemetry](#telemetry)) applies here too, which is why this only ever
1660
- asks when nothing is piping protocol messages into stdin to begin
1661
- with.
1662
-
1663
- Set `PATTERN_NO_ENFORCEMENT_NOTICE` to suppress both halves. See
1664
- `offerEnforcementSetupOnce` in `src/init-enforcement.ts` for the
1665
- implementation.
1666
-
1667
- **Or set it up by hand**, two pieces, neither installed automatically:
1668
-
1669
- - **`.claude/settings.json`** wired to run `npx --yes
1670
- pattern-check-gate-hook` on `PreToolUse` (see
1671
- `templates/claude-settings/settings.json` for the exact shape) -- a
1672
- Claude Code hook that runs on `Write`/`Edit` calls. For a genuinely new
1673
- `.tsx`/`.jsx` file that exports a non-trivial component, it looks up a
1674
- ledger entry (via `~/.pattern/ledger.jsonl`, same as everywhere else in
1675
- Pattern) whose `file_path` matches the file being written. A match
1676
- writes a receipt and allows the write; no match blocks it with a reason
1677
- fed back to the model as retryable guidance, not a hard failure.
1678
- **This is the one new exception where Pattern writes into your repo**
1679
- (`.pattern/receipts/<feature_id>.json`) -- everything else described in
1680
- this README is read-only. `project_id` no longer needs to be set by
1681
- hand either -- it's derived the same way `init` pre-fills it (see
1682
- `src/project-id.ts`); set `PATTERN_PROJECT_ID` only to override that.
1683
- - **`templates/github-workflows/pattern-gate.yml`** -- a required PR
1684
- check that reads the same receipt files back out of the diff. It never
1685
- touches `~/.pattern/` (not reachable from a CI runner) and needs no
1686
- `GITHUB_TOKEN` -- it trusts the committed receipt as the artifact of
1687
- record, the same way it would trust a committed test fixture.
1688
-
1689
- The join between the two depends on `file_path` being passed to
1690
- `recommend_component`/`record_component_decision` -- if it's omitted, the
1691
- gate has nothing to match against and fails closed (blocks) rather than
1692
- guessing. Pass `file_path` whenever you know it.
1693
-
1694
- An escape hatch exists for both a whole-hook kill switch
1695
- (`PATTERN_NO_ENFORCEMENT_HOOK`, local only -- does not affect the CI
1696
- check) and a per-file override (a `// pattern-mcp:override reason="..."`
1697
- comment) -- the override still writes a receipt recording
1698
- `manual_override: true` and the reason, so it stays visible rather than
1699
- silent. See `src/component-gate.ts`, `src/gate-receipt.ts`,
1700
- `src/check-gate.ts` (the `pattern-check-gate` CLI, this project's first
1701
- entry point separate from the stdio MCP server), `src/check-gate-hook.ts`,
1702
- and `src/init-enforcement.ts` for the implementation, and BACKLOG.md's
1703
- "Enforcement boundary" entries for the fuller design writeup.
1704
-
1705
1725
  ## Per-project decision memory
1706
1726
 
1707
1727
  Pattern stores confirmed decisions locally in:
@@ -1835,6 +1855,23 @@ are a biased, tiny sample of everyone who installs.
1835
1855
  - On a failed Anthropic API call specifically: the HTTP status code and a
1836
1856
  coarse classification (`rate_limit`, `insufficient_credit`, or `other`)
1837
1857
  -- never the request or response body.
1858
+ - On every invocation of the `pattern-mcp` binary, immediately at
1859
+ startup: a single `pattern_cli_started` event carrying only which
1860
+ mode it ran in (`server` -- the normal MCP-server start, or `init` --
1861
+ the [connect wizard](#connect-pattern-to-your-mcp-client)). This
1862
+ exists to separate real executions from npm registry traffic that
1863
+ never runs the code at all (security scanners, mirrors) -- something
1864
+ neither `recommend_component` counts nor `@posthog/mcp`'s handshake
1865
+ event below can answer, since both require getting further than a
1866
+ bare `npx pattern-mcp` run.
1867
+ - On process exit, as of v0.14.0: a single `pattern_cli_exited` event
1868
+ carrying only a coarse reason (`sigint`, `sigterm`,
1869
+ `uncaught_exception`, `unhandled_rejection`, or
1870
+ `fatal_startup_error`) and, for the two exception cases, the thrown
1871
+ value's constructor name (e.g. `TypeError`) -- never the error
1872
+ message or stack trace. Paired with `pattern_cli_started` so a start
1873
+ with no matching MCP handshake is diagnosable as a crash instead of
1874
+ silent.
1838
1875
  2. Standard MCP tool-call analytics, via
1839
1876
  [`@posthog/mcp`](https://posthog.com/docs/mcp-analytics): which tool
1840
1877
  was called, call duration, and success/failure, so unique installs and