pattern-mcp 0.5.0 → 0.6.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 +62 -3
- package/dist/index.js +96 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -32,7 +32,7 @@ whether to:
|
|
|
32
32
|
|
|
33
33
|
Pattern is designed for agents to use **while they are building**.
|
|
34
34
|
|
|
35
|
-
It exposes
|
|
35
|
+
It exposes eight tools:
|
|
36
36
|
|
|
37
37
|
- `recommend_component` — evaluates a UI component need and returns a
|
|
38
38
|
structured recommendation.
|
|
@@ -57,6 +57,10 @@ It exposes seven tools:
|
|
|
57
57
|
- `check_ledger_liveness` — checks whether a ledger entry's recorded
|
|
58
58
|
`file_path` still exists and still references its `chosen_candidate` --
|
|
59
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).
|
|
60
64
|
|
|
61
65
|
## How it works
|
|
62
66
|
|
|
@@ -959,6 +963,55 @@ there's nothing to check. Results here are also layered onto
|
|
|
959
963
|
entries afterward -- `check_ledger_liveness` is the only thing that
|
|
960
964
|
advances those fields past their write-time defaults.
|
|
961
965
|
|
|
966
|
+
## Tool: `export_ledger_provenance`
|
|
967
|
+
|
|
968
|
+
Formats one ledger entry -- requirements checklist, candidates compared,
|
|
969
|
+
verdict, confidence, `snapshot_ref` -- as a single markdown block: a
|
|
970
|
+
stable, portable record of that decision you can paste into a PR
|
|
971
|
+
description or issue by hand. See [Ledger integrity and decision
|
|
972
|
+
provenance](#ledger-integrity-and-decision-provenance) for the full
|
|
973
|
+
design and its deliberate limits.
|
|
974
|
+
|
|
975
|
+
Pure and deterministic: the same entry always produces byte-identical
|
|
976
|
+
markdown, since the function reads nothing but its input (no live system
|
|
977
|
+
time, no disk state). This only formats and returns text -- it does not
|
|
978
|
+
post anything to GitHub or anywhere else; that's a separate action, not
|
|
979
|
+
yet built.
|
|
980
|
+
|
|
981
|
+
### Input
|
|
982
|
+
|
|
983
|
+
```json
|
|
984
|
+
{
|
|
985
|
+
"project_id": "my-booking-app",
|
|
986
|
+
"ledger_entry_id": "a1b2c3d4-..."
|
|
987
|
+
}
|
|
988
|
+
```
|
|
989
|
+
|
|
990
|
+
Both fields are required -- unlike `check_ledger_liveness`, there's no
|
|
991
|
+
"every entry for this project" mode, since a provenance artifact is
|
|
992
|
+
inherently about one specific decision.
|
|
993
|
+
|
|
994
|
+
### Output
|
|
995
|
+
|
|
996
|
+
```json
|
|
997
|
+
{
|
|
998
|
+
"ledger_entry_id": "a1b2c3d4-...",
|
|
999
|
+
"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-...`._"
|
|
1000
|
+
}
|
|
1001
|
+
```
|
|
1002
|
+
|
|
1003
|
+
Errors (as `isError: true`, not a thrown exception) when `ledger_entry_id`
|
|
1004
|
+
doesn't match any entry for that `project_id` -- including when the id is
|
|
1005
|
+
real but belongs to a different project, since entries are always scoped
|
|
1006
|
+
per `project_id`.
|
|
1007
|
+
|
|
1008
|
+
For a `custom_build` verdict, the candidates section explains that gap in
|
|
1009
|
+
prose instead of an empty table -- Pattern doesn't persist the
|
|
1010
|
+
custom-build reference (Mobbin/Figma) to the ledger (see
|
|
1011
|
+
[`distillCandidate`](#data-minimization)), so it can't reproduce it here.
|
|
1012
|
+
A `null` `snapshot_ref` (project root wasn't a git repository at judgment
|
|
1013
|
+
time) renders as prose too, not the literal word `null`.
|
|
1014
|
+
|
|
962
1015
|
## Feature cost attribution
|
|
963
1016
|
|
|
964
1017
|
Every `recommend_component` call that writes to the ledger -- a fresh
|
|
@@ -1126,8 +1179,14 @@ cache-hit line was written*, not the original judgment's -- to see the
|
|
|
1126
1179
|
original judgment's snapshot, look up the entry named in its
|
|
1127
1180
|
`ledger_entry_id`/`original_verdict_timestamp` fields instead.
|
|
1128
1181
|
|
|
1129
|
-
|
|
1130
|
-
|
|
1182
|
+
[`export_ledger_provenance`](#tool-export_ledger_provenance) packages one
|
|
1183
|
+
entry's full record -- checklist, candidates, verdict, `snapshot_ref` --
|
|
1184
|
+
into a markdown block you can paste into a PR or issue by hand.
|
|
1185
|
+
|
|
1186
|
+
Not yet built (P2-P3 of Feature 2): the MCP action to post that artifact
|
|
1187
|
+
to a GitHub PR/issue automatically (blocked on an open question -- personal
|
|
1188
|
+
token vs. GitHub App -- see BACKLOG.md), and provenance backfill for
|
|
1189
|
+
entries that predate `snapshot_ref`.
|
|
1131
1190
|
|
|
1132
1191
|
### Referential integrity: `file_path` / `live_status`
|
|
1133
1192
|
|
package/dist/index.js
CHANGED
|
@@ -442,6 +442,7 @@ const READ_LEDGER_TOOL_NAME = "read_ledger";
|
|
|
442
442
|
const REPORT_BUILD_COST_TOOL_NAME = "report_build_cost";
|
|
443
443
|
const REPORT_OUTCOME_PROXY_TOOL_NAME = "report_outcome_proxy";
|
|
444
444
|
const CHECK_LEDGER_LIVENESS_TOOL_NAME = "check_ledger_liveness";
|
|
445
|
+
const EXPORT_LEDGER_PROVENANCE_TOOL_NAME = "export_ledger_provenance";
|
|
445
446
|
const INPUT_SCHEMA = {
|
|
446
447
|
type: "object",
|
|
447
448
|
properties: {
|
|
@@ -677,6 +678,20 @@ const CHECK_LEDGER_LIVENESS_INPUT_SCHEMA = {
|
|
|
677
678
|
},
|
|
678
679
|
required: ["project_id"],
|
|
679
680
|
};
|
|
681
|
+
const EXPORT_LEDGER_PROVENANCE_INPUT_SCHEMA = {
|
|
682
|
+
type: "object",
|
|
683
|
+
properties: {
|
|
684
|
+
project_id: {
|
|
685
|
+
type: "string",
|
|
686
|
+
description: "The project_id used in the recommend_component call that produced this ledger entry.",
|
|
687
|
+
},
|
|
688
|
+
ledger_entry_id: {
|
|
689
|
+
type: "string",
|
|
690
|
+
description: "The specific entry to export, from read_ledger or check_ledger_liveness.",
|
|
691
|
+
},
|
|
692
|
+
},
|
|
693
|
+
required: ["project_id", "ledger_entry_id"],
|
|
694
|
+
};
|
|
680
695
|
// Shared between buildSystemPrompt's own step 2 and
|
|
681
696
|
// buildExtractionSystemPrompt (the extract_requirements tool's standalone
|
|
682
697
|
// prompt) -- the extraction *instructions* are one piece of text reused
|
|
@@ -1541,6 +1556,54 @@ function findLedgerMatches(projectId, componentNeed, limit = 20) {
|
|
|
1541
1556
|
entries.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
|
|
1542
1557
|
return entries.slice(0, limit);
|
|
1543
1558
|
}
|
|
1559
|
+
// Feature 2 / Decision Provenance, P1: renders one ledger entry as a
|
|
1560
|
+
// stable markdown block -- "stable" meaning a pure function of the entry
|
|
1561
|
+
// alone (never Date.now(), never anything read live off disk), so the
|
|
1562
|
+
// same entry always produces byte-identical markdown. That determinism is
|
|
1563
|
+
// what makes verify-provenance-artifact.mjs's snapshot test meaningful:
|
|
1564
|
+
// a diff in the generated markdown for a fixed fixture means the format
|
|
1565
|
+
// changed, not that time passed. Markdown, not JSON, per the spec --
|
|
1566
|
+
// PRs/issues render it natively (P2, not built here, attaches this to
|
|
1567
|
+
// one).
|
|
1568
|
+
export function formatProvenanceArtifact(entry) {
|
|
1569
|
+
const lines = [];
|
|
1570
|
+
lines.push(`## Pattern decision: ${entry.component_need}`);
|
|
1571
|
+
lines.push("");
|
|
1572
|
+
lines.push(`- **Verdict:** ${entry.verdict} (confidence: ${entry.confidence})`);
|
|
1573
|
+
lines.push(`- **Reason:** ${entry.reason}`);
|
|
1574
|
+
lines.push(`- **Coverage:** ${entry.coverage ?? "n/a"}`);
|
|
1575
|
+
lines.push(`- **Domain:** ${entry.domain}`);
|
|
1576
|
+
lines.push(`- **Framework:** ${entry.framework}`);
|
|
1577
|
+
lines.push(`- **Snapshot:** ${entry.snapshot_ref ? "`" + entry.snapshot_ref + "`" : "not available (project root wasn't a git repository at judgment time)"}`);
|
|
1578
|
+
lines.push(`- **Judged at:** ${entry.timestamp}${entry.cache_hit ? " (served from ledger cache hit)" : ""}`);
|
|
1579
|
+
lines.push("");
|
|
1580
|
+
lines.push("### Requirements checked");
|
|
1581
|
+
if (entry.checklist.length === 0) {
|
|
1582
|
+
lines.push("_No checklist recorded for this entry._");
|
|
1583
|
+
}
|
|
1584
|
+
else {
|
|
1585
|
+
for (const item of entry.checklist)
|
|
1586
|
+
lines.push(`- ${item}`);
|
|
1587
|
+
}
|
|
1588
|
+
lines.push("");
|
|
1589
|
+
lines.push("### Candidates compared");
|
|
1590
|
+
if (entry.candidates_evaluated.length === 0) {
|
|
1591
|
+
lines.push(entry.verdict === "custom_build"
|
|
1592
|
+
? "_No existing candidate met the bar -- Pattern recommended a custom build. Pattern doesn't persist the custom-build reference (Mobbin/Figma) to the ledger, so it isn't reproducible here._"
|
|
1593
|
+
: "_No candidates recorded for this entry._");
|
|
1594
|
+
}
|
|
1595
|
+
else {
|
|
1596
|
+
lines.push("| Source | Name | Coverage | Chosen |");
|
|
1597
|
+
lines.push("| --- | --- | --- | --- |");
|
|
1598
|
+
for (const c of entry.candidates_evaluated) {
|
|
1599
|
+
const chosen = c.name !== null && c.name === entry.chosen_candidate ? "✓" : "";
|
|
1600
|
+
lines.push(`| ${c.source ?? "n/a"} | ${c.name ?? "n/a"} | ${c.coverage_pct ?? "n/a"} | ${chosen} |`);
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
lines.push("");
|
|
1604
|
+
lines.push(`_Generated by Pattern (\`export_ledger_provenance\`) from ledger entry \`${entry.id}\`._`);
|
|
1605
|
+
return lines.join("\n");
|
|
1606
|
+
}
|
|
1544
1607
|
// report_build_cost (cost-attribution build plan, 1.3) -- self-reported
|
|
1545
1608
|
// build cost, cheapest option first, since Pattern has no visibility into
|
|
1546
1609
|
// what happens after judgeComponent returns a verdict (1.4's
|
|
@@ -2477,6 +2540,18 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
2477
2540
|
"entries afterward.",
|
|
2478
2541
|
inputSchema: CHECK_LEDGER_LIVENESS_INPUT_SCHEMA,
|
|
2479
2542
|
},
|
|
2543
|
+
{
|
|
2544
|
+
name: EXPORT_LEDGER_PROVENANCE_TOOL_NAME,
|
|
2545
|
+
description: "Formats one ledger entry (requirements checklist, candidates " +
|
|
2546
|
+
"compared, verdict, confidence, snapshot_ref) as a single markdown " +
|
|
2547
|
+
"block -- a stable, portable record of that decision you can paste " +
|
|
2548
|
+
"into a PR description or issue by hand. Pure and deterministic: " +
|
|
2549
|
+
"the same entry always produces the same markdown, nothing here " +
|
|
2550
|
+
"reads live system time or disk state. This only formats and " +
|
|
2551
|
+
"returns text; it does not post anything to GitHub or anywhere " +
|
|
2552
|
+
"else -- that's a separate, not-yet-built action.",
|
|
2553
|
+
inputSchema: EXPORT_LEDGER_PROVENANCE_INPUT_SCHEMA,
|
|
2554
|
+
},
|
|
2480
2555
|
],
|
|
2481
2556
|
}));
|
|
2482
2557
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
@@ -2621,6 +2696,27 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2621
2696
|
};
|
|
2622
2697
|
}
|
|
2623
2698
|
}
|
|
2699
|
+
if (request.params.name === EXPORT_LEDGER_PROVENANCE_TOOL_NAME) {
|
|
2700
|
+
const args = request.params.arguments;
|
|
2701
|
+
try {
|
|
2702
|
+
const entry = readLedgerEntries(args.project_id).find((e) => e.id === args.ledger_entry_id);
|
|
2703
|
+
if (!entry) {
|
|
2704
|
+
throw new Error(`No ledger entry with id "${args.ledger_entry_id}" found for project_id "${args.project_id}". Use read_ledger to list entries and their ids.`);
|
|
2705
|
+
}
|
|
2706
|
+
return {
|
|
2707
|
+
content: [
|
|
2708
|
+
{ type: "text", text: JSON.stringify({ ledger_entry_id: entry.id, markdown: formatProvenanceArtifact(entry) }) },
|
|
2709
|
+
],
|
|
2710
|
+
};
|
|
2711
|
+
}
|
|
2712
|
+
catch (err) {
|
|
2713
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2714
|
+
return {
|
|
2715
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
2716
|
+
isError: true,
|
|
2717
|
+
};
|
|
2718
|
+
}
|
|
2719
|
+
}
|
|
2624
2720
|
throw new Error(`Unknown tool: ${request.params.name}`);
|
|
2625
2721
|
});
|
|
2626
2722
|
async function main() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pattern-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "MCP tool that judges whether a UI component need should be met with an existing shadcn/ui, 21st.dev, or ReUI component or requires a custom build, using field/requirement coverage scored against real component code.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|