pattern-mcp 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +314 -155
  2. package/dist/index.js +631 -40
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -2,15 +2,32 @@
2
2
 
3
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
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)
5
+ [![npm downloads](https://img.shields.io/npm/dt/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 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.
8
+ Pattern solves a simple problem: agents don't always follow your design
9
+ guidance. Instead of relying on the agent to interpret and follow
10
+ instructions, Pattern turns that guidance into a checkable process. It
11
+ evaluates UI components from external libraries or your own design
12
+ system against a requirements checklist, then tells the agent whether to
13
+ use an existing component or build one from scratch using a concrete
14
+ design reference.
11
15
 
12
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)
13
17
 
18
+ <details>
19
+ <summary><strong>Contents</strong> (click to expand)</summary>
20
+
21
+ - [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)
22
+ - **Make the judgment call:** [`recommend_component`](#tool-recommend_component) · [`extract_requirements`](#tool-extract_requirements)
23
+ - **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)
24
+ - **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)
25
+ - [Per-project decision memory](#per-project-decision-memory) · [Security and privacy](#security-and-privacy) · [Telemetry](#telemetry)
26
+ - **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)
27
+ - [Local call log](#local-call-log) · [Known limitations](#known-limitations)
28
+
29
+ </details>
30
+
14
31
  ## Install
15
32
 
16
33
  ```bash
@@ -32,13 +49,18 @@ whether to:
32
49
 
33
50
  Pattern is designed for agents to use **while they are building**.
34
51
 
35
- It exposes eleven tools:
52
+ It exposes eleven tools, in three groups:
53
+
54
+ **Make the judgment call.**
36
55
 
37
56
  - `recommend_component` — evaluates a UI component need and returns a
38
57
  structured recommendation.
39
58
  - `extract_requirements` — runs just the requirement-extraction step on
40
59
  its own, so you can inspect or hand-edit the checklist before
41
60
  `recommend_component` spends its search+score budget on it.
61
+
62
+ **Track what it cost and what actually happened.**
63
+
42
64
  - `record_component_decision` — records what the agent actually did so
43
65
  future recommendations in the same project can take that decision into
44
66
  account.
@@ -54,26 +76,27 @@ It exposes eleven tools:
54
76
  merge, kept-vs-replaced) for one feature, deliberately independent of
55
77
  Pattern's own verdict -- see [Outcome
56
78
  proxies](#outcome-proxies).
79
+
80
+ **Verify old decisions still hold up, and export a record of them.** See
81
+ [Ledger integrity and decision
82
+ provenance](#ledger-integrity-and-decision-provenance) for how these five
83
+ fit together.
84
+
57
85
  - `check_ledger_liveness` — checks whether a ledger entry's recorded
58
- `file_path` still exists and still references its `chosen_candidate` --
59
- see [Tool: `check_ledger_liveness`](#tool-check_ledger_liveness).
60
- - `export_ledger_provenance` — formats one ledger entry as a stable
61
- markdown block (checklist, candidates, verdict, `snapshot_ref`) you can
62
- paste into a PR or issue by hand -- see [Tool:
63
- `export_ledger_provenance`](#tool-export_ledger_provenance).
64
- - `post_ledger_provenance_to_github` — posts that same artifact as a real
65
- comment on a GitHub PR/issue, idempotently -- see [Tool:
66
- `post_ledger_provenance_to_github`](#tool-post_ledger_provenance_to_github).
67
- The one tool here with a real, visible side effect outside your own
68
- machine; confirm with the user before calling it.
86
+ `file_path` still exists and still references its `chosen_candidate`.
69
87
  - `sweep_ledger_liveness` — batch version of `check_ledger_liveness`
70
88
  across a whole project (or every project in the ledger), plus
71
- dangling-cluster detection -- see [Tool:
72
- `sweep_ledger_liveness`](#tool-sweep_ledger_liveness). Meant to be
73
- invoked by your own cron/CI, not something Pattern schedules itself.
89
+ dangling-cluster detection. Meant to be invoked by your own cron/CI, not
90
+ something Pattern schedules itself.
91
+ - `export_ledger_provenance` formats one ledger entry as a stable
92
+ markdown block (checklist, candidates, verdict, `snapshot_ref`) you can
93
+ paste into a PR or issue by hand.
74
94
  - `backfill_ledger_snapshot_ref` — best-effort `snapshot_ref`
75
- reconstruction for entries written before that field existed -- see
76
- [Tool: `backfill_ledger_snapshot_ref`](#tool-backfill_ledger_snapshot_ref).
95
+ reconstruction for entries written before that field existed.
96
+ - `post_ledger_provenance_to_github` — posts an `export_ledger_provenance`
97
+ artifact as a real comment on a GitHub PR/issue, idempotently. The one
98
+ tool here with a real, visible side effect outside your own machine;
99
+ confirm with the user before calling it.
77
100
 
78
101
  ## How it works
79
102
 
@@ -577,6 +600,114 @@ The calling agent should:
577
600
 
578
601
  See [SECURITY.md](./SECURITY.md) for more details.
579
602
 
603
+ ## Tool: `register_design_system`
604
+
605
+ Points `recommend_component` at *this project's own* design system instead
606
+ of shadcn/ui, 21st.dev, and ReUI -- for a solo dev with their own component
607
+ library or design spec who wants Pattern's coverage scoring against
608
+ candidates they'll actually use, not external libraries they won't. This is
609
+ the Solo Dev architecture from `pattern-solo-design-system-architecture.md`:
610
+ local, per-project, one-or-the-other -- registering a design system for a
611
+ `project_id` **replaces** external-library scoring for that project
612
+ entirely, it does not add to it. There's no shared/remote ledger, no
613
+ multi-user attribution, and no team auth in this scope -- those are
614
+ deliberately deferred to a team phase, only if this use case proves out.
615
+
616
+ ### Input
617
+
618
+ Exactly one of `manifest_path` or `directory_path` is required, both
619
+ relative to the project root (`PATTERN_PROJECT_ROOT`, defaults to this
620
+ server's working directory) -- never an absolute path.
621
+
622
+ ```json
623
+ {
624
+ "project_id": "my-booking-app",
625
+ "directory_path": "src/components"
626
+ }
627
+ ```
628
+
629
+ - **`manifest_path`** -- a components manifest. Two recognized shapes:
630
+ - A hand-authored JSON array of `{name, props, description,
631
+ usage_example}` objects, optionally wrapped in `{"components": [...]}`.
632
+ - A Storybook-exported `stories.json`/`index.json` file (an object with a
633
+ top-level `entries` or `stories` map). Component names only in this
634
+ case -- Storybook's basic export doesn't carry prop data, so candidates
635
+ from this path start with an empty `props` list.
636
+ - **`directory_path`** -- a directory of real component source files,
637
+ scanned recursively for `.jsx`/`.tsx`/`.js`/`.ts` files (excluding
638
+ `node_modules`/`dist`/`build`/`.git` and `.test.`/`.spec.`/`.stories.`
639
+ files). Each exported, uppercase-named function or const component found
640
+ becomes a candidate, with props read in priority order from a
641
+ `<Name>Props` interface/type, a `.propTypes` block, or (last resort) the
642
+ component's own destructured parameters. This is a heuristic scan, not a
643
+ full parser -- a sparse or partial props list for some components is
644
+ expected, not a bug, especially on plain JS with no prop typing at all.
645
+
646
+ ### Output
647
+
648
+ ```json
649
+ {
650
+ "status": "registered",
651
+ "registration": {
652
+ "project_id": "my-booking-app",
653
+ "source_kind": "directory_scan",
654
+ "source_path": "src/components",
655
+ "registered_at": "2026-09-03T18:04:11.201Z",
656
+ "candidate_count": 29,
657
+ "candidates": [
658
+ { "name": "ReferralBanner", "props": ["code", "bonusAmount"], "description": null, "usage_example": null, "file_path": "rewards/ReferralBanner.jsx" }
659
+ ]
660
+ }
661
+ }
662
+ ```
663
+
664
+ Registering overwrites (does not merge with) any prior registration for the
665
+ same `project_id`. Once registered, `recommend_component` scores ONLY
666
+ against these candidates for calls with this `project_id` -- no separate
667
+ flag needed, it's automatic based on `project_id` alone, and step 3's live
668
+ web search is skipped entirely (`web_search` is still available, but
669
+ reserved for a `custom_build` verdict's Mobbin/Figma Community reference
670
+ grounding, same as the external-library path). A `use_existing` verdict
671
+ scored this way always carries `"source": "design_system"` on the
672
+ resulting ledger entry, set server-side regardless of what the model wrote,
673
+ so `read_ledger` and `export_ledger_provenance` can match on it reliably.
674
+
675
+ This only writes local config to `~/.pattern/design_systems.json` (override
676
+ with `PATTERN_DESIGN_SYSTEMS_PATH`) -- it never calls the Anthropic API.
677
+ Registration is a point-in-time snapshot, not a live link: re-run this
678
+ whenever the design system's own components change meaningfully.
679
+
680
+ ### A safety net for a missed match
681
+
682
+ The model can occasionally say `custom_build`/`no_candidates_found`
683
+ against a registered design system even when a real match is sitting
684
+ right there in its own prompt -- a reading-comprehension miss over its own
685
+ known-complete candidate list, not evidence the list was actually empty.
686
+ When this happens, `recommend_component`'s response may carry a
687
+ `design_system_recall_check` field: a deterministic, zero-cost, local
688
+ keyword-overlap check (component name, props, description/usage_example
689
+ vs. `component_need`/`domain`) run automatically whenever reason is
690
+ `no_candidates_found` in this mode.
691
+
692
+ ```json
693
+ {
694
+ "verdict": "custom_build",
695
+ "reason": "no_candidates_found",
696
+ "design_system_recall_check": {
697
+ "possible_missed_candidates": [
698
+ { "name": "ReferralBanner", "shared_keywords": ["referral", "bonus"] }
699
+ ],
700
+ "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."
701
+ }
702
+ }
703
+ ```
704
+
705
+ This never overrides the verdict -- a shared keyword is weak evidence, not
706
+ proof of a real match -- it only surfaces the risk so you (or the calling
707
+ agent) know to double-check before accepting a `custom_build` verdict at
708
+ face value. Absent entirely when there's no overlap, or outside
709
+ design-system mode.
710
+
580
711
  ## Tool: `extract_requirements`
581
712
 
582
713
  Runs only the requirement-extraction step `recommend_component` normally
@@ -981,123 +1112,6 @@ there's nothing to check. Results here are also layered onto
981
1112
  entries afterward -- `check_ledger_liveness` is the only thing that
982
1113
  advances those fields past their write-time defaults.
983
1114
 
984
- ## Tool: `export_ledger_provenance`
985
-
986
- Formats one ledger entry -- requirements checklist, candidates compared,
987
- verdict, confidence, `snapshot_ref` -- as a single markdown block: a
988
- stable, portable record of that decision you can paste into a PR
989
- description or issue by hand. See [Ledger integrity and decision
990
- provenance](#ledger-integrity-and-decision-provenance) for the full
991
- design and its deliberate limits.
992
-
993
- Pure and deterministic: the same entry always produces byte-identical
994
- markdown, since the function reads nothing but its input (no live system
995
- time, no disk state). This only formats and returns text -- it does not
996
- post anything to GitHub or anywhere else; that's a separate action, not
997
- yet built.
998
-
999
- ### Input
1000
-
1001
- ```json
1002
- {
1003
- "project_id": "my-booking-app",
1004
- "ledger_entry_id": "a1b2c3d4-..."
1005
- }
1006
- ```
1007
-
1008
- Both fields are required -- unlike `check_ledger_liveness`, there's no
1009
- "every entry for this project" mode, since a provenance artifact is
1010
- inherently about one specific decision.
1011
-
1012
- ### Output
1013
-
1014
- ```json
1015
- {
1016
- "ledger_entry_id": "a1b2c3d4-...",
1017
- "markdown": "## Pattern decision: cancellation policy display with refund tiers by date\n\n- **Verdict:** use_existing (confidence: high)\n- **Reason:** scored\n- **Coverage:** 5/8 (62.5%)\n- **Domain:** Airbnb-style rental marketplace\n- **Framework:** React + Tailwind\n- **Snapshot:** `9f3a1c7e2b0d4f5a6b7c8d9e0f1a2b3c4d5e6f70`\n- **Judged at:** 2026-08-29T19:50:47.073Z\n\n### Requirements checked\n- ...\n\n### Candidates compared\n| Source | Name | Coverage | Chosen |\n| --- | --- | --- | --- |\n| ReUI (reui.io) | Timeline | 62.5 | ✓ |\n\n_Generated by Pattern (`export_ledger_provenance`) from ledger entry `a1b2c3d4-...`._"
1018
- }
1019
- ```
1020
-
1021
- Errors (as `isError: true`, not a thrown exception) when `ledger_entry_id`
1022
- doesn't match any entry for that `project_id` -- including when the id is
1023
- real but belongs to a different project, since entries are always scoped
1024
- per `project_id`.
1025
-
1026
- For a `custom_build` verdict, the candidates section explains that gap in
1027
- prose instead of an empty table -- Pattern doesn't persist the
1028
- custom-build reference (Mobbin/Figma) to the ledger (see
1029
- [`distillCandidate`](#data-minimization)), so it can't reproduce it here.
1030
- A `null` `snapshot_ref` (project root wasn't a git repository at judgment
1031
- time) renders as prose too, not the literal word `null`.
1032
-
1033
- ## Tool: `post_ledger_provenance_to_github`
1034
-
1035
- Posts one ledger entry's provenance artifact (the same content
1036
- `export_ledger_provenance` produces) as a real comment on a GitHub PR or
1037
- issue. **This is the one tool in this server with a real, visible side
1038
- effect on a third-party service** -- every other tool here only ever
1039
- touches local files. Confirm with the user before calling it, the same
1040
- way you're expected to confirm before running a suggested
1041
- `install_command` (see [Installation commands are not
1042
- trusted](#installation-commands-are-not-trusted) and SECURITY.md).
1043
-
1044
- GitHub treats a PR and an issue identically for comments (both use the
1045
- same `/issues/{number}/comments` endpoint), so there's one input shape
1046
- for both -- no separate "is this a PR" flag.
1047
-
1048
- ### Auth: `GITHUB_TOKEN`, not a GitHub App
1049
-
1050
- This resolves the open question left in [Ledger integrity and decision
1051
- provenance](#ledger-integrity-and-decision-provenance)'s earlier writeup
1052
- in favor of a **personal access token**, read from the `GITHUB_TOKEN`
1053
- environment variable -- the same convention every GitHub Action and the
1054
- `gh` CLI itself already use. Needs `repo` scope. A GitHub App was the
1055
- alternative on the table, but it needs a hosted installation flow and a
1056
- webhook receiver, which contradicts this project's entire distribution
1057
- model (a local npm package, no hosted infrastructure -- see [Ledger
1058
- integrity and decision provenance](#ledger-integrity-and-decision-provenance)
1059
- and the Pattern Primer's build-order principle). Pattern manages no
1060
- GitHub credential of its own, the same way it manages no git credential
1061
- for `snapshot_ref` -- it just reads what's already in your environment.
1062
-
1063
- ### Input
1064
-
1065
- ```json
1066
- {
1067
- "project_id": "my-booking-app",
1068
- "ledger_entry_id": "a1b2c3d4-...",
1069
- "repo": "my-org/my-booking-app",
1070
- "issue_number": 42
1071
- }
1072
- ```
1073
-
1074
- All four fields are required.
1075
-
1076
- ### Output
1077
-
1078
- ```json
1079
- {
1080
- "posted": true,
1081
- "comment_url": "https://github.com/my-org/my-booking-app/pull/42#issuecomment-...",
1082
- "comment_id": 123456789
1083
- }
1084
- ```
1085
-
1086
- ### Idempotent by construction
1087
-
1088
- Every posted comment is prefixed with a hidden HTML marker keyed to the
1089
- ledger entry's id (`<!-- pattern-ledger-provenance:<id> -->`). A call
1090
- first checks the thread's existing comments (most recent 100 -- full
1091
- pagination isn't handled yet) for that marker; if found, it returns
1092
- `{ "posted": false, "reason": "already_posted", "comment_url": "..." }`
1093
- pointing at the existing comment instead of creating a duplicate. A
1094
- repeat call is always safe to make.
1095
-
1096
- Errors (`isError: true`) clearly on: no `GITHUB_TOKEN` set, a malformed
1097
- `repo` (not `owner/repo`), an unknown `ledger_entry_id`, or a GitHub API
1098
- error (bad credentials, repo/issue not found, rate limit) -- the error
1099
- message includes the real HTTP status and GitHub's own error text.
1100
-
1101
1115
  ## Tool: `sweep_ledger_liveness`
1102
1116
 
1103
1117
  Batch version of [`check_ledger_liveness`](#tool-check_ledger_liveness):
@@ -1164,6 +1178,56 @@ live -- all 12 flag `dangling`, the 13th doesn't. Also tested at 200 and
1164
1178
  (both complete in well under a second -- this is `fs.existsSync` calls
1165
1179
  and in-memory grouping, not API calls).
1166
1180
 
1181
+ ## Tool: `export_ledger_provenance`
1182
+
1183
+ Formats one ledger entry -- requirements checklist, candidates compared,
1184
+ verdict, confidence, `snapshot_ref` -- as a single markdown block: a
1185
+ stable, portable record of that decision you can paste into a PR
1186
+ description or issue by hand. See [Ledger integrity and decision
1187
+ provenance](#ledger-integrity-and-decision-provenance) for the full
1188
+ design and its deliberate limits.
1189
+
1190
+ Pure and deterministic: the same entry always produces byte-identical
1191
+ markdown, since the function reads nothing but its input (no live system
1192
+ time, no disk state). This only formats and returns text -- it does not
1193
+ post anything anywhere; see
1194
+ [`post_ledger_provenance_to_github`](#tool-post_ledger_provenance_to_github)
1195
+ below for that.
1196
+
1197
+ ### Input
1198
+
1199
+ ```json
1200
+ {
1201
+ "project_id": "my-booking-app",
1202
+ "ledger_entry_id": "a1b2c3d4-..."
1203
+ }
1204
+ ```
1205
+
1206
+ Both fields are required -- unlike `check_ledger_liveness`, there's no
1207
+ "every entry for this project" mode, since a provenance artifact is
1208
+ inherently about one specific decision.
1209
+
1210
+ ### Output
1211
+
1212
+ ```json
1213
+ {
1214
+ "ledger_entry_id": "a1b2c3d4-...",
1215
+ "markdown": "## Pattern decision: cancellation policy display with refund tiers by date\n\n- **Verdict:** use_existing (confidence: high)\n- **Reason:** scored\n- **Coverage:** 5/8 (62.5%)\n- **Domain:** Airbnb-style rental marketplace\n- **Framework:** React + Tailwind\n- **Snapshot:** `9f3a1c7e2b0d4f5a6b7c8d9e0f1a2b3c4d5e6f70`\n- **Judged at:** 2026-08-29T19:50:47.073Z\n\n### Requirements checked\n- ...\n\n### Candidates compared\n| Source | Name | Coverage | Chosen |\n| --- | --- | --- | --- |\n| ReUI (reui.io) | Timeline | 62.5 | ✓ |\n\n_Generated by Pattern (`export_ledger_provenance`) from ledger entry `a1b2c3d4-...`._"
1216
+ }
1217
+ ```
1218
+
1219
+ Errors (as `isError: true`, not a thrown exception) when `ledger_entry_id`
1220
+ doesn't match any entry for that `project_id` -- including when the id is
1221
+ real but belongs to a different project, since entries are always scoped
1222
+ per `project_id`.
1223
+
1224
+ For a `custom_build` verdict, the candidates section explains that gap in
1225
+ prose instead of an empty table -- Pattern doesn't persist the
1226
+ custom-build reference (Mobbin/Figma) to the ledger (see
1227
+ [`distillCandidate`](#data-minimization)), so it can't reproduce it here.
1228
+ A `null` `snapshot_ref` (project root wasn't a git repository at judgment
1229
+ time) renders as prose too, not the literal word `null`.
1230
+
1167
1231
  ## Tool: `backfill_ledger_snapshot_ref`
1168
1232
 
1169
1233
  Best-effort reconstruction of `snapshot_ref` for ledger entries written
@@ -1224,6 +1288,74 @@ attempt fails fast and reports `null` rather than throwing), and a
1224
1288
  read-only run against this project's own real `coop-commerce` ledger
1225
1289
  entries, per the spec's own test plan.
1226
1290
 
1291
+ ## Tool: `post_ledger_provenance_to_github`
1292
+
1293
+ Posts one ledger entry's provenance artifact (the same content
1294
+ `export_ledger_provenance` produces) as a real comment on a GitHub PR or
1295
+ issue. **This is the one tool in this server with a real, visible side
1296
+ effect on a third-party service** -- every other tool here only ever
1297
+ touches local files. Confirm with the user before calling it, the same
1298
+ way you're expected to confirm before running a suggested
1299
+ `install_command` (see [Installation commands are not
1300
+ trusted](#installation-commands-are-not-trusted) and SECURITY.md).
1301
+
1302
+ GitHub treats a PR and an issue identically for comments (both use the
1303
+ same `/issues/{number}/comments` endpoint), so there's one input shape
1304
+ for both -- no separate "is this a PR" flag.
1305
+
1306
+ ### Auth: `GITHUB_TOKEN`, not a GitHub App
1307
+
1308
+ This resolves the open question left in [Ledger integrity and decision
1309
+ provenance](#ledger-integrity-and-decision-provenance)'s earlier writeup
1310
+ in favor of a **personal access token**, read from the `GITHUB_TOKEN`
1311
+ environment variable -- the same convention every GitHub Action and the
1312
+ `gh` CLI itself already use. Needs `repo` scope. A GitHub App was the
1313
+ alternative on the table, but it needs a hosted installation flow and a
1314
+ webhook receiver, which contradicts this project's entire distribution
1315
+ model (a local npm package, no hosted infrastructure -- see [Ledger
1316
+ integrity and decision provenance](#ledger-integrity-and-decision-provenance)
1317
+ and the Pattern Primer's build-order principle). Pattern manages no
1318
+ GitHub credential of its own, the same way it manages no git credential
1319
+ for `snapshot_ref` -- it just reads what's already in your environment.
1320
+
1321
+ ### Input
1322
+
1323
+ ```json
1324
+ {
1325
+ "project_id": "my-booking-app",
1326
+ "ledger_entry_id": "a1b2c3d4-...",
1327
+ "repo": "my-org/my-booking-app",
1328
+ "issue_number": 42
1329
+ }
1330
+ ```
1331
+
1332
+ All four fields are required.
1333
+
1334
+ ### Output
1335
+
1336
+ ```json
1337
+ {
1338
+ "posted": true,
1339
+ "comment_url": "https://github.com/my-org/my-booking-app/pull/42#issuecomment-...",
1340
+ "comment_id": 123456789
1341
+ }
1342
+ ```
1343
+
1344
+ ### Idempotent by construction
1345
+
1346
+ Every posted comment is prefixed with a hidden HTML marker keyed to the
1347
+ ledger entry's id (`<!-- pattern-ledger-provenance:<id> -->`). A call
1348
+ first checks the thread's existing comments (most recent 100 -- full
1349
+ pagination isn't handled yet) for that marker; if found, it returns
1350
+ `{ "posted": false, "reason": "already_posted", "comment_url": "..." }`
1351
+ pointing at the existing comment instead of creating a duplicate. A
1352
+ repeat call is always safe to make.
1353
+
1354
+ Errors (`isError: true`) clearly on: no `GITHUB_TOKEN` set, a malformed
1355
+ `repo` (not `owner/repo`), an unknown `ledger_entry_id`, or a GitHub API
1356
+ error (bad credentials, repo/issue not found, rate limit) -- the error
1357
+ message includes the real HTTP status and GitHub's own error text.
1358
+
1227
1359
  ## Feature cost attribution
1228
1360
 
1229
1361
  Every `recommend_component` call that writes to the ledger -- a fresh
@@ -1345,25 +1477,33 @@ Two gaps in the ledger, surfaced from user feedback: it tracks that a
1345
1477
  decision was made, but not whether the thing it decided about is still
1346
1478
  live in your codebase, and it stores the checklist/verdict but not a
1347
1479
  version pin or an exportable artifact you can attach to a PR or issue.
1348
- This section covers what's shipped so far -- **P0/P1 of both halves**, not
1349
- the full spec. See `pattern-ledger-integrity-and-provenance-spec.md` for
1350
- the complete phased plan; P2/P3 (a scheduled/batch sweep, dangling-cluster
1351
- detection, the provenance-artifact exporter, and GitHub PR/issue posting)
1352
- are not built yet.
1353
-
1354
- **This is the one deliberate exception** to Pattern otherwise having [no
1355
- filesystem/git access to your repo](#per-project-judgment-ledger) at all
1356
- (the principle `report_build_cost`/`report_outcome_proxy` are built
1357
- around). It's narrow on purpose:
1358
-
1359
- - `git rev-parse HEAD` (read-only, never touches repo state) to capture
1360
- `snapshot_ref` on every ledger write.
1480
+ Both are now fully addressed, across five tools:
1481
+ [`check_ledger_liveness`](#tool-check_ledger_liveness) and
1482
+ [`sweep_ledger_liveness`](#tool-sweep_ledger_liveness) close the first gap;
1483
+ [`export_ledger_provenance`](#tool-export_ledger_provenance),
1484
+ [`backfill_ledger_snapshot_ref`](#tool-backfill_ledger_snapshot_ref), and
1485
+ [`post_ledger_provenance_to_github`](#tool-post_ledger_provenance_to_github)
1486
+ close the second. See `pattern-ledger-integrity-and-provenance-spec.md`
1487
+ for the original phased plan this was built against.
1488
+
1489
+ **This required the one deliberate exception** to Pattern otherwise having
1490
+ [no filesystem/git access to your repo](#per-project-judgment-ledger) at
1491
+ all (the principle `report_build_cost`/`report_outcome_proxy` are built
1492
+ around). Still narrow, still all read-only, and still nothing here ever
1493
+ writes to your repo or runs an arbitrary git/shell command:
1494
+
1495
+ - `git rev-parse HEAD`, on every ledger write, to capture `snapshot_ref`.
1496
+ - `git log --before=<timestamp> -1 --format=%H`, only inside
1497
+ `backfill_ledger_snapshot_ref`, to reconstruct a best-effort
1498
+ `snapshot_ref` for an entry that predates it.
1361
1499
  - `fs.existsSync` plus a plain-text read of one file, only for a
1362
1500
  `file_path` you explicitly passed to `recommend_component`, only inside
1363
- `PROJECT_ROOT` (see below), to answer `check_ledger_liveness`.
1364
-
1365
- Nothing here runs an arbitrary git or shell command, and nothing writes to
1366
- your repo.
1501
+ `PROJECT_ROOT` (see below) -- what
1502
+ [`check_ledger_liveness`](#tool-check_ledger_liveness)/[`sweep_ledger_liveness`](#tool-sweep_ledger_liveness)
1503
+ check. `post_ledger_provenance_to_github` additionally makes a real,
1504
+ visible network call to the GitHub API -- see that tool's own docs,
1505
+ it's a materially different kind of exception (a third-party service,
1506
+ not your local machine) from the four above.
1367
1507
 
1368
1508
  ### `PROJECT_ROOT`
1369
1509
 
@@ -1975,6 +2115,25 @@ search or code problem.
1975
2115
 
1976
2116
  The boundary-risk ensemble exists to detect and surface this uncertainty.
1977
2117
 
2118
+ ### A missed match in your own design system isn't automatically re-checked
2119
+
2120
+ In [design-system mode](#tool-register_design_system), the model can say
2121
+ `custom_build`/`no_candidates_found` even when a real, relevant candidate
2122
+ is sitting right in its own prompt -- a reading-comprehension miss over a
2123
+ fully-known candidate list, not a live-search gap. The boundary-risk
2124
+ ensemble above doesn't catch this: it only re-checks a `"scored"` result
2125
+ near the 40%/80% threshold, never a `"no_candidates_found"` verdict.
2126
+
2127
+ The [keyword-overlap safety net](#a-safety-net-for-a-missed-match) flags
2128
+ this risk (`design_system_recall_check` on the response) but does not fix
2129
+ it -- it's a detection layer, not a re-check. The actual fix (re-running
2130
+ the model on a suspicious `no_candidates_found` verdict, the same way a
2131
+ close `"scored"` call already gets re-checked) is scoped but not built.
2132
+ `custom_build` ledger entries in this mode also record zero candidates
2133
+ (`candidates_evaluated: []`), same as the external-library path, so a
2134
+ genuine miss and a correct "nothing here" still look identical in
2135
+ `read_ledger` afterward unless the recall check happened to catch it.
2136
+
1978
2137
  ### A staged pipeline was evaluated and not adopted
1979
2138
 
1980
2139
  To address the variance above, an alternative architecture was built and