@linktr.ee/arbor-mcp 0.4.0-rc.fc311a291.7198 → 0.5.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 (2) hide show
  1. package/dist/index.js +305 -46
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -33,7 +33,6 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
33
33
  var require_rules = __commonJS({
34
34
  "../../docs/content/ux-writing/rules.json"(exports, module) {
35
35
  module.exports = {
36
- $schema: "./types.schema.json",
37
36
  version: "1.0.0",
38
37
  description: "Static lint rules for UX writing. Consumable by Lambda, Figma plugin, Claude skill, and CI scripts.",
39
38
  rules: {
@@ -160,6 +159,86 @@ var require_rules = __commonJS({
160
159
  pattern: "are you sure",
161
160
  reason: "Patronizing. State consequences clearly instead.",
162
161
  suggestion: "Replace with clear consequence statement: 'This will delete all data'"
162
+ },
163
+ {
164
+ pattern: "\\btier\\b",
165
+ matchType: "regex",
166
+ label: "tier",
167
+ reason: 'Linktree plans are called "plans", not "tiers". "Tier" is internal language.',
168
+ suggestion: 'Use "plan" instead of "tier".'
169
+ },
170
+ {
171
+ pattern: "\\bPRO\\b",
172
+ matchType: "regex",
173
+ caseSensitive: true,
174
+ label: "PRO",
175
+ reason: 'Plan names use title case: "Pro", not "PRO".',
176
+ suggestion: 'Write the plan name as "Pro".'
177
+ },
178
+ {
179
+ pattern: "\\b(?:your|my|their|our|the|a|an|this|that)\\s+profiles?\\b(?!\\s+(?:photo|image|picture|avatar|url|link))",
180
+ matchType: "regex",
181
+ label: "profile",
182
+ reason: `Don't call a Linktree a generic "profile".`,
183
+ suggestion: 'Refer to it as your "Linktree" (the proper noun "Linktree Profile" is fine).'
184
+ },
185
+ {
186
+ pattern: "\\bpackage(s)?\\b",
187
+ matchType: "regex",
188
+ label: "package",
189
+ reason: 'Linktree plans are called "plans", not "packages".',
190
+ suggestion: 'Use "plan" instead of "package".'
191
+ },
192
+ {
193
+ pattern: "\\bbucket(s)?\\b",
194
+ matchType: "regex",
195
+ label: "bucket",
196
+ reason: 'Linktree plans are called "plans", not "buckets".',
197
+ suggestion: 'Use "plan" instead of "bucket".'
198
+ },
199
+ {
200
+ pattern: "\\busers?\\b(?!\\s*-?\\s*(?:friendly|name|names|generated|facing|flow|flows|journey|journeys|persona|personas|research|testing|experience|interface|story|stories))",
201
+ matchType: "regex",
202
+ label: "users",
203
+ reason: 'People who use Linktree are "Linkers", not "users".',
204
+ suggestion: 'Use "Linkers" \u2014 or just say "you".'
205
+ },
206
+ {
207
+ pattern: "\\bLink Tree\\b",
208
+ matchType: "regex",
209
+ label: "Link Tree",
210
+ reason: 'Linktree is one word: "Linktree", not "Link Tree".',
211
+ suggestion: 'Write it as "Linktree".'
212
+ },
213
+ {
214
+ pattern: "\\bLinkTree\\b",
215
+ matchType: "regex",
216
+ caseSensitive: true,
217
+ label: "LinkTree",
218
+ reason: 'Linktree capitalizes only the L: "Linktree", not "LinkTree".',
219
+ suggestion: 'Write it as "Linktree".'
220
+ },
221
+ {
222
+ pattern: "\\b(linkinbio|link-in-bio|bio link)\\b",
223
+ matchType: "regex",
224
+ label: "link-in-bio",
225
+ reason: `It's "link in bio" \u2014 three words, no hyphen.`,
226
+ suggestion: 'Use "link in bio" (not "linkinbio", "link-in-bio" or "bio link").'
227
+ },
228
+ {
229
+ pattern: "subscription",
230
+ reason: 'Linktree billing is a "plan", not a "subscription".',
231
+ suggestion: 'Use "plan". (Keep "subscribe"/"subscriber" for the visitor notification feature.)'
232
+ },
233
+ {
234
+ pattern: "utilize",
235
+ reason: "Formal and stiff. Plain language is friendlier.",
236
+ suggestion: 'Use "use" instead of "utilize".'
237
+ },
238
+ {
239
+ pattern: "facilitate",
240
+ reason: "Formal and stiff. Plain language is friendlier.",
241
+ suggestion: 'Use "help" instead of "facilitate".'
163
242
  }
164
243
  ]
165
244
  },
@@ -462,7 +541,7 @@ var require_patterns = __commonJS({
462
541
  examples: {
463
542
  do: [
464
543
  "Update required. This version is no longer supported. Update now to continue.",
465
- "Subscription expired. Your account is paused. Renew subscription to restore access.",
544
+ "Plan expired. Your account is paused. Renew your plan to restore access.",
466
545
  "Verification needed. Confirm your email to access features. Check your inbox."
467
546
  ],
468
547
  dont: [
@@ -511,7 +590,7 @@ var require_patterns = __commonJS({
511
590
  do: [
512
591
  "Changes saved",
513
592
  "Email sent",
514
- "Profile updated",
593
+ "Linktree updated",
515
594
  "You're all set",
516
595
  "You're officially a Pro!"
517
596
  ],
@@ -915,6 +994,7 @@ var require_engine = __commonJS({
915
994
  var contentTypesData = require_content_types();
916
995
  var rules = rulesData.rules;
917
996
  var scoring = rulesData.scoring;
997
+ var MIN_CONTENT_TYPE_CONFIDENCE = 30;
918
998
  var passiveVoiceRegex = new RegExp(
919
999
  rules["passive-voice"].check.pattern,
920
1000
  "gi"
@@ -931,6 +1011,15 @@ var require_engine = __commonJS({
931
1011
  }
932
1012
  compiledTextPatterns.set(typeId, regexes);
933
1013
  }
1014
+ var compiledBannedPhrases = rules["banned-phrases"].phrases.map((phrase) => {
1015
+ if (phrase.matchType !== "regex") return phrase;
1016
+ try {
1017
+ const flags = phrase.caseSensitive ? "" : "i";
1018
+ return { ...phrase, _regex: new RegExp(phrase.pattern, flags) };
1019
+ } catch {
1020
+ return { ...phrase, _regex: null };
1021
+ }
1022
+ });
934
1023
  function countSyllables(word) {
935
1024
  const w = word.toLowerCase().replace(/[^a-z]/g, "");
936
1025
  if (w.length <= 3) return 1;
@@ -946,34 +1035,42 @@ var require_engine = __commonJS({
946
1035
  }
947
1036
  function checkSentenceCase(text, issues) {
948
1037
  const words = text.split(/\s+/);
949
- for (let i = 1; i < words.length; i++) {
950
- const word = words[i].replace(/[^a-zA-Z]/g, "");
951
- if (word === "I" || /^[^a-zA-Z]*I['\u2019\u2018]/.test(words[i])) continue;
952
- if (word.length > 0 && word[0] === word[0].toUpperCase() && word[0] !== word[0].toLowerCase() && !properNounAllowlist.has(word)) {
953
- issues.push({
954
- ruleId: "sentence-case",
955
- ruleName: "Sentence case",
956
- severity: "warning",
957
- message: `"${word}" appears capitalized. Use sentence case for UI text.`,
958
- suggestion: `Change to "${word.toLowerCase()}"`
959
- });
960
- break;
1038
+ let sentenceStart = true;
1039
+ for (let i = 0; i < words.length; i++) {
1040
+ const raw = words[i];
1041
+ if (!raw) continue;
1042
+ const endsSentence = /[.!?][)\]"'\u2019\u201d]*$/.test(raw);
1043
+ if (!sentenceStart) {
1044
+ const word = raw.replace(/^[^a-zA-Z]+/, "").replace(/[^a-zA-Z]+$/, "");
1045
+ const isPronounI = word.replace(/[^a-zA-Z]/g, "") === "I" || /^[^a-zA-Z]*I['\u2019\u2018]/.test(raw);
1046
+ if (!isPronounI && word.length > 0 && word[0] === word[0].toUpperCase() && word[0] !== word[0].toLowerCase() && !properNounAllowlist.has(word)) {
1047
+ const sentenceCased = word === word.toUpperCase() ? word.toLowerCase() : word[0].toLowerCase() + word.slice(1);
1048
+ issues.push({
1049
+ ruleId: "sentence-case",
1050
+ ruleName: "Sentence case",
1051
+ severity: "warning",
1052
+ message: `"${word}" appears capitalized. Use sentence case for UI text.`,
1053
+ suggestion: `Change to "${sentenceCased}"`
1054
+ });
1055
+ break;
1056
+ }
961
1057
  }
1058
+ sentenceStart = endsSentence;
962
1059
  }
963
1060
  }
964
1061
  function checkBannedPhrases(text, contentType, issues) {
965
1062
  const lower = text.toLowerCase();
966
- for (const phrase of rules["banned-phrases"].phrases) {
967
- if (lower.includes(phrase.pattern)) {
968
- const severity = contentType === "button" ? rules["banned-phrases"].severityByContext.button : rules["banned-phrases"].severityByContext.other;
969
- issues.push({
970
- ruleId: "banned-phrases",
971
- ruleName: "Banned phrase",
972
- severity,
973
- message: `"${phrase.pattern}" \u2014 ${phrase.reason}`,
974
- suggestion: phrase.suggestion
975
- });
976
- }
1063
+ for (const phrase of compiledBannedPhrases) {
1064
+ const matched = phrase.matchType === "regex" ? phrase._regex !== null && phrase._regex.test(text) : lower.includes(phrase.pattern);
1065
+ if (!matched) continue;
1066
+ const severity = contentType === "button" ? rules["banned-phrases"].severityByContext.button : rules["banned-phrases"].severityByContext.other;
1067
+ issues.push({
1068
+ ruleId: "banned-phrases",
1069
+ ruleName: "Banned phrase",
1070
+ severity,
1071
+ message: `"${phrase.label || phrase.pattern}" \u2014 ${phrase.reason}`,
1072
+ suggestion: phrase.suggestion
1073
+ });
977
1074
  }
978
1075
  }
979
1076
  function checkGenericButtons(text, issues) {
@@ -1113,6 +1210,9 @@ var require_engine = __commonJS({
1113
1210
  bestMatch = { type: typeId, confidence: score };
1114
1211
  }
1115
1212
  }
1213
+ if (!bestMatch || bestMatch.confidence < MIN_CONTENT_TYPE_CONFIDENCE) {
1214
+ return null;
1215
+ }
1116
1216
  return bestMatch;
1117
1217
  }
1118
1218
  function getPattern(contentType) {
@@ -5930,6 +6030,7 @@ async function querySupernova(resource, opts = {}) {
5930
6030
  const {
5931
6031
  designSystemId,
5932
6032
  versionId,
6033
+ versionSelector,
5933
6034
  proxyUrl = process.env.ARBOR_MCP_SUPERNOVA_PROXY_URL ?? "",
5934
6035
  proxyToken = process.env.ARBOR_MCP_SUPERNOVA_PROXY_TOKEN ?? "",
5935
6036
  fetchImpl = globalThis.fetch,
@@ -5948,12 +6049,13 @@ async function querySupernova(resource, opts = {}) {
5948
6049
  if (parsed.protocol !== "https:") {
5949
6050
  return { ok: false, reason: "not_configured", detail: `proxy URL must be https (got ${parsed.protocol})` };
5950
6051
  }
5951
- const cacheKey = `${resource}|${designSystemId ?? ""}|${versionId ?? ""}`;
6052
+ const cacheKey = `${resource}|${designSystemId ?? ""}|${versionId ?? ""}|${versionSelector ?? ""}`;
5952
6053
  const cached = cache.get(cacheKey);
5953
6054
  if (cached && now() - cached.fetchedAt < CACHE_TTL_MS2) return cached.value;
5954
6055
  const body = { resource };
5955
6056
  if (designSystemId) body.designSystemId = designSystemId;
5956
6057
  if (versionId) body.versionId = versionId;
6058
+ if (versionSelector && !versionId) body.versionSelector = versionSelector;
5957
6059
  const controller = new AbortController();
5958
6060
  const timer = setTimeout(() => controller.abort(), timeoutMs);
5959
6061
  let res;
@@ -5998,11 +6100,49 @@ async function querySupernova(resource, opts = {}) {
5998
6100
  return value;
5999
6101
  }
6000
6102
 
6103
+ // src/lib/resolve-version.ts
6104
+ function extractVersionList(data) {
6105
+ const wrapped = data?.result?.designSystemVersions;
6106
+ const list = Array.isArray(wrapped) ? wrapped : Array.isArray(data) ? data : null;
6107
+ if (list === null) return null;
6108
+ return list.filter((x) => !!x && typeof x === "object");
6109
+ }
6110
+ async function resolveVersionId(version, deps = {}) {
6111
+ const query = deps.query ?? ((r) => querySupernova(r));
6112
+ const result = await query("versions");
6113
+ if (!result.ok) {
6114
+ return { ok: false, reason: "unavailable", detail: `could not load versions (${result.reason})` };
6115
+ }
6116
+ const list = extractVersionList(result.data);
6117
+ if (list === null) {
6118
+ return { ok: false, reason: "unavailable", detail: "proxy returned an unrecognized versions payload" };
6119
+ }
6120
+ const match = list.find((v) => v.isReadonly === true && v.version === version);
6121
+ if (!match) {
6122
+ return { ok: false, reason: "not_found", detail: `no frozen Supernova version "${version}" \u2014 it may not be cut yet (see arbor_list_versions)` };
6123
+ }
6124
+ const versionId = String(match.id ?? "");
6125
+ if (!versionId) {
6126
+ return { ok: false, reason: "unavailable", detail: `frozen version "${version}" has no id` };
6127
+ }
6128
+ const meta = match.meta;
6129
+ const versionName = meta && typeof meta === "object" && typeof meta.name === "string" && meta.name.length > 0 ? meta.name : version;
6130
+ return { ok: true, versionId, versionName };
6131
+ }
6132
+
6001
6133
  // src/tools/list-components.ts
6002
6134
  var TOOL_NAME3 = "arbor_list_components";
6003
6135
  var TOOL_TITLE3 = "List Arbor components";
6004
6136
  var TOOL_DESCRIPTION3 = "List the components in the Arbor design system (name, id, short description) from Supernova via the Arbor federation proxy. Read-only metadata for discovery; returns top-level components, not Figma variants. Does NOT return token values, source code, or props \u2014 use arbor_get_component for one component, the shadcn registry/Storybook for source, arbor_open_in_playroom for a runnable snippet. Does NOT change anything.";
6005
- var inputSchema3 = external_exports.object({ query: external_exports.string().trim().min(1).max(100).optional().describe("Optional case-insensitive name/description filter") }).strict();
6137
+ var inputSchema3 = external_exports.object({
6138
+ query: external_exports.string().trim().min(1).max(100).optional().describe("Optional case-insensitive name/description filter"),
6139
+ versionSelector: external_exports.enum(["draft", "latest_released"]).optional().describe(
6140
+ "Which design system version to read: 'draft' (default \u2014 the editable working version) or 'latest_released' (the newest frozen release; available only once a release has been cut, otherwise unavailable)"
6141
+ ),
6142
+ version: external_exports.string().trim().min(1).max(50).optional().describe(
6143
+ 'List components as of a specific released version, e.g. "15.1.0" \u2014 resolves to that frozen Supernova version ("what shipped in 15.1.0"). Takes precedence over versionSelector. See arbor_list_versions for the versions that have been cut.'
6144
+ )
6145
+ }).strict();
6006
6146
  var outputSchema3 = external_exports.object({
6007
6147
  source: external_exports.enum(["remote", "unavailable"]),
6008
6148
  total: external_exports.number(),
@@ -6025,8 +6165,23 @@ function extractComponents(data) {
6025
6165
  });
6026
6166
  }
6027
6167
  async function handleListComponents(args, deps = {}) {
6028
- const query = deps.query ?? ((r) => querySupernova(r));
6029
- const result = await query("components");
6168
+ const query = deps.query ?? ((r, opts) => querySupernova(r, opts));
6169
+ let queryOpts;
6170
+ if (args.version) {
6171
+ const resolveVersion = deps.resolveVersion ?? ((v) => resolveVersionId(v));
6172
+ const resolved = await resolveVersion(args.version);
6173
+ if (!resolved.ok) {
6174
+ const out2 = { source: "unavailable", total: 0, components: [], detail: resolved.detail };
6175
+ return {
6176
+ content: [{ type: "text", text: `Could not read version ${args.version}: ${resolved.detail}. Treat as unavailable \u2014 do not assume there are none.` }],
6177
+ structuredContent: out2
6178
+ };
6179
+ }
6180
+ queryOpts = { versionId: resolved.versionId };
6181
+ } else {
6182
+ queryOpts = { versionSelector: args.versionSelector };
6183
+ }
6184
+ const result = await query("components", queryOpts);
6030
6185
  let out;
6031
6186
  const extracted = result.ok ? extractComponents(result.data) : null;
6032
6187
  if (!result.ok) {
@@ -6058,7 +6213,13 @@ var TOOL_TITLE4 = "Get one Arbor component";
6058
6213
  var TOOL_DESCRIPTION4 = "Get a single Arbor component's metadata by name (case-insensitive) or id from Supernova via the Arbor federation proxy (name, id, description). Read-only; matches top-level components, not Figma variants. Does NOT return token values or rendered source \u2014 use the shadcn registry / Storybook for implementation and arbor_open_in_playroom for a runnable snippet. Does NOT change anything.";
6059
6214
  var inputSchema4 = external_exports.object({
6060
6215
  name: external_exports.string().trim().min(1).max(100).optional().describe('Component name (case-insensitive), e.g. "Button"'),
6061
- id: external_exports.string().trim().min(1).max(100).optional().describe("Supernova component id")
6216
+ id: external_exports.string().trim().min(1).max(100).optional().describe("Supernova component id"),
6217
+ versionSelector: external_exports.enum(["draft", "latest_released"]).optional().describe(
6218
+ "Which design system version to read: 'draft' (default \u2014 the editable working version) or 'latest_released' (the newest frozen release; available only once a release has been cut, otherwise unavailable)"
6219
+ ),
6220
+ version: external_exports.string().trim().min(1).max(50).optional().describe(
6221
+ 'Read a specific released version, e.g. "15.1.0" \u2014 resolves to that frozen Supernova version and reads the component there ("what shipped in 15.1.0"). Takes precedence over versionSelector. See arbor_list_versions for the versions that have been cut.'
6222
+ )
6062
6223
  }).strict();
6063
6224
  var outputSchema4 = external_exports.object({
6064
6225
  source: external_exports.enum(["remote", "unavailable"]),
@@ -6077,8 +6238,23 @@ async function handleGetComponent(args, deps = {}) {
6077
6238
  structuredContent: out2
6078
6239
  };
6079
6240
  }
6080
- const query = deps.query ?? ((r) => querySupernova(r));
6081
- const result = await query("components");
6241
+ const query = deps.query ?? ((r, opts) => querySupernova(r, opts));
6242
+ let queryOpts;
6243
+ if (args.version) {
6244
+ const resolveVersion = deps.resolveVersion ?? ((v) => resolveVersionId(v));
6245
+ const resolved = await resolveVersion(args.version);
6246
+ if (!resolved.ok) {
6247
+ const out2 = { source: "unavailable", found: false, component: null, detail: resolved.detail };
6248
+ return {
6249
+ content: [{ type: "text", text: `Could not read version ${args.version}: ${resolved.detail}. Treat as unavailable.` }],
6250
+ structuredContent: out2
6251
+ };
6252
+ }
6253
+ queryOpts = { versionId: resolved.versionId };
6254
+ } else {
6255
+ queryOpts = { versionSelector: args.versionSelector };
6256
+ }
6257
+ const result = await query("components", queryOpts);
6082
6258
  let out;
6083
6259
  if (!result.ok) {
6084
6260
  out = { source: "unavailable", found: false, component: null, detail: `could not load components (${result.reason})` };
@@ -6103,15 +6279,38 @@ var getComponentTool = {
6103
6279
  // src/tools/list-versions.ts
6104
6280
  var TOOL_NAME5 = "arbor_list_versions";
6105
6281
  var TOOL_TITLE5 = "List Arbor design system versions";
6106
- var TOOL_DESCRIPTION5 = "List the version history of the Arbor design system (id, name, created date, read-only flag) from Supernova via the Arbor federation proxy. Read-only metadata. Does NOT return token values (use the compiled CSS / registry) and does NOT change anything.";
6282
+ var TOOL_DESCRIPTION5 = "List the version history of the Arbor design system from Supernova (id, name, semver, created date, read-only flag, draft / latest-released markers) via the Arbor federation proxy, plus the committed release manifest (npm release \u2194 Supernova version). Read-only metadata. Does NOT return token values (use the compiled CSS / registry) and does NOT change anything.";
6107
6283
  var inputSchema5 = external_exports.object({}).strict();
6284
+ var versionSchema = external_exports.object({
6285
+ id: external_exports.string(),
6286
+ name: external_exports.string(),
6287
+ semver: external_exports.string().nullable().describe('The version string when it parses as semver (X.Y.Z); null for the draft, whose version is a label like "Shared Draft"'),
6288
+ createdAt: external_exports.string().nullable(),
6289
+ readOnly: external_exports.boolean().nullable(),
6290
+ isDraft: external_exports.boolean().nullable().describe("The editable working version (readOnly === false). DNG-594: a draft's version is a label, not null \u2014 so readOnly is the signal."),
6291
+ isLatestReleased: external_exports.boolean().describe("True for the single newest read-only (frozen) version by createdAt")
6292
+ });
6293
+ var manifestReleaseSchema = external_exports.object({
6294
+ releaseId: external_exports.string(),
6295
+ date: external_exports.string().nullable(),
6296
+ frontDoorVersion: external_exports.string().nullable().describe("The @linktr.ee/arbor version for this release (DL-076 front-door number)"),
6297
+ supernovaVersionId: external_exports.string().nullable().describe("The frozen Supernova version id this release was cut as (null until the cutter + stamper run \u2014 later phases)"),
6298
+ supernovaStatus: external_exports.string().nullable()
6299
+ });
6108
6300
  var outputSchema5 = external_exports.object({
6109
- source: external_exports.enum(["remote", "unavailable"]).describe("'remote' = fetched; 'unavailable' = proxy fetch failed"),
6110
- total: external_exports.number().describe("Number of versions returned"),
6111
- versions: external_exports.array(external_exports.object({ id: external_exports.string(), name: external_exports.string(), createdAt: external_exports.string().nullable(), readOnly: external_exports.boolean().nullable() })).describe("Versions, in the order Supernova returned them"),
6301
+ source: external_exports.enum(["remote", "unavailable"]).describe("'remote' = Supernova versions fetched; 'unavailable' = proxy fetch failed"),
6302
+ total: external_exports.number().describe("Number of Supernova versions returned"),
6303
+ versions: external_exports.array(versionSchema).describe("Supernova versions, in the order returned"),
6304
+ manifest: external_exports.object({
6305
+ source: external_exports.enum(["local", "unavailable"]).describe("'local' = release manifest loaded; 'unavailable' = not published yet / degraded"),
6306
+ total: external_exports.number(),
6307
+ releases: external_exports.array(manifestReleaseSchema),
6308
+ detail: external_exports.string().nullable()
6309
+ }).describe("The committed release manifest (releases.json) mapping npm releases \u2194 Supernova versions"),
6112
6310
  detail: external_exports.string().nullable().describe("Degraded reason when source is unavailable")
6113
6311
  });
6114
6312
  var annotations5 = { readOnlyHint: true, idempotentHint: true, openWorldHint: true, destructiveHint: false };
6313
+ var SEMVER_RE = /^\d+\.\d+\.\d+(?:[-+].*)?$/;
6115
6314
  function extractList(data) {
6116
6315
  const wrapped = data?.result?.designSystemVersions;
6117
6316
  const list = Array.isArray(wrapped) ? wrapped : Array.isArray(data) ? data : null;
@@ -6119,26 +6318,86 @@ function extractList(data) {
6119
6318
  return list.filter((x) => !!x && typeof x === "object");
6120
6319
  }
6121
6320
  var str2 = (v) => typeof v === "string" && v.length > 0 ? v : null;
6321
+ var semverOf = (v) => {
6322
+ const s = str2(v);
6323
+ return s && SEMVER_RE.test(s) ? s : null;
6324
+ };
6325
+ var createdAtEpoch = (s) => {
6326
+ const t = new Date(s ?? 0).getTime();
6327
+ return Number.isFinite(t) ? t : 0;
6328
+ };
6329
+ var metaName = (v) => {
6330
+ const meta = v.meta;
6331
+ return meta && typeof meta === "object" ? str2(meta.name) : null;
6332
+ };
6333
+ function latestReleasedId(versions) {
6334
+ const frozen = versions.filter((v) => v.readOnly === true);
6335
+ if (frozen.length === 0) return null;
6336
+ frozen.sort((a, b) => createdAtEpoch(b.createdAt) - createdAtEpoch(a.createdAt));
6337
+ return frozen[0].id || null;
6338
+ }
6339
+ async function buildManifestLane(load) {
6340
+ const res = await load("releases.json");
6341
+ if (!res.ok) {
6342
+ return { source: "unavailable", total: 0, releases: [], detail: `could not load release manifest (${res.reason})` };
6343
+ }
6344
+ const data = res.data;
6345
+ if (data?.degraded === true) {
6346
+ return { source: "unavailable", total: 0, releases: [], detail: "release manifest is a degraded fallback" };
6347
+ }
6348
+ if (!data || !Array.isArray(data.releases)) {
6349
+ return { source: "unavailable", total: 0, releases: [], detail: "unrecognized releases.json payload" };
6350
+ }
6351
+ const releases = data.releases.filter((r) => !!r && typeof r === "object").map((r) => {
6352
+ const sn = r.supernova ?? {};
6353
+ return {
6354
+ releaseId: String(r.releaseId ?? ""),
6355
+ date: str2(r.date),
6356
+ frontDoorVersion: str2(r.frontDoorVersion),
6357
+ supernovaVersionId: str2(sn.versionId),
6358
+ supernovaStatus: str2(sn.status)
6359
+ };
6360
+ });
6361
+ return { source: "local", total: releases.length, releases, detail: null };
6362
+ }
6122
6363
  async function handleListVersions(_args, deps = {}) {
6123
6364
  const query = deps.query ?? ((r) => querySupernova(r));
6365
+ const load = deps.load ?? ((f) => loadPublishedJson(f));
6124
6366
  const result = await query("versions");
6125
- let out;
6126
6367
  const list = result.ok ? extractList(result.data) : null;
6368
+ let source;
6369
+ let versions;
6370
+ let detail;
6127
6371
  if (!result.ok) {
6128
- out = { source: "unavailable", total: 0, versions: [], detail: `could not load versions (${result.reason})` };
6372
+ source = "unavailable";
6373
+ versions = [];
6374
+ detail = `could not load versions (${result.reason})`;
6129
6375
  } else if (list === null) {
6130
- out = { source: "unavailable", total: 0, versions: [], detail: "proxy returned an unrecognized versions payload" };
6376
+ source = "unavailable";
6377
+ versions = [];
6378
+ detail = "proxy returned an unrecognized versions payload";
6131
6379
  } else {
6132
- const versions = list.map((v) => ({
6380
+ const base = list.map((v) => ({
6133
6381
  id: String(v.id ?? ""),
6134
- name: str2(v.version) ?? str2(v.name) ?? "(unnamed)",
6382
+ name: metaName(v) ?? str2(v.version) ?? "(unnamed)",
6383
+ semver: semverOf(v.version),
6135
6384
  createdAt: str2(v.createdAt),
6136
6385
  readOnly: typeof v.isReadonly === "boolean" ? v.isReadonly : null
6137
6386
  }));
6138
- out = { source: "remote", total: versions.length, versions, detail: null };
6139
- }
6140
- const summary = out.source === "unavailable" ? `Could not load design system versions (${out.detail}). Treat as unavailable \u2014 do not assume there are none.` : `${out.total} design system version${out.total === 1 ? "" : "s"}: ${out.versions.map((v) => v.name).join(", ") || "(none)"}.`;
6141
- return { content: [{ type: "text", text: summary }], structuredContent: out };
6387
+ const latest = latestReleasedId(base);
6388
+ versions = base.map((v) => ({
6389
+ ...v,
6390
+ isDraft: v.readOnly === null ? null : v.readOnly === false,
6391
+ isLatestReleased: latest !== null && v.id === latest
6392
+ }));
6393
+ source = "remote";
6394
+ detail = null;
6395
+ }
6396
+ const manifest = await buildManifestLane(load);
6397
+ const out = { source, total: versions.length, versions, manifest, detail };
6398
+ const versionsLine = out.source === "unavailable" ? `Supernova versions unavailable (${out.detail}) \u2014 do not assume there are none.` : `${out.total} Supernova version${out.total === 1 ? "" : "s"}: ${out.versions.map((v) => v.name).join(", ") || "(none)"}.`;
6399
+ const manifestLine = manifest.source === "unavailable" ? `Release manifest unavailable (${manifest.detail}).` : `${manifest.total} manifest release${manifest.total === 1 ? "" : "s"} (front-door: ${manifest.releases.map((r) => r.frontDoorVersion).filter(Boolean).join(", ") || "n/a"}).`;
6400
+ return { content: [{ type: "text", text: `${versionsLine} ${manifestLine}` }], structuredContent: out };
6142
6401
  }
6143
6402
  var listVersionsTool = {
6144
6403
  name: TOOL_NAME5,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linktr.ee/arbor-mcp",
3
- "version": "0.4.0-rc.fc311a291.7198",
3
+ "version": "0.5.0",
4
4
  "description": "Model Context Protocol server exposing Arbor design system tools: Playroom snippets, Figma→code sync health, UX-writing checks, decision-log lookup, and federated component/version metadata.",
5
5
  "keywords": [
6
6
  "arbor",