skilld 1.7.3 → 2.0.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 (161) hide show
  1. package/dist/_chunks/add.mjs +66 -0
  2. package/dist/_chunks/add.mjs.map +1 -0
  3. package/dist/_chunks/agent-prompt.mjs +88 -0
  4. package/dist/_chunks/agent-prompt.mjs.map +1 -0
  5. package/dist/_chunks/agent.mjs +737 -619
  6. package/dist/_chunks/agent.mjs.map +1 -1
  7. package/dist/_chunks/args.mjs +42 -0
  8. package/dist/_chunks/args.mjs.map +1 -0
  9. package/dist/_chunks/assemble.mjs +11 -8
  10. package/dist/_chunks/assemble.mjs.map +1 -1
  11. package/dist/_chunks/author.mjs +77 -131
  12. package/dist/_chunks/author.mjs.map +1 -1
  13. package/dist/_chunks/cache.mjs +320 -54
  14. package/dist/_chunks/cache.mjs.map +1 -1
  15. package/dist/_chunks/cache2.mjs +7 -6
  16. package/dist/_chunks/cache2.mjs.map +1 -1
  17. package/dist/_chunks/client.mjs +117 -0
  18. package/dist/_chunks/client.mjs.map +1 -0
  19. package/dist/_chunks/core.mjs +7 -4
  20. package/dist/_chunks/detect.mjs +54 -44
  21. package/dist/_chunks/detect.mjs.map +1 -1
  22. package/dist/_chunks/eject.mjs +69 -0
  23. package/dist/_chunks/eject.mjs.map +1 -0
  24. package/dist/_chunks/embedding-cache2.mjs +2 -2
  25. package/dist/_chunks/env.mjs +19 -0
  26. package/dist/_chunks/env.mjs.map +1 -0
  27. package/dist/_chunks/install-many.mjs +376 -0
  28. package/dist/_chunks/install-many.mjs.map +1 -0
  29. package/dist/_chunks/install.mjs +86 -371
  30. package/dist/_chunks/install.mjs.map +1 -1
  31. package/dist/_chunks/intro.mjs +63 -0
  32. package/dist/_chunks/intro.mjs.map +1 -0
  33. package/dist/_chunks/list.mjs +2 -2
  34. package/dist/_chunks/list.mjs.map +1 -1
  35. package/dist/_chunks/lockfile.mjs +31 -7
  36. package/dist/_chunks/lockfile.mjs.map +1 -1
  37. package/dist/_chunks/login.mjs +233 -0
  38. package/dist/_chunks/login.mjs.map +1 -0
  39. package/dist/_chunks/logout.mjs +27 -0
  40. package/dist/_chunks/logout.mjs.map +1 -0
  41. package/dist/_chunks/map.mjs +11 -0
  42. package/dist/_chunks/map.mjs.map +1 -0
  43. package/dist/_chunks/markdown.mjs +79 -54
  44. package/dist/_chunks/markdown.mjs.map +1 -1
  45. package/dist/_chunks/menu.mjs +33 -0
  46. package/dist/_chunks/menu.mjs.map +1 -0
  47. package/dist/_chunks/model-picker.mjs +61 -0
  48. package/dist/_chunks/model-picker.mjs.map +1 -0
  49. package/dist/_chunks/monorepo.mjs +73 -0
  50. package/dist/_chunks/monorepo.mjs.map +1 -0
  51. package/dist/_chunks/package-json.mjs.map +1 -1
  52. package/dist/_chunks/paths.mjs +47 -0
  53. package/dist/_chunks/paths.mjs.map +1 -0
  54. package/dist/_chunks/pipeline.mjs +985 -0
  55. package/dist/_chunks/pipeline.mjs.map +1 -0
  56. package/dist/_chunks/pool2.mjs +2 -2
  57. package/dist/_chunks/portable.mjs +151 -0
  58. package/dist/_chunks/portable.mjs.map +1 -0
  59. package/dist/_chunks/prepare-hook.mjs +2 -0
  60. package/dist/_chunks/prepare-hook2.mjs +61 -0
  61. package/dist/_chunks/prepare-hook2.mjs.map +1 -0
  62. package/dist/_chunks/prepare.mjs +47 -3
  63. package/dist/_chunks/prepare.mjs.map +1 -1
  64. package/dist/_chunks/prepare2.mjs +9 -8
  65. package/dist/_chunks/prepare2.mjs.map +1 -1
  66. package/dist/_chunks/prompts.mjs +784 -26
  67. package/dist/_chunks/prompts.mjs.map +1 -1
  68. package/dist/_chunks/pull.mjs +219 -0
  69. package/dist/_chunks/pull.mjs.map +1 -0
  70. package/dist/_chunks/regex.mjs +19 -0
  71. package/dist/_chunks/regex.mjs.map +1 -0
  72. package/dist/_chunks/retriv.mjs +2 -171
  73. package/dist/_chunks/retriv2.mjs +159 -0
  74. package/dist/_chunks/retriv2.mjs.map +1 -0
  75. package/dist/_chunks/sanitize.mjs +12 -9
  76. package/dist/_chunks/sanitize.mjs.map +1 -1
  77. package/dist/_chunks/search-helpers.mjs +9 -8
  78. package/dist/_chunks/search-helpers.mjs.map +1 -1
  79. package/dist/_chunks/search-interactive.mjs +23 -20
  80. package/dist/_chunks/search-interactive.mjs.map +1 -1
  81. package/dist/_chunks/search.mjs +3 -4
  82. package/dist/_chunks/search.mjs.map +1 -1
  83. package/dist/_chunks/{sources.mjs → semver.mjs} +1128 -838
  84. package/dist/_chunks/semver.mjs.map +1 -0
  85. package/dist/_chunks/skill-installer.mjs +2 -0
  86. package/dist/_chunks/skill-installer2.mjs +154 -0
  87. package/dist/_chunks/skill-installer2.mjs.map +1 -0
  88. package/dist/_chunks/skills.mjs +12 -12
  89. package/dist/_chunks/skills.mjs.map +1 -1
  90. package/dist/_chunks/store.mjs +107 -0
  91. package/dist/_chunks/store.mjs.map +1 -0
  92. package/dist/_chunks/sync.mjs +761 -1349
  93. package/dist/_chunks/sync.mjs.map +1 -1
  94. package/dist/_chunks/sync2.mjs +2 -3
  95. package/dist/_chunks/telemetry.mjs +26 -0
  96. package/dist/_chunks/telemetry.mjs.map +1 -0
  97. package/dist/_chunks/uninstall.mjs +15 -13
  98. package/dist/_chunks/uninstall.mjs.map +1 -1
  99. package/dist/_chunks/update.mjs +171 -0
  100. package/dist/_chunks/update.mjs.map +1 -0
  101. package/dist/_chunks/upload.mjs +4 -4
  102. package/dist/_chunks/validate.mjs +1 -1
  103. package/dist/_chunks/version.mjs +16 -27
  104. package/dist/_chunks/version.mjs.map +1 -1
  105. package/dist/_chunks/whoami.mjs +21 -0
  106. package/dist/_chunks/whoami.mjs.map +1 -0
  107. package/dist/_chunks/wizard.mjs +2 -190
  108. package/dist/_chunks/wizard2.mjs +200 -0
  109. package/dist/_chunks/wizard2.mjs.map +1 -0
  110. package/dist/cli.mjs +77 -59
  111. package/dist/cli.mjs.map +1 -1
  112. package/dist/prepare.mjs +5 -4
  113. package/dist/prepare.mjs.map +1 -1
  114. package/dist/retriv/worker.d.mts +5 -1
  115. package/dist/retriv/worker.d.mts.map +1 -1
  116. package/dist/retriv/worker.mjs +1 -1
  117. package/package.json +20 -29
  118. package/dist/_chunks/author-group.mjs +0 -17
  119. package/dist/_chunks/author-group.mjs.map +0 -1
  120. package/dist/_chunks/cli-helpers.mjs +0 -335
  121. package/dist/_chunks/cli-helpers.mjs.map +0 -1
  122. package/dist/_chunks/cli-helpers2.mjs +0 -2
  123. package/dist/_chunks/config.mjs +0 -122
  124. package/dist/_chunks/config.mjs.map +0 -1
  125. package/dist/_chunks/index.d.mts +0 -151
  126. package/dist/_chunks/index.d.mts.map +0 -1
  127. package/dist/_chunks/index2.d.mts +0 -44
  128. package/dist/_chunks/index2.d.mts.map +0 -1
  129. package/dist/_chunks/index3.d.mts +0 -589
  130. package/dist/_chunks/index3.d.mts.map +0 -1
  131. package/dist/_chunks/prefix.mjs +0 -108
  132. package/dist/_chunks/prefix.mjs.map +0 -1
  133. package/dist/_chunks/retriv.mjs.map +0 -1
  134. package/dist/_chunks/setup.mjs +0 -17
  135. package/dist/_chunks/setup.mjs.map +0 -1
  136. package/dist/_chunks/shared.mjs +0 -503
  137. package/dist/_chunks/shared.mjs.map +0 -1
  138. package/dist/_chunks/skill.mjs +0 -329
  139. package/dist/_chunks/skill.mjs.map +0 -1
  140. package/dist/_chunks/sources.mjs.map +0 -1
  141. package/dist/_chunks/sync-registry.mjs +0 -59
  142. package/dist/_chunks/sync-registry.mjs.map +0 -1
  143. package/dist/_chunks/sync-shared.mjs +0 -2
  144. package/dist/_chunks/sync-shared2.mjs +0 -1020
  145. package/dist/_chunks/sync-shared2.mjs.map +0 -1
  146. package/dist/_chunks/types.d.mts +0 -88
  147. package/dist/_chunks/types.d.mts.map +0 -1
  148. package/dist/_chunks/wizard.mjs.map +0 -1
  149. package/dist/agent/index.d.mts +0 -346
  150. package/dist/agent/index.d.mts.map +0 -1
  151. package/dist/agent/index.mjs +0 -5
  152. package/dist/cache/index.d.mts +0 -2
  153. package/dist/cache/index.mjs +0 -4
  154. package/dist/index.d.mts +0 -5
  155. package/dist/index.mjs +0 -5
  156. package/dist/retriv/index.d.mts +0 -3
  157. package/dist/retriv/index.mjs +0 -2
  158. package/dist/sources/index.d.mts +0 -2
  159. package/dist/sources/index.mjs +0 -3
  160. package/dist/types.d.mts +0 -4
  161. package/dist/types.mjs +0 -1
@@ -1,21 +1,27 @@
1
- import { t as getCacheDir } from "./version.mjs";
2
- import { i as readPackageJsonSafe } from "./package-json.mjs";
1
+ import { D as getCrawlUrl, E as getBlogPreset, O as getDocOverride } from "./prompts.mjs";
2
+ import { a as GIT_PROTOCOL_PREFIX_RE, c as NPM_SCOPE_PREFIX_RE, g as V_PREFIX_RE, h as VERSION_RANGE_PREFIX_RE, i as GIT_PLUS_PREFIX_RE, l as NPM_SCOPE_WITH_SLASH_RE, m as TRAILING_SLASH_RE, o as GIT_SUFFIX_RE, r as GITHUB_SSH_URL_PREFIX_RE, s as LEADING_SLASH_RE, u as README_FILENAME_RE } from "./regex.mjs";
3
3
  import { t as yamlEscape } from "./yaml.mjs";
4
+ import { s as getCacheDir } from "./prepare.mjs";
5
+ import { i as readPackageJsonSafe } from "./package-json.mjs";
6
+ import "./cache.mjs";
7
+ import { t as mapInsert } from "./map.mjs";
4
8
  import { i as parseFrontmatter, n as extractLinks, r as extractTitle, t as extractDescription } from "./markdown.mjs";
5
- import { c as getBlogPreset, l as getCrawlUrl, r as mapInsert, u as getDocOverride } from "./shared.mjs";
6
- import { tmpdir } from "node:os";
7
- import { basename, dirname, join, resolve } from "pathe";
9
+ import { createRequire } from "node:module";
8
10
  import { createWriteStream, existsSync, mkdirSync, readFileSync, readdirSync, rmSync } from "node:fs";
9
- import { htmlToMarkdown } from "mdream";
10
11
  import pLimit from "p-limit";
12
+ import { basename, dirname, join, resolve } from "pathe";
11
13
  import { spawnSync } from "node:child_process";
12
- import { ofetch } from "ofetch";
14
+ import { tmpdir } from "node:os";
15
+ import { glob } from "node:fs/promises";
13
16
  import { fileURLToPath, pathToFileURL } from "node:url";
17
+ import { htmlToMarkdown } from "mdream";
18
+ import { ofetch } from "ofetch";
14
19
  import { crawlAndGenerate } from "@mdream/crawl";
15
- import { glob } from "tinyglobby";
16
20
  import { downloadTemplate } from "giget";
17
21
  import { Writable } from "node:stream";
18
- import { resolvePathSync } from "mlly";
22
+ import { diff, gt, valid } from "semver";
23
+ const STATIC_REGEX_1$9 = /```[\s\S]*?```/;
24
+ const STATIC_REGEX_2$4 = /`[^`]+`/;
19
25
  const BOT_USERS = new Set([
20
26
  "renovate[bot]",
21
27
  "dependabot[bot]",
@@ -31,7 +37,7 @@ function buildFrontmatter(fields) {
31
37
  return lines.join("\n");
32
38
  }
33
39
  function hasCodeBlock(text) {
34
- return /```[\s\S]*?```/.test(text) || /`[^`]+`/.test(text);
40
+ return STATIC_REGEX_1$9.test(text) || STATIC_REGEX_2$4.test(text);
35
41
  }
36
42
  const COMMENT_NOISE_RE = /^(?:\+1|👍|same here|any update|bump|following|is there any progress|when will this|me too|i have the same|same issue|thanks|thank you)[\s!?.]*$/i;
37
43
  function truncateBody(body, limit) {
@@ -79,6 +85,15 @@ function markRepoPrivate(owner, repo) {
79
85
  function isKnownPrivateRepo(owner, repo) {
80
86
  return _needsAuth.has(`${owner}/${repo}`);
81
87
  }
88
+ async function fetchUnghOrApi(owner, repo, ungh, api) {
89
+ if (!isKnownPrivateRepo(owner, repo)) {
90
+ const r = await ungh().catch(() => null);
91
+ if (r) return r;
92
+ }
93
+ const r = await api();
94
+ if (r) markRepoPrivate(owner, repo);
95
+ return r;
96
+ }
82
97
  const GH_API = "https://api.github.com";
83
98
  const ghApiFetch = ofetch.create({
84
99
  retry: 2,
@@ -172,92 +187,12 @@ async function verifyUrl(url) {
172
187
  if (!res) return false;
173
188
  return !(res.headers.get("content-type") || "").includes("text/html");
174
189
  }
175
- const USELESS_HOSTS = new Set([
176
- "twitter.com",
177
- "x.com",
178
- "facebook.com",
179
- "linkedin.com",
180
- "youtube.com",
181
- "instagram.com",
182
- "npmjs.com",
183
- "www.npmjs.com",
184
- "yarnpkg.com"
185
- ]);
186
- function isUselessDocsUrl(url) {
187
- try {
188
- const { hostname } = new URL(url);
189
- return USELESS_HOSTS.has(hostname);
190
- } catch {
191
- return false;
192
- }
193
- }
194
- function isGitHubRepoUrl(url) {
195
- try {
196
- const parsed = new URL(url);
197
- return parsed.hostname === "github.com" || parsed.hostname === "www.github.com";
198
- } catch {
199
- return false;
200
- }
201
- }
202
- function isLikelyCodeHostUrl(url) {
203
- if (!url) return false;
204
- try {
205
- const parsed = new URL(url);
206
- return [
207
- "github.com",
208
- "www.github.com",
209
- "gitlab.com",
210
- "www.gitlab.com"
211
- ].includes(parsed.hostname);
212
- } catch {
213
- return false;
214
- }
215
- }
216
- function parseGitHubUrl(url) {
217
- const match = url.match(/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?(?:[/#]|$)/);
218
- if (!match) return null;
219
- return {
220
- owner: match[1],
221
- repo: match[2]
222
- };
223
- }
224
- function parseGitHubRepoSlug(url) {
225
- if (!url) return void 0;
226
- const parsed = parseGitHubUrl(url);
227
- return parsed ? `${parsed.owner}/${parsed.repo}` : void 0;
228
- }
229
- function normalizeRepoUrl(url) {
230
- return url.replace(/^git\+/, "").replace(/#.*$/, "").replace(/\.git$/, "").replace(/^git:\/\//, "https://").replace(/^ssh:\/\/git@github\.com/, "https://github.com").replace(/^git@github\.com:/, "https://github.com/");
231
- }
232
- function parsePackageSpec(spec) {
233
- if (spec.startsWith("@")) {
234
- const slashIdx = spec.indexOf("/");
235
- if (slashIdx !== -1) {
236
- const atIdx = spec.indexOf("@", slashIdx + 1);
237
- if (atIdx !== -1) return {
238
- name: spec.slice(0, atIdx),
239
- tag: spec.slice(atIdx + 1)
240
- };
241
- }
242
- return { name: spec };
243
- }
244
- const atIdx = spec.indexOf("@");
245
- if (atIdx !== -1) return {
246
- name: spec.slice(0, atIdx),
247
- tag: spec.slice(atIdx + 1)
248
- };
249
- return { name: spec };
250
- }
251
- function extractBranchHint(url) {
252
- const hash = url.indexOf("#");
253
- if (hash === -1) return void 0;
254
- const fragment = url.slice(hash + 1);
255
- if (!fragment || fragment === "readme") return void 0;
256
- return fragment;
257
- }
190
+ const STATIC_REGEX_2$3 = /^(\d+)(?:\.(\d+))?(?:\.(\d+))?/;
191
+ const STATIC_REGEX_3$3 = /^\d+\.\d+\.\d+-.+/;
192
+ const STATIC_REGEX_4$1 = /changelog\.md/i;
258
193
  function parseSemver(version) {
259
- const clean = version.replace(/^v/, "");
260
- const match = clean.match(/^(\d+)(?:\.(\d+))?(?:\.(\d+))?/);
194
+ const clean = version.replace(V_PREFIX_RE, "");
195
+ const match = clean.match(STATIC_REGEX_2$3);
261
196
  if (!match) return null;
262
197
  return {
263
198
  major: +match[1],
@@ -273,7 +208,7 @@ function extractVersion(tag, packageName) {
273
208
  const dashMatch = tag.match(new RegExp(`^${escapeRegex(packageName)}-v?(.+)$`));
274
209
  if (dashMatch) return dashMatch[1];
275
210
  }
276
- return tag.replace(/^v/, "");
211
+ return tag.replace(V_PREFIX_RE, "");
277
212
  }
278
213
  function escapeRegex(str) {
279
214
  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -282,7 +217,7 @@ function tagMatchesPackage(tag, packageName) {
282
217
  return tag.startsWith(`${packageName}@`) || tag.startsWith(`${packageName}-v`) || tag.startsWith(`${packageName}-`);
283
218
  }
284
219
  function isPrerelease(version) {
285
- return /^\d+\.\d+\.\d+-.+/.test(version.replace(/^v/, ""));
220
+ return STATIC_REGEX_3$3.test(version.replace(V_PREFIX_RE, ""));
286
221
  }
287
222
  function compareSemver(a, b) {
288
223
  if (a.major !== b.major) return a.major - b.major;
@@ -390,13 +325,13 @@ function generateReleaseIndex(releasesOrOpts, packageName) {
390
325
  }
391
326
  function isStubRelease(release) {
392
327
  const body = (release.markdown || "").trim();
393
- return body.length < 500 && /changelog\.md/i.test(body);
328
+ return body.length < 500 && STATIC_REGEX_4$1.test(body);
394
329
  }
395
330
  async function fetchChangelog(owner, repo, ref, packageName) {
396
331
  const paths = [];
397
332
  if (packageName) {
398
- const shortName = packageName.replace(/^@.*\//, "");
399
- const scopeless = packageName.replace(/^@/, "").replace("/", "-");
333
+ const shortName = packageName.replace(NPM_SCOPE_WITH_SLASH_RE, "");
334
+ const scopeless = packageName.replace(NPM_SCOPE_PREFIX_RE, "").replace("/", "-");
400
335
  const candidates = [...new Set([shortName, scopeless])];
401
336
  for (const name of candidates) paths.push(`packages/${name}/CHANGELOG.md`);
402
337
  }
@@ -430,6 +365,8 @@ async function fetchReleaseNotes(owner, repo, installedVersion, gitRef, packageN
430
365
  content: changelog
431
366
  }];
432
367
  }
368
+ const STATIC_REGEX_1$8 = /<h1[^>]*>([^<]+)<\/h1>/;
369
+ const STATIC_REGEX_2$2 = /<title>([^<]+)<\/title>/;
433
370
  function formatBlogRelease(release) {
434
371
  return `${[
435
372
  "---",
@@ -449,10 +386,10 @@ async function fetchBlogPost(entry) {
449
386
  }).catch(() => null);
450
387
  if (!html) return null;
451
388
  let title = "";
452
- const titleMatch = html.match(/<h1[^>]*>([^<]+)<\/h1>/);
389
+ const titleMatch = html.match(STATIC_REGEX_1$8);
453
390
  if (titleMatch) title = titleMatch[1].trim();
454
391
  if (!title) {
455
- const metaTitleMatch = html.match(/<title>([^<]+)<\/title>/);
392
+ const metaTitleMatch = html.match(STATIC_REGEX_2$2);
456
393
  if (metaTitleMatch) title = metaTitleMatch[1].trim();
457
394
  }
458
395
  const markdown = htmlToMarkdown(html);
@@ -499,342 +436,93 @@ async function fetchBlogReleases(packageName, installedVersion) {
499
436
  content: formatBlogRelease(r)
500
437
  }));
501
438
  }
502
- let _ghAvailable;
503
- function isGhAvailable() {
504
- if (_ghAvailable !== void 0) return _ghAvailable;
505
- const { status } = spawnSync("gh", ["auth", "status"], { stdio: "ignore" });
506
- return _ghAvailable = status === 0;
439
+ const STATIC_REGEX_1$7 = /github\.com\/([^/]+)\/([^/]+?)(?:\.git)?(?:[/#]|$)/;
440
+ const STATIC_REGEX_3$2 = /#.*$/;
441
+ const STATIC_REGEX_7 = /^git@github\.com:/;
442
+ const STATIC_REGEX_8 = /^(?:127\.|10\.|172\.(?:1[6-9]|2\d|3[01])\.|192\.168\.|169\.254\.)/;
443
+ const STATIC_REGEX_9 = /^\[(?:f[cd]|fe[89ab]|::ffff:)/i;
444
+ function parseGitHubUrl(url) {
445
+ const match = url.match(STATIC_REGEX_1$7);
446
+ if (!match) return null;
447
+ return {
448
+ owner: match[1],
449
+ repo: match[2]
450
+ };
507
451
  }
508
- const NOISE_LABELS = new Set([
509
- "duplicate",
510
- "stale",
511
- "invalid",
512
- "wontfix",
513
- "won't fix",
514
- "spam",
515
- "off-topic",
516
- "needs triage",
517
- "triage"
518
- ]);
519
- const FEATURE_LABELS = new Set([
520
- "enhancement",
521
- "feature",
522
- "feature request",
523
- "feature-request",
524
- "proposal",
525
- "rfc",
526
- "idea",
527
- "suggestion"
528
- ]);
529
- const BUG_LABELS = new Set([
530
- "bug",
531
- "defect",
532
- "regression",
533
- "error",
534
- "crash",
535
- "fix",
536
- "confirmed",
537
- "verified"
538
- ]);
539
- const QUESTION_LABELS = new Set([
540
- "question",
541
- "help wanted",
542
- "support",
543
- "usage",
544
- "how-to",
545
- "help",
546
- "assistance"
547
- ]);
548
- const DOCS_LABELS = new Set([
549
- "documentation",
550
- "docs",
551
- "doc",
552
- "typo"
553
- ]);
554
- const labelRegexCache = /* @__PURE__ */ new WeakMap();
555
- function getLabelRegex(keywords) {
556
- let re = labelRegexCache.get(keywords);
557
- if (!re) {
558
- const escaped = Array.from(keywords, (k) => k.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
559
- re = new RegExp(`\\b(?:${escaped.join("|")})\\b`);
560
- labelRegexCache.set(keywords, re);
452
+ function parseGitHubRepoSlug(url) {
453
+ if (!url) return void 0;
454
+ const parsed = parseGitHubUrl(url);
455
+ return parsed ? `${parsed.owner}/${parsed.repo}` : void 0;
456
+ }
457
+ function parsePackageSpec(spec) {
458
+ if (spec.startsWith("@")) {
459
+ const slashIdx = spec.indexOf("/");
460
+ if (slashIdx !== -1) {
461
+ const atIdx = spec.indexOf("@", slashIdx + 1);
462
+ if (atIdx !== -1) return {
463
+ name: spec.slice(0, atIdx),
464
+ tag: spec.slice(atIdx + 1)
465
+ };
466
+ }
467
+ return { name: spec };
561
468
  }
562
- return re;
469
+ const atIdx = spec.indexOf("@");
470
+ if (atIdx !== -1) return {
471
+ name: spec.slice(0, atIdx),
472
+ tag: spec.slice(atIdx + 1)
473
+ };
474
+ return { name: spec };
563
475
  }
564
- function labelMatchesAny(label, keywords) {
565
- if (keywords.has(label)) return true;
566
- return getLabelRegex(keywords).test(label);
476
+ function normalizeRepoUrl(url) {
477
+ return url.replace(GIT_PLUS_PREFIX_RE, "").replace(STATIC_REGEX_3$2, "").replace(GIT_SUFFIX_RE, "").replace(GIT_PROTOCOL_PREFIX_RE, "https://").replace(GITHUB_SSH_URL_PREFIX_RE, "https://github.com").replace(STATIC_REGEX_7, "https://github.com/");
567
478
  }
568
- function classifyIssue(labels) {
569
- const lower = labels.map((l) => l.toLowerCase());
570
- if (lower.some((l) => labelMatchesAny(l, BUG_LABELS))) return "bug";
571
- if (lower.some((l) => labelMatchesAny(l, QUESTION_LABELS))) return "question";
572
- if (lower.some((l) => labelMatchesAny(l, DOCS_LABELS))) return "docs";
573
- if (lower.some((l) => labelMatchesAny(l, FEATURE_LABELS))) return "feature";
574
- return "other";
479
+ function extractBranchHint(url) {
480
+ const hash = url.indexOf("#");
481
+ if (hash === -1) return void 0;
482
+ const fragment = url.slice(hash + 1);
483
+ if (!fragment || fragment === "readme") return void 0;
484
+ return fragment;
575
485
  }
576
- function isNoiseIssue(issue) {
577
- if (issue.labels.map((l) => l.toLowerCase()).some((l) => labelMatchesAny(l, NOISE_LABELS))) return true;
578
- if (issue.title.startsWith("☂️") || issue.title.startsWith("[META]") || issue.title.startsWith("[Tracking]")) return true;
579
- return false;
486
+ function isGitHubRepoUrl(url) {
487
+ try {
488
+ const parsed = new URL(url);
489
+ return parsed.hostname === "github.com" || parsed.hostname === "www.github.com";
490
+ } catch {
491
+ return false;
492
+ }
580
493
  }
581
- function isNonTechnical(issue) {
582
- const body = (issue.body || "").trim();
583
- if (body.length < 200 && !hasCodeBlock(body) && issue.reactions > 50) return true;
584
- if (/\b(?:love|thank|awesome|great work)\b/i.test(issue.title) && !hasCodeBlock(body)) return true;
585
- return false;
494
+ function isLikelyCodeHostUrl(url) {
495
+ if (!url) return false;
496
+ try {
497
+ const parsed = new URL(url);
498
+ return [
499
+ "github.com",
500
+ "www.github.com",
501
+ "gitlab.com",
502
+ "www.gitlab.com"
503
+ ].includes(parsed.hostname);
504
+ } catch {
505
+ return false;
506
+ }
586
507
  }
587
- function freshnessScore(reactions, createdAt) {
588
- return reactions * (1 / (1 + (Date.now() - new Date(createdAt).getTime()) / (365.25 * 24 * 60 * 60 * 1e3) * .6));
589
- }
590
- function applyTypeQuotas(issues, limit) {
591
- const byType = /* @__PURE__ */ new Map();
592
- for (const issue of issues) mapInsert(byType, issue.type, () => []).push(issue);
593
- for (const group of byType.values()) group.sort((a, b) => b.score - a.score);
594
- const quotas = [
595
- ["bug", Math.ceil(limit * .4)],
596
- ["question", Math.ceil(limit * .3)],
597
- ["docs", Math.ceil(limit * .15)],
598
- ["feature", Math.ceil(limit * .1)],
599
- ["other", Math.ceil(limit * .05)]
600
- ];
601
- const selected = [];
602
- const used = /* @__PURE__ */ new Set();
603
- let remaining = limit;
604
- for (const [type, quota] of quotas) {
605
- const group = byType.get(type) || [];
606
- const take = Math.min(quota, group.length, remaining);
607
- for (let i = 0; i < take; i++) {
608
- selected.push(group[i]);
609
- used.add(group[i].number);
610
- remaining--;
611
- }
612
- }
613
- if (remaining > 0) {
614
- const unused = issues.filter((i) => !used.has(i.number) && i.type !== "feature").sort((a, b) => b.score - a.score);
615
- for (const issue of unused) {
616
- if (remaining <= 0) break;
617
- selected.push(issue);
618
- remaining--;
619
- }
620
- }
621
- return selected.sort((a, b) => b.score - a.score);
622
- }
623
- function bodyLimit(reactions) {
624
- if (reactions >= 10) return 2e3;
625
- if (reactions >= 5) return 1500;
626
- return 800;
627
- }
628
- function fetchIssuesByState(owner, repo, state, count, releasedAt, fromDate) {
629
- const fetchCount = Math.min(count * 3, 100);
630
- let datePart = "";
631
- if (fromDate) datePart = state === "closed" ? `+closed:>=${fromDate}` : `+created:>=${fromDate}`;
632
- else if (state === "closed") if (releasedAt) {
633
- datePart = `+closed:>=${isoDate(releasedAt)}`;
634
- const cap = new Date(releasedAt);
635
- cap.setMonth(cap.getMonth() + 6);
636
- if (cap < /* @__PURE__ */ new Date()) datePart += `+closed:<=${isoDate(cap.toISOString())}`;
637
- } else datePart = `+closed:>${oneYearAgo()}`;
638
- else if (releasedAt) {
639
- const date = new Date(releasedAt);
640
- date.setMonth(date.getMonth() + 6);
641
- datePart = `+created:<=${isoDate(date.toISOString())}`;
642
- }
643
- const { stdout: result } = spawnSync("gh", [
644
- "api",
645
- `search/issues?q=${`repo:${owner}/${repo}+is:issue+is:${state}${datePart}`}&sort=reactions&order=desc&per_page=${fetchCount}`,
646
- "-q",
647
- ".items[] | {number, title, state, labels: [.labels[]?.name], body, createdAt: .created_at, url: .html_url, reactions: .reactions[\"+1\"], comments: .comments, user: .user.login, userType: .user.type, authorAssociation: .author_association}"
648
- ], {
649
- encoding: "utf-8",
650
- maxBuffer: 10 * 1024 * 1024
651
- });
652
- if (!result) return [];
653
- return result.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line)).filter((issue) => !BOT_USERS.has(issue.user) && issue.userType !== "Bot").filter((issue) => !isNoiseIssue(issue)).filter((issue) => !isNonTechnical(issue)).map(({ user: _, userType: __, authorAssociation, ...issue }) => {
654
- const isMaintainer = [
655
- "OWNER",
656
- "MEMBER",
657
- "COLLABORATOR"
658
- ].includes(authorAssociation);
659
- const isRoadmap = /\broadmap\b/i.test(issue.title) || issue.labels.some((l) => /roadmap/i.test(l));
660
- return {
661
- ...issue,
662
- type: classifyIssue(issue.labels),
663
- topComments: [],
664
- score: freshnessScore(issue.reactions, issue.createdAt) * (isMaintainer && isRoadmap ? 5 : 1)
665
- };
666
- }).sort((a, b) => b.score - a.score).slice(0, count);
667
- }
668
- function oneYearAgo() {
669
- const d = /* @__PURE__ */ new Date();
670
- d.setFullYear(d.getFullYear() - 1);
671
- return isoDate(d.toISOString());
672
- }
673
- function enrichWithComments(owner, repo, issues, topN = 15) {
674
- const worth = issues.filter((i) => i.comments > 0 && (i.type === "bug" || i.type === "question" || i.reactions >= 3)).sort((a, b) => b.score - a.score).slice(0, topN);
675
- if (worth.length === 0) return;
676
- const query = `query($owner: String!, $repo: String!) { repository(owner: $owner, name: $repo) { ${worth.map((issue, i) => `i${i}: issue(number: ${issue.number}) { comments(first: 10) { nodes { body author { login } authorAssociation reactions { totalCount } } } }`).join(" ")} } }`;
677
- try {
678
- const { stdout: result } = spawnSync("gh", [
679
- "api",
680
- "graphql",
681
- "-f",
682
- `query=${query}`,
683
- "-f",
684
- `owner=${owner}`,
685
- "-f",
686
- `repo=${repo}`
687
- ], {
688
- encoding: "utf-8",
689
- maxBuffer: 10 * 1024 * 1024
690
- });
691
- if (!result) return;
692
- const repo_ = JSON.parse(result)?.data?.repository;
693
- if (!repo_) return;
694
- for (let i = 0; i < worth.length; i++) {
695
- const nodes = repo_[`i${i}`]?.comments?.nodes;
696
- if (!Array.isArray(nodes)) continue;
697
- const issue = worth[i];
698
- const comments = nodes.filter((c) => c.author && !BOT_USERS.has(c.author.login)).filter((c) => !COMMENT_NOISE_RE.test((c.body || "").trim())).map((c) => {
699
- const isMaintainer = [
700
- "OWNER",
701
- "MEMBER",
702
- "COLLABORATOR"
703
- ].includes(c.authorAssociation);
704
- const body = c.body || "";
705
- const reactions = c.reactions?.totalCount || 0;
706
- const _score = (isMaintainer ? 3 : 1) * (hasCodeBlock(body) ? 2 : 1) * (1 + reactions);
707
- return {
708
- body,
709
- author: c.author.login,
710
- reactions,
711
- isMaintainer,
712
- _score
713
- };
714
- }).sort((a, b) => b._score - a._score);
715
- issue.topComments = comments.slice(0, 3).map(({ _score: _, ...c }) => c);
716
- if (issue.state === "closed") issue.resolvedIn = detectResolvedVersion(comments);
717
- }
718
- } catch {}
719
- }
720
- function detectResolvedVersion(comments) {
721
- const maintainerComments = comments.filter((c) => c.isMaintainer);
722
- for (const c of maintainerComments.reverse()) {
723
- const match = c.body.match(/(?:fixed|landed|released|available|shipped|resolved|included)\s+in\s+v?(\d+\.\d+(?:\.\d+)?)/i);
724
- if (match) return match[1];
725
- if (c.body.length < 100) {
726
- const vMatch = c.body.match(/\bv?(\d+\.\d+\.\d+)\b/);
727
- if (vMatch) return vMatch[1];
728
- }
729
- }
730
- }
731
- async function fetchGitHubIssues(owner, repo, limit = 30, releasedAt, fromDate) {
732
- if (!isGhAvailable()) return [];
733
- const openCount = Math.ceil(limit * .75);
734
- const closedCount = limit - openCount;
735
- try {
736
- const open = fetchIssuesByState(owner, repo, "open", Math.min(openCount * 2, 100), releasedAt, fromDate);
737
- const closed = fetchIssuesByState(owner, repo, "closed", Math.min(closedCount * 2, 50), releasedAt, fromDate);
738
- const selected = applyTypeQuotas([...open, ...closed], limit);
739
- enrichWithComments(owner, repo, selected);
740
- return selected;
741
- } catch {
742
- return [];
743
- }
744
- }
745
- function formatIssueAsMarkdown(issue) {
746
- const limit = bodyLimit(issue.reactions);
747
- const fmFields = {
748
- number: issue.number,
749
- title: issue.title,
750
- type: issue.type,
751
- state: issue.state,
752
- created: isoDate(issue.createdAt),
753
- url: issue.url,
754
- reactions: issue.reactions,
755
- comments: issue.comments
756
- };
757
- if (issue.resolvedIn) fmFields.resolvedIn = issue.resolvedIn;
758
- if (issue.labels.length > 0) fmFields.labels = `[${issue.labels.join(", ")}]`;
759
- const lines = [
760
- buildFrontmatter(fmFields),
761
- "",
762
- `# ${issue.title}`
763
- ];
764
- if (issue.body) {
765
- const body = truncateBody(issue.body, limit);
766
- lines.push("", body);
767
- }
768
- if (issue.topComments.length > 0) {
769
- lines.push("", "---", "", "## Top Comments");
770
- for (const c of issue.topComments) {
771
- const reactions = c.reactions > 0 ? ` (+${c.reactions})` : "";
772
- const maintainer = c.isMaintainer ? " [maintainer]" : "";
773
- const commentBody = truncateBody(c.body, 600);
774
- lines.push("", `**@${c.author}**${maintainer}${reactions}:`, "", commentBody);
775
- }
776
- }
777
- return lines.join("\n");
778
- }
779
- function generateIssueIndex(issues) {
780
- const byType = /* @__PURE__ */ new Map();
781
- for (const issue of issues) mapInsert(byType, issue.type, () => []).push(issue);
782
- const typeLabels = {
783
- bug: "Bugs & Regressions",
784
- question: "Questions & Usage Help",
785
- docs: "Documentation",
786
- feature: "Feature Requests",
787
- other: "Other"
788
- };
789
- const typeOrder = [
790
- "bug",
791
- "question",
792
- "docs",
793
- "other",
794
- "feature"
795
- ];
796
- const sections = [
797
- [
798
- "---",
799
- `total: ${issues.length}`,
800
- `open: ${issues.filter((i) => i.state === "open").length}`,
801
- `closed: ${issues.filter((i) => i.state !== "open").length}`,
802
- "---"
803
- ].join("\n"),
804
- "",
805
- "# Issues Index",
806
- ""
807
- ];
808
- for (const type of typeOrder) {
809
- const group = byType.get(type);
810
- if (!group?.length) continue;
811
- sections.push(`## ${typeLabels[type]} (${group.length})`, "");
812
- for (const issue of group) {
813
- const reactions = issue.reactions > 0 ? ` (+${issue.reactions})` : "";
814
- const state = issue.state === "open" ? "" : " [closed]";
815
- const resolved = issue.resolvedIn ? ` [fixed in ${issue.resolvedIn}]` : "";
816
- const date = isoDate(issue.createdAt);
817
- sections.push(`- [#${issue.number}](./issue-${issue.number}.md): ${issue.title}${reactions}${state}${resolved} (${date})`);
818
- }
819
- sections.push("");
820
- }
821
- return sections.join("\n");
822
- }
823
- async function fetchLlmsUrl(docsUrl) {
824
- const llmsUrl = `${new URL(docsUrl).origin}/llms.txt`;
825
- if (await verifyUrl(llmsUrl)) return llmsUrl;
826
- return null;
827
- }
828
- async function fetchLlmsTxt(url) {
829
- const content = await fetchText(url);
830
- if (!content || content.length < 50) return null;
831
- return {
832
- raw: content,
833
- links: parseMarkdownLinks(content)
834
- };
835
- }
836
- function parseMarkdownLinks(content) {
837
- return extractLinks(content).filter((l) => l.url.endsWith(".md"));
508
+ const USELESS_HOSTS = new Set([
509
+ "twitter.com",
510
+ "x.com",
511
+ "facebook.com",
512
+ "linkedin.com",
513
+ "youtube.com",
514
+ "instagram.com",
515
+ "npmjs.com",
516
+ "www.npmjs.com",
517
+ "yarnpkg.com"
518
+ ]);
519
+ function isUselessDocsUrl(url) {
520
+ try {
521
+ const { hostname } = new URL(url);
522
+ return USELESS_HOSTS.has(hostname);
523
+ } catch {
524
+ return false;
525
+ }
838
526
  }
839
527
  function isSafeUrl(url) {
840
528
  try {
@@ -842,66 +530,21 @@ function isSafeUrl(url) {
842
530
  if (parsed.protocol !== "https:") return false;
843
531
  const host = parsed.hostname;
844
532
  if (host === "localhost" || host === "0.0.0.0" || host === "[::1]") return false;
845
- if (/^(?:127\.|10\.|172\.(?:1[6-9]|2\d|3[01])\.|192\.168\.|169\.254\.)/.test(host)) return false;
846
- if (/^\[(?:f[cd]|fe[89ab]|::ffff:)/i.test(host)) return false;
533
+ if (STATIC_REGEX_8.test(host)) return false;
534
+ if (STATIC_REGEX_9.test(host)) return false;
847
535
  return true;
848
536
  } catch {
849
537
  return false;
850
538
  }
851
539
  }
852
- async function downloadLlmsDocs(llmsContent, baseUrl, onProgress) {
853
- const limit = pLimit(5);
854
- let completed = 0;
855
- return (await Promise.all(llmsContent.links.map((link) => limit(async () => {
856
- const url = link.url.startsWith("http") ? link.url : `${baseUrl.replace(/\/$/, "")}${link.url.startsWith("/") ? "" : "/"}${link.url}`;
857
- if (!isSafeUrl(url)) return null;
858
- const content = await fetchText(url);
859
- onProgress?.(link.url, ++completed, llmsContent.links.length);
860
- if (content && content.length > 100) return {
861
- url: link.url.startsWith("http") ? new URL(link.url).pathname : link.url,
862
- title: link.title,
863
- content
864
- };
865
- return null;
866
- })))).filter((d) => d !== null);
867
- }
868
- function normalizeLlmsLinks(content, baseUrl) {
869
- let normalized = content;
870
- if (baseUrl) {
871
- const escaped = baseUrl.replace(/\/$/, "").replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
872
- normalized = normalized.replace(new RegExp(`\\]\\(${escaped}(/[^)]+\\.md)\\)`, "g"), "](./docs$1)");
873
- }
874
- normalized = normalized.replace(/\]\(\/([^)]+\.md)\)/g, "](./docs/$1)");
875
- return normalized;
876
- }
877
- function extractSections(content, patterns) {
878
- const sections = [];
879
- const parts = content.split(/\n---\n/);
880
- for (const part of parts) {
881
- const urlMatch = part.match(/^url: *(\S.*)$/m);
882
- if (!urlMatch) continue;
883
- const url = urlMatch[1];
884
- if (patterns.some((p) => url.includes(p))) {
885
- const contentStart = part.indexOf("\n", part.indexOf("url:"));
886
- if (contentStart > -1) sections.push(part.slice(contentStart + 1));
887
- }
888
- }
889
- if (sections.length === 0) return null;
890
- return sections.join("\n\n---\n\n");
891
- }
892
- const MIN_GIT_DOCS = 5;
893
- const isShallowGitDocs = (n) => n > 0 && n < 5;
894
- async function listFilesAtRef(owner, repo, ref) {
895
- if (!isKnownPrivateRepo(owner, repo)) {
896
- const data = await $fetch(`https://ungh.cc/repos/${owner}/${repo}/files/${ref}`).catch(() => null);
897
- if (data?.files?.length) return data.files.map((f) => f.path);
898
- }
899
- const tree = await ghApi(`repos/${owner}/${repo}/git/trees/${ref}?recursive=1`);
900
- if (tree?.tree?.length) {
901
- markRepoPrivate(owner, repo);
902
- return tree.tree.map((f) => f.path);
903
- }
904
- return [];
540
+ async function listFilesAtRef(owner, repo, ref) {
541
+ return await fetchUnghOrApi(owner, repo, async () => {
542
+ const data = await $fetch(`https://ungh.cc/repos/${owner}/${repo}/files/${ref}`);
543
+ return data.files?.length ? data.files.map((f) => f.path) : null;
544
+ }, async () => {
545
+ const tree = await ghApi(`repos/${owner}/${repo}/git/trees/${ref}?recursive=1`);
546
+ return tree?.tree?.length ? tree.tree.map((f) => f.path) : null;
547
+ }) ?? [];
905
548
  }
906
549
  async function findGitTag(owner, repo, version, packageName, branchHint) {
907
550
  const candidates = [`v${version}`, version];
@@ -935,26 +578,25 @@ async function findGitTag(owner, repo, version, packageName, branchHint) {
935
578
  return null;
936
579
  }
937
580
  async function fetchUnghReleases(owner, repo) {
938
- if (!isKnownPrivateRepo(owner, repo)) {
939
- const data = await $fetch(`https://ungh.cc/repos/${owner}/${repo}/releases`).catch(() => null);
940
- if (data?.releases?.length) return data.releases;
941
- }
942
- const raw = await ghApiPaginated(`repos/${owner}/${repo}/releases`);
943
- if (raw.length > 0) {
944
- markRepoPrivate(owner, repo);
945
- return raw.map((r) => ({
581
+ return await fetchUnghOrApi(owner, repo, async () => {
582
+ const data = await $fetch(`https://ungh.cc/repos/${owner}/${repo}/releases`);
583
+ return data.releases?.length ? data.releases : null;
584
+ }, async () => {
585
+ const raw = await ghApiPaginated(`repos/${owner}/${repo}/releases`);
586
+ return raw.length > 0 ? raw.map((r) => ({
946
587
  tag: r.tag_name,
947
588
  publishedAt: r.published_at
948
- }));
949
- }
950
- return [];
589
+ })) : null;
590
+ }) ?? [];
951
591
  }
952
592
  async function findLatestReleaseTag(owner, repo, packageName) {
953
593
  const prefix = `${packageName}@`;
954
594
  return (await fetchUnghReleases(owner, repo)).find((r) => r.tag.startsWith(prefix))?.tag ?? null;
955
595
  }
596
+ const STATIC_REGEX_1$6 = /\.(?:md|mdx)$/;
597
+ const isShallowGitDocs = (n) => n > 0 && n < 5;
956
598
  function filterDocFiles(files, pathPrefix) {
957
- return files.filter((f) => f.startsWith(pathPrefix) && /\.(?:md|mdx)$/.test(f));
599
+ return files.filter((f) => f.startsWith(pathPrefix) && STATIC_REGEX_1$6.test(f));
958
600
  }
959
601
  const FRAMEWORK_NAMES = new Set([
960
602
  "vue",
@@ -968,7 +610,7 @@ const FRAMEWORK_NAMES = new Set([
968
610
  ]);
969
611
  function filterFrameworkDocs(files, packageName) {
970
612
  if (!packageName) return files;
971
- const shortName = packageName.replace(/^@.*\//, "");
613
+ const shortName = packageName.replace(NPM_SCOPE_WITH_SLASH_RE, "");
972
614
  const targetFramework = [...FRAMEWORK_NAMES].find((fw) => shortName.includes(fw));
973
615
  if (!targetFramework) return files;
974
616
  const otherFrameworks = [...FRAMEWORK_NAMES].filter((fw) => fw !== targetFramework);
@@ -1025,7 +667,7 @@ function scoreDocDir(dir, fileCount) {
1025
667
  return fileCount * (hasDocDirBonus(dir) ? 1.5 : 1) / depth;
1026
668
  }
1027
669
  function discoverDocFiles(allFiles, packageName) {
1028
- const mdFiles = allFiles.filter((f) => /\.(?:md|mdx)$/.test(f)).filter((f) => !NOISE_PATTERNS.some((p) => p.test(f))).filter((f) => f.includes("/"));
670
+ const mdFiles = allFiles.filter((f) => STATIC_REGEX_1$6.test(f)).filter((f) => !NOISE_PATTERNS.some((p) => p.test(f))).filter((f) => f.includes("/"));
1029
671
  if (packageName?.includes("/")) {
1030
672
  const subPkgPrefix = `packages/${packageName.split("/").pop().toLowerCase()}/`;
1031
673
  const subPkgFiles = mdFiles.filter((f) => f.startsWith(subPkgPrefix));
@@ -1103,51 +745,418 @@ async function fetchGitDocs(owner, repo, version, packageName, repoUrl) {
1103
745
  allFiles = tag.files;
1104
746
  }
1105
747
  }
1106
- docs = filterFrameworkDocs(docs, packageName);
1107
- if (docs.length === 0) return null;
748
+ docs = filterFrameworkDocs(docs, packageName);
749
+ if (docs.length === 0) return null;
750
+ return {
751
+ baseUrl: `https://raw.githubusercontent.com/${owner}/${repo}/${tag.ref}`,
752
+ ref: tag.ref,
753
+ files: docs,
754
+ docsPrefix,
755
+ allFiles,
756
+ fallback: tag.fallback
757
+ };
758
+ }
759
+ function normalizePath(p) {
760
+ return p.replace(LEADING_SLASH_RE, "").replace(STATIC_REGEX_1$6, "");
761
+ }
762
+ function validateGitDocsWithLlms(llmsLinks, repoFiles) {
763
+ if (llmsLinks.length === 0) return {
764
+ isValid: true,
765
+ matchRatio: 1
766
+ };
767
+ const sample = llmsLinks.slice(0, 10);
768
+ const normalizedLinks = sample.map((link) => {
769
+ let path = link.url;
770
+ if (path.startsWith("http")) try {
771
+ path = new URL(path).pathname;
772
+ } catch {}
773
+ return normalizePath(path);
774
+ });
775
+ const repoNormalized = new Set(repoFiles.map(normalizePath));
776
+ let matches = 0;
777
+ for (const linkPath of normalizedLinks) for (const repoPath of repoNormalized) if (repoPath === linkPath || repoPath.endsWith(`/${linkPath}`)) {
778
+ matches++;
779
+ break;
780
+ }
781
+ const matchRatio = matches / sample.length;
782
+ return {
783
+ isValid: matchRatio >= .3,
784
+ matchRatio
785
+ };
786
+ }
787
+ const STATIC_REGEX_1$5 = /\b(?:love|thank|awesome|great work)\b/i;
788
+ const STATIC_REGEX_2$1 = /\broadmap\b/i;
789
+ const STATIC_REGEX_3$1 = /roadmap/i;
790
+ const STATIC_REGEX_4 = /(?:fixed|landed|released|available|shipped|resolved|included)\s+in\s+v?(\d+\.\d+(?:\.\d+)?)/i;
791
+ const STATIC_REGEX_5 = /\bv?(\d+\.\d+\.\d+)\b/;
792
+ let _ghAvailable;
793
+ function isGhAvailable() {
794
+ if (_ghAvailable !== void 0) return _ghAvailable;
795
+ const { status } = spawnSync("gh", ["auth", "status"], { stdio: "ignore" });
796
+ return _ghAvailable = status === 0;
797
+ }
798
+ const NOISE_LABELS = new Set([
799
+ "duplicate",
800
+ "stale",
801
+ "invalid",
802
+ "wontfix",
803
+ "won't fix",
804
+ "spam",
805
+ "off-topic",
806
+ "needs triage",
807
+ "triage"
808
+ ]);
809
+ const FEATURE_LABELS = new Set([
810
+ "enhancement",
811
+ "feature",
812
+ "feature request",
813
+ "feature-request",
814
+ "proposal",
815
+ "rfc",
816
+ "idea",
817
+ "suggestion"
818
+ ]);
819
+ const BUG_LABELS = new Set([
820
+ "bug",
821
+ "defect",
822
+ "regression",
823
+ "error",
824
+ "crash",
825
+ "fix",
826
+ "confirmed",
827
+ "verified"
828
+ ]);
829
+ const QUESTION_LABELS = new Set([
830
+ "question",
831
+ "help wanted",
832
+ "support",
833
+ "usage",
834
+ "how-to",
835
+ "help",
836
+ "assistance"
837
+ ]);
838
+ const DOCS_LABELS = new Set([
839
+ "documentation",
840
+ "docs",
841
+ "doc",
842
+ "typo"
843
+ ]);
844
+ const labelRegexCache = /* @__PURE__ */ new WeakMap();
845
+ function getLabelRegex(keywords) {
846
+ let re = labelRegexCache.get(keywords);
847
+ if (!re) {
848
+ const escaped = Array.from(keywords, (k) => k.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
849
+ re = new RegExp(`\\b(?:${escaped.join("|")})\\b`);
850
+ labelRegexCache.set(keywords, re);
851
+ }
852
+ return re;
853
+ }
854
+ function labelMatchesAny(label, keywords) {
855
+ if (keywords.has(label)) return true;
856
+ return getLabelRegex(keywords).test(label);
857
+ }
858
+ function classifyIssue(labels) {
859
+ const lower = labels.map((l) => l.toLowerCase());
860
+ if (lower.some((l) => labelMatchesAny(l, BUG_LABELS))) return "bug";
861
+ if (lower.some((l) => labelMatchesAny(l, QUESTION_LABELS))) return "question";
862
+ if (lower.some((l) => labelMatchesAny(l, DOCS_LABELS))) return "docs";
863
+ if (lower.some((l) => labelMatchesAny(l, FEATURE_LABELS))) return "feature";
864
+ return "other";
865
+ }
866
+ function isNoiseIssue(issue) {
867
+ if (issue.labels.map((l) => l.toLowerCase()).some((l) => labelMatchesAny(l, NOISE_LABELS))) return true;
868
+ if (issue.title.startsWith("☂️") || issue.title.startsWith("[META]") || issue.title.startsWith("[Tracking]")) return true;
869
+ return false;
870
+ }
871
+ function isNonTechnical(issue) {
872
+ const body = (issue.body || "").trim();
873
+ if (body.length < 200 && !hasCodeBlock(body) && issue.reactions > 50) return true;
874
+ if (STATIC_REGEX_1$5.test(issue.title) && !hasCodeBlock(body)) return true;
875
+ return false;
876
+ }
877
+ function freshnessScore(reactions, createdAt) {
878
+ return reactions * (1 / (1 + (Date.now() - new Date(createdAt).getTime()) / (365.25 * 24 * 60 * 60 * 1e3) * .6));
879
+ }
880
+ function applyTypeQuotas(issues, limit) {
881
+ const byType = /* @__PURE__ */ new Map();
882
+ for (const issue of issues) mapInsert(byType, issue.type, () => []).push(issue);
883
+ for (const group of byType.values()) group.sort((a, b) => b.score - a.score);
884
+ const quotas = [
885
+ ["bug", Math.ceil(limit * .4)],
886
+ ["question", Math.ceil(limit * .3)],
887
+ ["docs", Math.ceil(limit * .15)],
888
+ ["feature", Math.ceil(limit * .1)],
889
+ ["other", Math.ceil(limit * .05)]
890
+ ];
891
+ const selected = [];
892
+ const used = /* @__PURE__ */ new Set();
893
+ let remaining = limit;
894
+ for (const [type, quota] of quotas) {
895
+ const group = byType.get(type) || [];
896
+ const take = Math.min(quota, group.length, remaining);
897
+ for (let i = 0; i < take; i++) {
898
+ selected.push(group[i]);
899
+ used.add(group[i].number);
900
+ remaining--;
901
+ }
902
+ }
903
+ if (remaining > 0) {
904
+ const unused = issues.filter((i) => !used.has(i.number) && i.type !== "feature").sort((a, b) => b.score - a.score);
905
+ for (const issue of unused) {
906
+ if (remaining <= 0) break;
907
+ selected.push(issue);
908
+ remaining--;
909
+ }
910
+ }
911
+ return selected.sort((a, b) => b.score - a.score);
912
+ }
913
+ function bodyLimit(reactions) {
914
+ if (reactions >= 10) return 2e3;
915
+ if (reactions >= 5) return 1500;
916
+ return 800;
917
+ }
918
+ function fetchIssuesByState(owner, repo, state, count, releasedAt, fromDate) {
919
+ const fetchCount = Math.min(count * 3, 100);
920
+ let datePart = "";
921
+ if (fromDate) datePart = state === "closed" ? `+closed:>=${fromDate}` : `+created:>=${fromDate}`;
922
+ else if (state === "closed") if (releasedAt) {
923
+ datePart = `+closed:>=${isoDate(releasedAt)}`;
924
+ const cap = new Date(releasedAt);
925
+ cap.setMonth(cap.getMonth() + 6);
926
+ if (cap < /* @__PURE__ */ new Date()) datePart += `+closed:<=${isoDate(cap.toISOString())}`;
927
+ } else datePart = `+closed:>${oneYearAgo()}`;
928
+ else if (releasedAt) {
929
+ const date = new Date(releasedAt);
930
+ date.setMonth(date.getMonth() + 6);
931
+ datePart = `+created:<=${isoDate(date.toISOString())}`;
932
+ }
933
+ const { stdout: result } = spawnSync("gh", [
934
+ "api",
935
+ `search/issues?q=${`repo:${owner}/${repo}+is:issue+is:${state}${datePart}`}&sort=reactions&order=desc&per_page=${fetchCount}`,
936
+ "-q",
937
+ ".items[] | {number, title, state, labels: [.labels[]?.name], body, createdAt: .created_at, url: .html_url, reactions: .reactions[\"+1\"], comments: .comments, user: .user.login, userType: .user.type, authorAssociation: .author_association}"
938
+ ], {
939
+ encoding: "utf-8",
940
+ maxBuffer: 10 * 1024 * 1024
941
+ });
942
+ if (!result) return [];
943
+ return result.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line)).filter((issue) => !BOT_USERS.has(issue.user) && issue.userType !== "Bot").filter((issue) => !isNoiseIssue(issue)).filter((issue) => !isNonTechnical(issue)).map(({ user: _, userType: __, authorAssociation, ...issue }) => {
944
+ const isMaintainer = [
945
+ "OWNER",
946
+ "MEMBER",
947
+ "COLLABORATOR"
948
+ ].includes(authorAssociation);
949
+ const isRoadmap = STATIC_REGEX_2$1.test(issue.title) || issue.labels.some((l) => STATIC_REGEX_3$1.test(l));
950
+ return {
951
+ ...issue,
952
+ type: classifyIssue(issue.labels),
953
+ topComments: [],
954
+ score: freshnessScore(issue.reactions, issue.createdAt) * (isMaintainer && isRoadmap ? 5 : 1)
955
+ };
956
+ }).sort((a, b) => b.score - a.score).slice(0, count);
957
+ }
958
+ function oneYearAgo() {
959
+ const d = /* @__PURE__ */ new Date();
960
+ d.setFullYear(d.getFullYear() - 1);
961
+ return isoDate(d.toISOString());
962
+ }
963
+ function enrichWithComments(owner, repo, issues, topN = 15) {
964
+ const worth = issues.filter((i) => i.comments > 0 && (i.type === "bug" || i.type === "question" || i.reactions >= 3)).sort((a, b) => b.score - a.score).slice(0, topN);
965
+ if (worth.length === 0) return;
966
+ const query = `query($owner: String!, $repo: String!) { repository(owner: $owner, name: $repo) { ${worth.map((issue, i) => `i${i}: issue(number: ${issue.number}) { comments(first: 10) { nodes { body author { login } authorAssociation reactions { totalCount } } } }`).join(" ")} } }`;
967
+ try {
968
+ const { stdout: result } = spawnSync("gh", [
969
+ "api",
970
+ "graphql",
971
+ "-f",
972
+ `query=${query}`,
973
+ "-f",
974
+ `owner=${owner}`,
975
+ "-f",
976
+ `repo=${repo}`
977
+ ], {
978
+ encoding: "utf-8",
979
+ maxBuffer: 10 * 1024 * 1024
980
+ });
981
+ if (!result) return;
982
+ const repo_ = JSON.parse(result)?.data?.repository;
983
+ if (!repo_) return;
984
+ for (let i = 0; i < worth.length; i++) {
985
+ const nodes = repo_[`i${i}`]?.comments?.nodes;
986
+ if (!Array.isArray(nodes)) continue;
987
+ const issue = worth[i];
988
+ const comments = nodes.filter((c) => c.author && !BOT_USERS.has(c.author.login)).filter((c) => !COMMENT_NOISE_RE.test((c.body || "").trim())).map((c) => {
989
+ const isMaintainer = [
990
+ "OWNER",
991
+ "MEMBER",
992
+ "COLLABORATOR"
993
+ ].includes(c.authorAssociation);
994
+ const body = c.body || "";
995
+ const reactions = c.reactions?.totalCount || 0;
996
+ const _score = (isMaintainer ? 3 : 1) * (hasCodeBlock(body) ? 2 : 1) * (1 + reactions);
997
+ return {
998
+ body,
999
+ author: c.author.login,
1000
+ reactions,
1001
+ isMaintainer,
1002
+ _score
1003
+ };
1004
+ }).sort((a, b) => b._score - a._score);
1005
+ issue.topComments = comments.slice(0, 3).map(({ _score: _, ...c }) => c);
1006
+ if (issue.state === "closed") issue.resolvedIn = detectResolvedVersion(comments);
1007
+ }
1008
+ } catch {}
1009
+ }
1010
+ function detectResolvedVersion(comments) {
1011
+ const maintainerComments = comments.filter((c) => c.isMaintainer);
1012
+ for (const c of maintainerComments.reverse()) {
1013
+ const match = c.body.match(STATIC_REGEX_4);
1014
+ if (match) return match[1];
1015
+ if (c.body.length < 100) {
1016
+ const vMatch = c.body.match(STATIC_REGEX_5);
1017
+ if (vMatch) return vMatch[1];
1018
+ }
1019
+ }
1020
+ }
1021
+ async function fetchGitHubIssues(owner, repo, limit = 30, releasedAt, fromDate) {
1022
+ if (!isGhAvailable()) return [];
1023
+ const openCount = Math.ceil(limit * .75);
1024
+ const closedCount = limit - openCount;
1025
+ try {
1026
+ const open = fetchIssuesByState(owner, repo, "open", Math.min(openCount * 2, 100), releasedAt, fromDate);
1027
+ const closed = fetchIssuesByState(owner, repo, "closed", Math.min(closedCount * 2, 50), releasedAt, fromDate);
1028
+ const selected = applyTypeQuotas([...open, ...closed], limit);
1029
+ enrichWithComments(owner, repo, selected);
1030
+ return selected;
1031
+ } catch {
1032
+ return [];
1033
+ }
1034
+ }
1035
+ function formatIssueAsMarkdown(issue) {
1036
+ const limit = bodyLimit(issue.reactions);
1037
+ const fmFields = {
1038
+ number: issue.number,
1039
+ title: issue.title,
1040
+ type: issue.type,
1041
+ state: issue.state,
1042
+ created: isoDate(issue.createdAt),
1043
+ url: issue.url,
1044
+ reactions: issue.reactions,
1045
+ comments: issue.comments
1046
+ };
1047
+ if (issue.resolvedIn) fmFields.resolvedIn = issue.resolvedIn;
1048
+ if (issue.labels.length > 0) fmFields.labels = `[${issue.labels.join(", ")}]`;
1049
+ const lines = [
1050
+ buildFrontmatter(fmFields),
1051
+ "",
1052
+ `# ${issue.title}`
1053
+ ];
1054
+ if (issue.body) {
1055
+ const body = truncateBody(issue.body, limit);
1056
+ lines.push("", body);
1057
+ }
1058
+ if (issue.topComments.length > 0) {
1059
+ lines.push("", "---", "", "## Top Comments");
1060
+ for (const c of issue.topComments) {
1061
+ const reactions = c.reactions > 0 ? ` (+${c.reactions})` : "";
1062
+ const maintainer = c.isMaintainer ? " [maintainer]" : "";
1063
+ const commentBody = truncateBody(c.body, 600);
1064
+ lines.push("", `**@${c.author}**${maintainer}${reactions}:`, "", commentBody);
1065
+ }
1066
+ }
1067
+ return lines.join("\n");
1068
+ }
1069
+ function generateIssueIndex(issues) {
1070
+ const byType = /* @__PURE__ */ new Map();
1071
+ for (const issue of issues) mapInsert(byType, issue.type, () => []).push(issue);
1072
+ const typeLabels = {
1073
+ bug: "Bugs & Regressions",
1074
+ question: "Questions & Usage Help",
1075
+ docs: "Documentation",
1076
+ feature: "Feature Requests",
1077
+ other: "Other"
1078
+ };
1079
+ const typeOrder = [
1080
+ "bug",
1081
+ "question",
1082
+ "docs",
1083
+ "other",
1084
+ "feature"
1085
+ ];
1086
+ const sections = [
1087
+ [
1088
+ "---",
1089
+ `total: ${issues.length}`,
1090
+ `open: ${issues.filter((i) => i.state === "open").length}`,
1091
+ `closed: ${issues.filter((i) => i.state !== "open").length}`,
1092
+ "---"
1093
+ ].join("\n"),
1094
+ "",
1095
+ "# Issues Index",
1096
+ ""
1097
+ ];
1098
+ for (const type of typeOrder) {
1099
+ const group = byType.get(type);
1100
+ if (!group?.length) continue;
1101
+ sections.push(`## ${typeLabels[type]} (${group.length})`, "");
1102
+ for (const issue of group) {
1103
+ const reactions = issue.reactions > 0 ? ` (+${issue.reactions})` : "";
1104
+ const state = issue.state === "open" ? "" : " [closed]";
1105
+ const resolved = issue.resolvedIn ? ` [fixed in ${issue.resolvedIn}]` : "";
1106
+ const date = isoDate(issue.createdAt);
1107
+ sections.push(`- [#${issue.number}](./issue-${issue.number}.md): ${issue.title}${reactions}${state}${resolved} (${date})`);
1108
+ }
1109
+ sections.push("");
1110
+ }
1111
+ return sections.join("\n");
1112
+ }
1113
+ async function fetchLlmsUrl(docsUrl) {
1114
+ const llmsUrl = `${new URL(docsUrl).origin}/llms.txt`;
1115
+ if (await verifyUrl(llmsUrl)) return llmsUrl;
1116
+ return null;
1117
+ }
1118
+ async function fetchLlmsTxt(url) {
1119
+ const content = await fetchText(url);
1120
+ if (!content || content.length < 50) return null;
1108
1121
  return {
1109
- baseUrl: `https://raw.githubusercontent.com/${owner}/${repo}/${tag.ref}`,
1110
- ref: tag.ref,
1111
- files: docs,
1112
- docsPrefix,
1113
- allFiles,
1114
- fallback: tag.fallback
1122
+ raw: content,
1123
+ links: parseMarkdownLinks(content)
1115
1124
  };
1116
1125
  }
1117
- function normalizePath(p) {
1118
- return p.replace(/^\//, "").replace(/\.(?:md|mdx)$/, "");
1126
+ function parseMarkdownLinks(content) {
1127
+ return extractLinks(content).filter((l) => l.url.endsWith(".md"));
1119
1128
  }
1120
- function validateGitDocsWithLlms(llmsLinks, repoFiles) {
1121
- if (llmsLinks.length === 0) return {
1122
- isValid: true,
1123
- matchRatio: 1
1124
- };
1125
- const sample = llmsLinks.slice(0, 10);
1126
- const normalizedLinks = sample.map((link) => {
1127
- let path = link.url;
1128
- if (path.startsWith("http")) try {
1129
- path = new URL(path).pathname;
1130
- } catch {}
1131
- return normalizePath(path);
1132
- });
1133
- const repoNormalized = new Set(repoFiles.map(normalizePath));
1134
- let matches = 0;
1135
- for (const linkPath of normalizedLinks) for (const repoPath of repoNormalized) if (repoPath === linkPath || repoPath.endsWith(`/${linkPath}`)) {
1136
- matches++;
1137
- break;
1129
+ async function downloadLlmsDocs(llmsContent, baseUrl, onProgress) {
1130
+ const limit = pLimit(5);
1131
+ let completed = 0;
1132
+ return (await Promise.all(llmsContent.links.map((link) => limit(async () => {
1133
+ const url = link.url.startsWith("http") ? link.url : `${baseUrl.replace(TRAILING_SLASH_RE, "")}${link.url.startsWith("/") ? "" : "/"}${link.url}`;
1134
+ if (!isSafeUrl(url)) return null;
1135
+ const content = await fetchText(url);
1136
+ onProgress?.(link.url, ++completed, llmsContent.links.length);
1137
+ if (content && content.length > 100) return {
1138
+ url: link.url.startsWith("http") ? new URL(link.url).pathname : link.url,
1139
+ title: link.title,
1140
+ content
1141
+ };
1142
+ return null;
1143
+ })))).filter((d) => d !== null);
1144
+ }
1145
+ function normalizeLlmsLinks(content, baseUrl) {
1146
+ let normalized = content;
1147
+ if (baseUrl) {
1148
+ const escaped = baseUrl.replace(TRAILING_SLASH_RE, "").replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1149
+ normalized = normalized.replace(new RegExp(`\\]\\(${escaped}(/[^)]+\\.md)\\)`, "g"), "](./docs$1)");
1138
1150
  }
1139
- const matchRatio = matches / sample.length;
1140
- return {
1141
- isValid: matchRatio >= .3,
1142
- matchRatio
1143
- };
1151
+ normalized = normalized.replace(/\]\(\/([^)]+\.md)\)/g, "](./docs/$1)");
1152
+ return normalized;
1144
1153
  }
1145
1154
  async function verifyNpmRepo(owner, repo, packageName) {
1146
1155
  const base = `https://raw.githubusercontent.com/${owner}/${repo}/HEAD`;
1147
1156
  const paths = [
1148
1157
  "package.json",
1149
- `packages/${packageName.replace(/^@.*\//, "")}/package.json`,
1150
- `packages/${packageName.replace(/^@/, "").replace("/", "-")}/package.json`
1158
+ `packages/${packageName.replace(NPM_SCOPE_WITH_SLASH_RE, "")}/package.json`,
1159
+ `packages/${packageName.replace(NPM_SCOPE_PREFIX_RE, "").replace("/", "-")}/package.json`
1151
1160
  ];
1152
1161
  for (const path of paths) {
1153
1162
  const text = await fetchGitHubRaw(`${base}/${path}`);
@@ -1159,15 +1168,15 @@ async function verifyNpmRepo(owner, repo, packageName) {
1159
1168
  return false;
1160
1169
  }
1161
1170
  async function searchGitHubRepo(packageName) {
1162
- const shortName = packageName.replace(/^@.*\//, "");
1163
- for (const candidate of [packageName.replace(/^@/, "").replace("/", "/"), shortName]) {
1171
+ const shortName = packageName.replace(NPM_SCOPE_WITH_SLASH_RE, "");
1172
+ for (const candidate of [packageName.replace(NPM_SCOPE_PREFIX_RE, "").replace("/", "/"), shortName]) {
1164
1173
  if (!candidate.includes("/")) {
1165
1174
  if ((await $fetch.raw(`https://ungh.cc/repos/${shortName}/${shortName}`).catch(() => null))?.ok) return `https://github.com/${shortName}/${shortName}`;
1166
1175
  continue;
1167
1176
  }
1168
1177
  if ((await $fetch.raw(`https://ungh.cc/repos/${candidate}`).catch(() => null))?.ok) return `https://github.com/${candidate}`;
1169
1178
  }
1170
- const searchTerm = packageName.replace(/^@/, "");
1179
+ const searchTerm = packageName.replace(NPM_SCOPE_PREFIX_RE, "");
1171
1180
  if (isGhAvailable()) try {
1172
1181
  const { stdout: json } = spawnSync("gh", [
1173
1182
  "search",
@@ -1274,7 +1283,7 @@ async function resolveGitHubRepo(owner, repo, onProgress) {
1274
1283
  let releasedAt;
1275
1284
  const latestRelease = releases[0];
1276
1285
  if (latestRelease) {
1277
- version = latestRelease.tag.replace(/^v/, "");
1286
+ version = latestRelease.tag.replace(V_PREFIX_RE, "");
1278
1287
  releasedAt = latestRelease.publishedAt;
1279
1288
  }
1280
1289
  onProgress?.("Resolving docs");
@@ -1453,6 +1462,8 @@ async function resolveCrateDocsWithAttempts(crateName, options = {}) {
1453
1462
  attempts
1454
1463
  };
1455
1464
  }
1465
+ const STATIC_REGEX_2 = /[_.:-]/;
1466
+ const STATIC_REGEX_3 = /\/+$/;
1456
1467
  async function fetchCrawledDocs(url, onProgress, maxPages = 200) {
1457
1468
  const outputDir = join(tmpdir(), "skilld-crawl", Date.now().toString());
1458
1469
  onProgress?.(`Crawling ${url}`);
@@ -1492,7 +1503,7 @@ async function fetchCrawledDocs(url, onProgress, maxPages = 200) {
1492
1503
  localeFiltered++;
1493
1504
  continue;
1494
1505
  }
1495
- const segments = (new URL(result.url).pathname.replace(/\/$/, "") || "/index").split("/").filter(Boolean);
1506
+ const segments = (new URL(result.url).pathname.replace(TRAILING_SLASH_RE, "") || "/index").split("/").filter(Boolean);
1496
1507
  if (isForeignPathPrefix(segments[0], userLang)) {
1497
1508
  localeFiltered++;
1498
1509
  continue;
@@ -1540,11 +1551,11 @@ function isForeignPathPrefix(segment, userLang) {
1540
1551
  return LOCALE_CODES.has(lower);
1541
1552
  }
1542
1553
  function getUserLang() {
1543
- const code = (process.env.LC_ALL || process.env.LANG || process.env.LANGUAGE || "").split(/[_.:-]/)[0]?.toLowerCase() || "";
1554
+ const code = (process.env.LC_ALL || process.env.LANG || process.env.LANGUAGE || "").split(STATIC_REGEX_2)[0]?.toLowerCase() || "";
1544
1555
  return code.length >= 2 ? code.slice(0, 2) : "en";
1545
1556
  }
1546
1557
  function toCrawlPattern(docsUrl) {
1547
- return `${docsUrl.replace(/\/+$/, "")}/**`;
1558
+ return `${docsUrl.replace(STATIC_REGEX_3, "")}/**`;
1548
1559
  }
1549
1560
  const HIGH_VALUE_CATEGORIES = new Set([
1550
1561
  "q&a",
@@ -1722,6 +1733,7 @@ function generateDiscussionIndex(discussions) {
1722
1733
  }
1723
1734
  return sections.join("\n");
1724
1735
  }
1736
+ const STATIC_REGEX_1$4 = /\.md$/;
1725
1737
  function generateDocsIndex(docs) {
1726
1738
  const docFiles = docs.filter((d) => d.path.startsWith("docs/") && d.path.endsWith(".md") && !d.path.endsWith("_INDEX.md")).sort((a, b) => a.path.localeCompare(b.path));
1727
1739
  if (docFiles.length === 0) return "";
@@ -1747,7 +1759,7 @@ function generateDocsIndex(docs) {
1747
1759
  ];
1748
1760
  for (const file of rootFiles) {
1749
1761
  const rel = file.path.slice(5);
1750
- const title = extractTitle(file.content) || rel.replace(/\.md$/, "");
1762
+ const title = extractTitle(file.content) || rel.replace(STATIC_REGEX_1$4, "");
1751
1763
  const desc = extractDescription(file.content);
1752
1764
  const descPart = desc ? `: ${desc}` : "";
1753
1765
  sections.push(`- [${title}](./${rel})${descPart}`);
@@ -1757,7 +1769,7 @@ function generateDocsIndex(docs) {
1757
1769
  sections.push(`## ${dir} (${files.length})`, "");
1758
1770
  for (const file of files) {
1759
1771
  const rel = file.path.slice(5);
1760
- const title = extractTitle(file.content) || rel.replace(/\.md$/, "").split("/").pop();
1772
+ const title = extractTitle(file.content) || rel.replace(STATIC_REGEX_1$4, "").split("/").pop();
1761
1773
  const desc = extractDescription(file.content);
1762
1774
  const descPart = desc ? `: ${desc}` : "";
1763
1775
  sections.push(`- [${title}](./${rel})${descPart}`);
@@ -1795,12 +1807,24 @@ const SKIP_PATTERNS = [
1795
1807
  const MAX_FILE_SIZE = 500 * 1024;
1796
1808
  async function resolveEntryFiles(packageDir) {
1797
1809
  if (!existsSync(join(packageDir, "package.json"))) return [];
1798
- const files = await glob(["**/*.d.{ts,mts,cts}"], {
1799
- cwd: packageDir,
1800
- ignore: [...SKIP_DIRS.map((d) => `**/${d}/**`), ...SKIP_PATTERNS],
1801
- absolute: false,
1802
- expandDirectories: false
1810
+ const skipDirSet = new Set(SKIP_DIRS);
1811
+ const isSkipPattern = (name) => SKIP_PATTERNS.some((p) => {
1812
+ const star = p.indexOf("*");
1813
+ if (star === -1) return name === p;
1814
+ const prefix = p.slice(0, star);
1815
+ const suffix = p.slice(star + 1);
1816
+ return name.startsWith(prefix) && name.endsWith(suffix);
1803
1817
  });
1818
+ const files = [];
1819
+ for await (const file of glob(["**/*.d.{ts,mts,cts}"], {
1820
+ cwd: packageDir,
1821
+ exclude: (p) => {
1822
+ const segs = p.split("/");
1823
+ const last = segs[segs.length - 1];
1824
+ if (isSkipPattern(last)) return true;
1825
+ return segs.some((s) => skipDirSet.has(s));
1826
+ }
1827
+ })) files.push(file);
1804
1828
  const entries = [];
1805
1829
  for (const file of files) {
1806
1830
  const absPath = join(packageDir, file);
@@ -1819,6 +1843,7 @@ async function resolveEntryFiles(packageDir) {
1819
1843
  }
1820
1844
  return entries;
1821
1845
  }
1846
+ const STATIC_REGEX_1$3 = /^[\w.-]+\/[\w.-]+$/;
1822
1847
  function parseGitSkillInput(input) {
1823
1848
  const trimmed = input.trim();
1824
1849
  if (trimmed.startsWith("@")) return null;
@@ -1836,7 +1861,7 @@ function parseGitSkillInput(input) {
1836
1861
  return null;
1837
1862
  }
1838
1863
  if (trimmed.startsWith("https://") || trimmed.startsWith("http://")) return parseGitUrl(trimmed);
1839
- if (/^[\w.-]+\/[\w.-]+$/.test(trimmed)) return {
1864
+ if (STATIC_REGEX_1$3.test(trimmed)) return {
1840
1865
  type: "github",
1841
1866
  owner: trimmed.split("/")[0],
1842
1867
  repo: trimmed.split("/")[1]
@@ -1847,7 +1872,7 @@ function parseGitUrl(url) {
1847
1872
  try {
1848
1873
  const parsed = new URL(url);
1849
1874
  if (parsed.hostname === "github.com" || parsed.hostname === "www.github.com") {
1850
- const parts = parsed.pathname.replace(/^\//, "").replace(/\.git$/, "").split("/");
1875
+ const parts = parsed.pathname.replace(LEADING_SLASH_RE, "").replace(GIT_SUFFIX_RE, "").split("/");
1851
1876
  const owner = parts[0];
1852
1877
  const repo = parts[1];
1853
1878
  if (!owner || !repo) return null;
@@ -1865,7 +1890,7 @@ function parseGitUrl(url) {
1865
1890
  };
1866
1891
  }
1867
1892
  if (parsed.hostname === "gitlab.com") {
1868
- const parts = parsed.pathname.replace(/^\//, "").replace(/\.git$/, "").split("/");
1893
+ const parts = parsed.pathname.replace(LEADING_SLASH_RE, "").replace(GIT_SUFFIX_RE, "").split("/");
1869
1894
  const owner = parts[0];
1870
1895
  const repo = parts[1];
1871
1896
  if (!owner || !repo) return null;
@@ -2004,287 +2029,65 @@ async function downloadGitHubSkills(owner, repo, ref, skillPath, onProgress) {
2004
2029
  path: "",
2005
2030
  content,
2006
2031
  files: []
2007
- }];
2008
- }
2009
- return [];
2010
- } catch {
2011
- return [];
2012
- } finally {
2013
- rmSync(tempDir, {
2014
- recursive: true,
2015
- force: true
2016
- });
2017
- }
2018
- }
2019
- async function fetchGitLabSkills(source, onProgress) {
2020
- const { owner, repo } = source;
2021
- if (!owner || !repo) return { skills: [] };
2022
- const ref = source.ref || "main";
2023
- const tempDir = join(tmpdir(), `skilld-gitlab-${Date.now()}`);
2024
- try {
2025
- const subdir = source.skillPath || "skills";
2026
- onProgress?.(`Downloading ${owner}/${repo}/${subdir}@${ref}`);
2027
- const { dir } = await downloadTemplate(`gitlab:${owner}/${repo}/${subdir}#${ref}`, {
2028
- dir: tempDir,
2029
- force: true
2030
- });
2031
- if (source.skillPath) {
2032
- const skill = readLocalSkill(dir, source.skillPath);
2033
- return { skills: skill ? [skill] : [] };
2034
- }
2035
- const skills = [];
2036
- for (const { dir: skillDir, repoPath } of findSkillDirs(dir, "skills")) {
2037
- const skill = readLocalSkill(skillDir, repoPath);
2038
- if (skill) skills.push(skill);
2039
- }
2040
- if (skills.length > 0) {
2041
- onProgress?.(`Found ${skills.length} skill(s)`);
2042
- return { skills };
2043
- }
2044
- const content = await $fetch(`https://gitlab.com/${owner}/${repo}/-/raw/${ref}/SKILL.md`, { responseType: "text" }).catch(() => null);
2045
- if (content) {
2046
- const fm = parseSkillFrontmatterName(content);
2047
- return { skills: [{
2048
- name: fm.name || repo,
2049
- description: fm.description || "",
2050
- path: "",
2051
- content,
2052
- files: []
2053
- }] };
2054
- }
2055
- return { skills: [] };
2056
- } catch {
2057
- return { skills: [] };
2058
- } finally {
2059
- rmSync(tempDir, {
2060
- recursive: true,
2061
- force: true
2062
- });
2063
- }
2064
- }
2065
- async function searchNpmPackages(query, size = 5) {
2066
- const data = await $fetch(`https://registry.npmjs.org/-/v1/search?text=${encodeURIComponent(query)}&size=${size}`).catch(() => null);
2067
- if (!data?.objects?.length) return [];
2068
- return data.objects.map((o) => ({
2069
- name: o.package.name,
2070
- description: o.package.description,
2071
- version: o.package.version
2072
- }));
2073
- }
2074
- async function fetchNpmPackage(packageName) {
2075
- const data = await $fetch(`https://unpkg.com/${packageName}/package.json`).catch(() => null);
2076
- if (data) return data;
2077
- return $fetch(`https://registry.npmjs.org/${packageName}/latest`).catch(() => null);
2078
- }
2079
- async function fetchNpmRegistryMeta(packageName, version) {
2080
- const { name: barePackageName } = parsePackageSpec(packageName);
2081
- const data = await $fetch(`https://registry.npmjs.org/${barePackageName}`, { headers: { Accept: "application/vnd.npm.install-v1+json" } }).catch(() => null);
2082
- if (!data) return {};
2083
- const distTags = data["dist-tags"] ? Object.fromEntries(Object.entries(data["dist-tags"]).map(([tag, ver]) => [tag, {
2084
- version: ver,
2085
- releasedAt: data.time?.[ver]
2086
- }])) : void 0;
2087
- return {
2088
- releasedAt: data.time?.[version] || void 0,
2089
- distTags
2090
- };
2091
- }
2092
- async function resolveGitHub(gh, targetVersion, pkg, result, attempts, onProgress, opts) {
2093
- let allFiles;
2094
- if (targetVersion) {
2095
- onProgress?.("github-docs");
2096
- const gitDocs = await fetchGitDocs(gh.owner, gh.repo, targetVersion, pkg.name, opts?.rawRepoUrl);
2097
- if (gitDocs) {
2098
- result.gitDocsUrl = gitDocs.baseUrl;
2099
- result.gitRef = gitDocs.ref;
2100
- result.gitDocsFallback = gitDocs.fallback;
2101
- allFiles = gitDocs.allFiles;
2102
- attempts.push({
2103
- source: "github-docs",
2104
- url: gitDocs.baseUrl,
2105
- status: "success",
2106
- message: gitDocs.fallback ? `Found ${gitDocs.files.length} docs at ${gitDocs.ref} (no tag for v${targetVersion})` : `Found ${gitDocs.files.length} docs at ${gitDocs.ref}`
2107
- });
2108
- } else attempts.push({
2109
- source: "github-docs",
2110
- url: `${result.repoUrl}/tree/v${targetVersion}/docs`,
2111
- status: "not-found",
2112
- message: "No docs/ folder found at version tag"
2113
- });
2114
- }
2115
- if (!result.docsUrl) {
2116
- onProgress?.("github-meta");
2117
- const repoMeta = await fetchGitHubRepoMeta(gh.owner, gh.repo, pkg.name);
2118
- if (repoMeta?.homepage && !isUselessDocsUrl(repoMeta.homepage)) {
2119
- result.docsUrl = repoMeta.homepage;
2120
- attempts.push({
2121
- source: "github-meta",
2122
- url: result.repoUrl,
2123
- status: "success",
2124
- message: `Found homepage: ${repoMeta.homepage}`
2125
- });
2126
- } else attempts.push({
2127
- source: "github-meta",
2128
- url: result.repoUrl,
2129
- status: "not-found",
2130
- message: "No homepage in repo metadata"
2131
- });
2132
- }
2133
- onProgress?.("readme");
2134
- const readmeUrl = await fetchReadme(gh.owner, gh.repo, opts?.subdir, result.gitRef);
2135
- if (readmeUrl) {
2136
- result.readmeUrl = readmeUrl;
2137
- attempts.push({
2138
- source: "readme",
2139
- url: readmeUrl,
2140
- status: "success"
2141
- });
2142
- } else attempts.push({
2143
- source: "readme",
2144
- url: `${result.repoUrl}/README.md`,
2145
- status: "not-found",
2146
- message: "No README found"
2147
- });
2148
- return allFiles;
2149
- }
2150
- async function resolvePackageDocs(packageName, options = {}) {
2151
- return (await resolvePackageDocsWithAttempts(packageName, options)).package;
2152
- }
2153
- async function resolvePackageDocsWithAttempts(packageName, options = {}) {
2154
- const attempts = [];
2155
- const { onProgress } = options;
2156
- onProgress?.("npm");
2157
- const pkg = await fetchNpmPackage(packageName);
2158
- if (!pkg) {
2159
- attempts.push({
2160
- source: "npm",
2161
- url: `https://registry.npmjs.org/${packageName}/latest`,
2162
- status: "not-found",
2163
- message: "Package not found on npm registry"
2164
- });
2165
- return {
2166
- package: null,
2167
- attempts
2168
- };
2169
- }
2170
- attempts.push({
2171
- source: "npm",
2172
- url: `https://registry.npmjs.org/${packageName}/latest`,
2173
- status: "success",
2174
- message: `Found ${pkg.name}@${pkg.version}`
2175
- });
2176
- const registryMeta = pkg.version ? await fetchNpmRegistryMeta(packageName, pkg.version) : {};
2177
- const result = {
2178
- name: pkg.name,
2179
- version: pkg.version,
2180
- releasedAt: registryMeta.releasedAt,
2181
- description: pkg.description,
2182
- dependencies: pkg.dependencies,
2183
- distTags: registryMeta.distTags
2184
- };
2185
- let gitDocsAllFiles;
2186
- let subdir;
2187
- let rawRepoUrl;
2188
- if (typeof pkg.repository === "object" && pkg.repository?.url) {
2189
- rawRepoUrl = pkg.repository.url;
2190
- const normalized = normalizeRepoUrl(rawRepoUrl);
2191
- if (!normalized.includes("://") && normalized.includes("/") && !normalized.includes(":")) result.repoUrl = `https://github.com/${normalized}`;
2192
- else result.repoUrl = normalized;
2193
- subdir = pkg.repository.directory;
2194
- } else if (typeof pkg.repository === "string") if (pkg.repository.includes("://")) {
2195
- const gh = parseGitHubUrl(pkg.repository);
2196
- if (gh) result.repoUrl = `https://github.com/${gh.owner}/${gh.repo}`;
2197
- } else {
2198
- const repo = pkg.repository.replace(/^github:/, "");
2199
- if (repo.includes("/") && !repo.includes(":")) result.repoUrl = `https://github.com/${repo}`;
2200
- }
2201
- if (pkg.homepage && !isGitHubRepoUrl(pkg.homepage) && !isUselessDocsUrl(pkg.homepage)) result.docsUrl = pkg.homepage;
2202
- if (result.repoUrl?.includes("github.com")) {
2203
- const gh = parseGitHubUrl(result.repoUrl);
2204
- if (gh) gitDocsAllFiles = await resolveGitHub(gh, options.version || pkg.version, pkg, result, attempts, onProgress, {
2205
- rawRepoUrl,
2206
- subdir
2207
- });
2208
- } else if (!result.repoUrl) {
2209
- onProgress?.("github-search");
2210
- const searchedUrl = await searchGitHubRepo(pkg.name);
2211
- if (searchedUrl) {
2212
- result.repoUrl = searchedUrl;
2213
- attempts.push({
2214
- source: "github-search",
2215
- url: searchedUrl,
2216
- status: "success",
2217
- message: `Found via GitHub search: ${searchedUrl}`
2218
- });
2219
- const gh = parseGitHubUrl(searchedUrl);
2220
- if (gh) gitDocsAllFiles = await resolveGitHub(gh, options.version || pkg.version, pkg, result, attempts, onProgress);
2221
- } else attempts.push({
2222
- source: "github-search",
2223
- status: "not-found",
2224
- message: "No repository URL in package.json and GitHub search found no match"
2225
- });
2226
- }
2227
- const crawlUrl = getCrawlUrl(packageName);
2228
- if (crawlUrl) result.crawlUrl = crawlUrl;
2229
- if (result.docsUrl) {
2230
- onProgress?.("llms.txt");
2231
- const llmsUrl = await fetchLlmsUrl(result.docsUrl);
2232
- if (llmsUrl) {
2233
- result.llmsUrl = llmsUrl;
2234
- attempts.push({
2235
- source: "llms.txt",
2236
- url: llmsUrl,
2237
- status: "success"
2238
- });
2239
- } else attempts.push({
2240
- source: "llms.txt",
2241
- url: `${new URL(result.docsUrl).origin}/llms.txt`,
2242
- status: "not-found",
2243
- message: "No llms.txt at docs URL"
2032
+ }];
2033
+ }
2034
+ return [];
2035
+ } catch {
2036
+ return [];
2037
+ } finally {
2038
+ rmSync(tempDir, {
2039
+ recursive: true,
2040
+ force: true
2244
2041
  });
2245
2042
  }
2246
- if (result.gitDocsUrl && result.llmsUrl && gitDocsAllFiles) {
2247
- const llmsContent = await fetchLlmsTxt(result.llmsUrl);
2248
- if (llmsContent && llmsContent.links.length > 0) {
2249
- const validation = validateGitDocsWithLlms(llmsContent.links, gitDocsAllFiles);
2250
- if (!validation.isValid) {
2251
- attempts.push({
2252
- source: "github-docs",
2253
- url: result.gitDocsUrl,
2254
- status: "not-found",
2255
- message: `Heuristic git docs don't match llms.txt links (${Math.round(validation.matchRatio * 100)}% match), preferring llms.txt`
2256
- });
2257
- result.gitDocsUrl = void 0;
2258
- result.gitRef = void 0;
2259
- }
2043
+ }
2044
+ async function fetchGitLabSkills(source, onProgress) {
2045
+ const { owner, repo } = source;
2046
+ if (!owner || !repo) return { skills: [] };
2047
+ const ref = source.ref || "main";
2048
+ const tempDir = join(tmpdir(), `skilld-gitlab-${Date.now()}`);
2049
+ try {
2050
+ const subdir = source.skillPath || "skills";
2051
+ onProgress?.(`Downloading ${owner}/${repo}/${subdir}@${ref}`);
2052
+ const { dir } = await downloadTemplate(`gitlab:${owner}/${repo}/${subdir}#${ref}`, {
2053
+ dir: tempDir,
2054
+ force: true
2055
+ });
2056
+ if (source.skillPath) {
2057
+ const skill = readLocalSkill(dir, source.skillPath);
2058
+ return { skills: skill ? [skill] : [] };
2260
2059
  }
2261
- }
2262
- if (!result.docsUrl && !result.llmsUrl && !result.readmeUrl && !result.gitDocsUrl && options.cwd) {
2263
- onProgress?.("local");
2264
- const pkgDir = join(options.cwd, "node_modules", packageName);
2265
- const readmeFile = existsSync(pkgDir) && readdirSync(pkgDir).find((f) => /^readme\.md$/i.test(f));
2266
- if (readmeFile) {
2267
- const readmePath = join(pkgDir, readmeFile);
2268
- result.readmeUrl = pathToFileURL(readmePath).href;
2269
- attempts.push({
2270
- source: "readme",
2271
- url: readmePath,
2272
- status: "success",
2273
- message: "Found local readme in node_modules"
2274
- });
2060
+ const skills = [];
2061
+ for (const { dir: skillDir, repoPath } of findSkillDirs(dir, "skills")) {
2062
+ const skill = readLocalSkill(skillDir, repoPath);
2063
+ if (skill) skills.push(skill);
2064
+ }
2065
+ if (skills.length > 0) {
2066
+ onProgress?.(`Found ${skills.length} skill(s)`);
2067
+ return { skills };
2068
+ }
2069
+ const content = await $fetch(`https://gitlab.com/${owner}/${repo}/-/raw/${ref}/SKILL.md`, { responseType: "text" }).catch(() => null);
2070
+ if (content) {
2071
+ const fm = parseSkillFrontmatterName(content);
2072
+ return { skills: [{
2073
+ name: fm.name || repo,
2074
+ description: fm.description || "",
2075
+ path: "",
2076
+ content,
2077
+ files: []
2078
+ }] };
2275
2079
  }
2080
+ return { skills: [] };
2081
+ } catch {
2082
+ return { skills: [] };
2083
+ } finally {
2084
+ rmSync(tempDir, {
2085
+ recursive: true,
2086
+ force: true
2087
+ });
2276
2088
  }
2277
- if (!result.docsUrl && !result.llmsUrl && !result.readmeUrl && !result.gitDocsUrl) return {
2278
- package: null,
2279
- attempts,
2280
- registryVersion: pkg.version
2281
- };
2282
- return {
2283
- package: result,
2284
- attempts,
2285
- registryVersion: pkg.version
2286
- };
2287
2089
  }
2090
+ const STATIC_REGEX_1$2 = /^[\^~>=<\d]/;
2288
2091
  function parseVersionSpecifier(name, version, cwd) {
2289
2092
  if (version.startsWith("link:")) {
2290
2093
  const linkedPkg = readPackageJsonSafe(join(resolve(cwd, version.slice(5)), "package.json"));
@@ -2309,9 +2112,9 @@ function parseVersionSpecifier(name, version, cwd) {
2309
2112
  name,
2310
2113
  version: installed
2311
2114
  };
2312
- if (/^[\^~>=<\d]/.test(version)) return {
2115
+ if (STATIC_REGEX_1$2.test(version)) return {
2313
2116
  name,
2314
- version: version.replace(/^[\^~>=<]+/, "")
2117
+ version: version.replace(VERSION_RANGE_PREFIX_RE, "")
2315
2118
  };
2316
2119
  if (version.startsWith("catalog:") || version.startsWith("workspace:")) return {
2317
2120
  name,
@@ -2320,11 +2123,14 @@ function parseVersionSpecifier(name, version, cwd) {
2320
2123
  return null;
2321
2124
  }
2322
2125
  function resolveInstalledVersion(name, cwd) {
2126
+ const direct = readPackageJsonSafe(join(cwd, "node_modules", ...name.split("/"), "package.json"));
2127
+ if (direct) return direct.parsed.version || null;
2128
+ const req = createRequire(join(cwd, "package.json"));
2323
2129
  try {
2324
- return readPackageJsonSafe(resolvePathSync(`${name}/package.json`, { url: cwd }))?.parsed.version || null;
2130
+ return readPackageJsonSafe(req.resolve(`${name}/package.json`))?.parsed.version || null;
2325
2131
  } catch {
2326
2132
  try {
2327
- let dir = dirname(resolvePathSync(name, { url: cwd }));
2133
+ let dir = dirname(req.resolve(name));
2328
2134
  while (dir && basename(dir) !== "node_modules") {
2329
2135
  const pkg = readPackageJsonSafe(join(dir, "package.json"));
2330
2136
  if (pkg) return pkg.parsed.version || null;
@@ -2389,12 +2195,50 @@ async function resolveLocalPackageDocs(localPath) {
2389
2195
  }
2390
2196
  }
2391
2197
  if (!result.readmeUrl && !result.gitDocsUrl) {
2392
- const readmeFile = readdirSync(localPath).find((f) => /^readme\.md$/i.test(f));
2198
+ const readmeFile = readdirSync(localPath).find((f) => README_FILENAME_RE.test(f));
2393
2199
  if (readmeFile) result.readmeUrl = pathToFileURL(join(localPath, readmeFile)).href;
2394
2200
  }
2395
2201
  if (!result.readmeUrl && !result.gitDocsUrl) return null;
2396
2202
  return result;
2397
2203
  }
2204
+ async function resolveLocalDep(packageName, cwd) {
2205
+ const result = readPackageJsonSafe(join(cwd, "package.json"));
2206
+ if (!result) return null;
2207
+ const pkg = result.parsed;
2208
+ const depVersion = {
2209
+ ...pkg.dependencies,
2210
+ ...pkg.devDependencies
2211
+ }[packageName];
2212
+ if (!depVersion?.startsWith("link:")) return null;
2213
+ return resolveLocalPackageDocs(resolve(cwd, depVersion.slice(5)));
2214
+ }
2215
+ async function searchNpmPackages(query, size = 5) {
2216
+ const data = await $fetch(`https://registry.npmjs.org/-/v1/search?text=${encodeURIComponent(query)}&size=${size}`).catch(() => null);
2217
+ if (!data?.objects?.length) return [];
2218
+ return data.objects.map((o) => ({
2219
+ name: o.package.name,
2220
+ description: o.package.description,
2221
+ version: o.package.version
2222
+ }));
2223
+ }
2224
+ async function fetchNpmPackage(packageName) {
2225
+ const data = await $fetch(`https://unpkg.com/${packageName}/package.json`).catch(() => null);
2226
+ if (data) return data;
2227
+ return $fetch(`https://registry.npmjs.org/${packageName}/latest`).catch(() => null);
2228
+ }
2229
+ async function fetchNpmRegistryMeta(packageName, version) {
2230
+ const { name: barePackageName } = parsePackageSpec(packageName);
2231
+ const data = await $fetch(`https://registry.npmjs.org/${barePackageName}`, { headers: { Accept: "application/vnd.npm.install-v1+json" } }).catch(() => null);
2232
+ if (!data) return {};
2233
+ const distTags = data["dist-tags"] ? Object.fromEntries(Object.entries(data["dist-tags"]).map(([tag, ver]) => [tag, {
2234
+ version: ver,
2235
+ releasedAt: data.time?.[ver]
2236
+ }])) : void 0;
2237
+ return {
2238
+ releasedAt: data.time?.[version] || void 0,
2239
+ distTags
2240
+ };
2241
+ }
2398
2242
  async function fetchPkgDist(name, version) {
2399
2243
  const cacheDir = getCacheDir(name, version);
2400
2244
  const pkgDir = join(cacheDir, "pkg");
@@ -2467,11 +2311,457 @@ async function fetchLatestVersion(packageName) {
2467
2311
  if (data?.version) return data.version;
2468
2312
  return (await $fetch(`https://registry.npmjs.org/${packageName}`, { headers: { Accept: "application/vnd.npm.install-v1+json" } }).catch(() => null))?.["dist-tags"]?.latest || null;
2469
2313
  }
2470
- function getInstalledSkillVersion(skillDir) {
2471
- const skillPath = join(skillDir, "SKILL.md");
2472
- if (!existsSync(skillPath)) return null;
2473
- return readFileSync(skillPath, "utf-8").match(/^version:\s*"?([^"\n]+)"?/m)?.[1] || null;
2314
+ const STATIC_REGEX_1$1 = /^[\w.-]+\/[\w.-]+/;
2315
+ function parseSkillInput(input) {
2316
+ const trimmed = input.trim();
2317
+ if (trimmed.startsWith("npm:")) {
2318
+ const { name, tag } = splitPackageTag(trimmed.slice(4));
2319
+ return {
2320
+ type: "npm",
2321
+ package: name,
2322
+ tag
2323
+ };
2324
+ }
2325
+ if (trimmed.startsWith("crate:")) {
2326
+ const rest = trimmed.slice(6).trim();
2327
+ const atIdx = rest.indexOf("@");
2328
+ return {
2329
+ type: "crate",
2330
+ package: (atIdx === -1 ? rest : rest.slice(0, atIdx)).toLowerCase(),
2331
+ version: atIdx === -1 ? void 0 : rest.slice(atIdx + 1) || void 0
2332
+ };
2333
+ }
2334
+ if (trimmed.startsWith("gh:") || trimmed.startsWith("github:")) {
2335
+ const rest = trimmed.startsWith("gh:") ? trimmed.slice(3) : trimmed.slice(7);
2336
+ const gitSource = parseGitSkillInput(rest);
2337
+ if (gitSource) return {
2338
+ type: "git",
2339
+ source: gitSource
2340
+ };
2341
+ if (STATIC_REGEX_1$1.test(rest)) {
2342
+ const [owner, repo] = rest.split("/");
2343
+ return {
2344
+ type: "git",
2345
+ source: {
2346
+ type: "github",
2347
+ owner,
2348
+ repo
2349
+ }
2350
+ };
2351
+ }
2352
+ return {
2353
+ type: "bare",
2354
+ package: rest
2355
+ };
2356
+ }
2357
+ if (trimmed.startsWith("@")) {
2358
+ const rest = trimmed.slice(1);
2359
+ if (rest.indexOf("/") === -1) return {
2360
+ type: "curator",
2361
+ handle: rest
2362
+ };
2363
+ const { name, tag } = splitPackageTag(trimmed);
2364
+ return {
2365
+ type: "bare",
2366
+ package: name,
2367
+ tag
2368
+ };
2369
+ }
2370
+ const gitSource = parseGitSkillInput(trimmed);
2371
+ if (gitSource) return {
2372
+ type: "git",
2373
+ source: gitSource
2374
+ };
2375
+ const { name, tag } = splitPackageTag(trimmed);
2376
+ return {
2377
+ type: "bare",
2378
+ package: name,
2379
+ tag
2380
+ };
2381
+ }
2382
+ function resolveSkillName(input) {
2383
+ const source = parseSkillInput(input);
2384
+ switch (source.type) {
2385
+ case "npm":
2386
+ case "bare": return source.package;
2387
+ case "crate": return `crate:${source.package}`;
2388
+ case "git":
2389
+ if (source.source.type === "github" && source.source.repo) return source.source.repo;
2390
+ return null;
2391
+ case "curator":
2392
+ case "collection": return null;
2393
+ default: throw new Error(`Unhandled SkillSource type: ${JSON.stringify(source)}`);
2394
+ }
2395
+ }
2396
+ function toStoragePackageName(identityName) {
2397
+ if (identityName.startsWith("crate:")) return `@skilld-crate/${identityName.slice(6)}`;
2398
+ return identityName;
2399
+ }
2400
+ function isCrateSpec(spec) {
2401
+ return spec.startsWith("crate:");
2402
+ }
2403
+ function toCrateIdentity(crateName) {
2404
+ return `crate:${crateName}`;
2405
+ }
2406
+ function splitPackageTag(spec) {
2407
+ if (spec.startsWith("@")) {
2408
+ const slashIdx = spec.indexOf("/");
2409
+ if (slashIdx !== -1) {
2410
+ const afterSlash = spec.indexOf("@", slashIdx);
2411
+ if (afterSlash !== -1) return {
2412
+ name: spec.slice(0, afterSlash),
2413
+ tag: spec.slice(afterSlash + 1) || void 0
2414
+ };
2415
+ }
2416
+ return { name: spec };
2417
+ }
2418
+ const atIdx = spec.indexOf("@");
2419
+ if (atIdx !== -1) return {
2420
+ name: spec.slice(0, atIdx),
2421
+ tag: spec.slice(atIdx + 1) || void 0
2422
+ };
2423
+ return { name: spec };
2424
+ }
2425
+ const crawlUrlResolver = defineResolver({
2426
+ id: "crawl",
2427
+ canResolve: (ctx) => !!ctx.result,
2428
+ async run(ctx) {
2429
+ const crawlUrl = getCrawlUrl(ctx.packageName);
2430
+ if (crawlUrl) ctx.result.crawlUrl = crawlUrl;
2431
+ return { kind: "ok" };
2432
+ }
2433
+ });
2434
+ const gitTagResolver = defineResolver({
2435
+ id: "github-docs",
2436
+ canResolve: (ctx) => !!ctx.result?.repoUrl?.includes("github.com"),
2437
+ async run(ctx) {
2438
+ const result = ctx.result;
2439
+ const gh = parseGitHubUrl(result.repoUrl);
2440
+ if (!gh) return { kind: "skip" };
2441
+ const targetVersion = ctx.options.version || ctx.npm?.version;
2442
+ if (!targetVersion) return { kind: "skip" };
2443
+ ctx.options.onProgress?.("github-docs");
2444
+ const gitDocs = await fetchGitDocs(gh.owner, gh.repo, targetVersion, ctx.packageName, ctx.rawRepoUrl);
2445
+ if (gitDocs) {
2446
+ result.gitDocsUrl = gitDocs.baseUrl;
2447
+ result.gitRef = gitDocs.ref;
2448
+ result.gitDocsFallback = gitDocs.fallback;
2449
+ ctx.gitDocsAllFiles = gitDocs.allFiles;
2450
+ ctx.attempts.push({
2451
+ source: "github-docs",
2452
+ url: gitDocs.baseUrl,
2453
+ status: "success",
2454
+ message: gitDocs.fallback ? `Found ${gitDocs.files.length} docs at ${gitDocs.ref} (no tag for v${targetVersion})` : `Found ${gitDocs.files.length} docs at ${gitDocs.ref}`
2455
+ });
2456
+ return { kind: "ok" };
2457
+ }
2458
+ ctx.attempts.push({
2459
+ source: "github-docs",
2460
+ url: `${result.repoUrl}/tree/v${targetVersion}/docs`,
2461
+ status: "not-found",
2462
+ message: "No docs/ folder found at version tag"
2463
+ });
2464
+ return { kind: "skip" };
2465
+ }
2466
+ });
2467
+ const githubMetaResolver = defineResolver({
2468
+ id: "github-meta",
2469
+ canResolve: (ctx) => !!ctx.result?.repoUrl?.includes("github.com") && !ctx.result.docsUrl,
2470
+ async run(ctx) {
2471
+ const result = ctx.result;
2472
+ const gh = parseGitHubUrl(result.repoUrl);
2473
+ if (!gh) return { kind: "skip" };
2474
+ ctx.options.onProgress?.("github-meta");
2475
+ const repoMeta = await fetchGitHubRepoMeta(gh.owner, gh.repo, ctx.packageName);
2476
+ if (repoMeta?.homepage && !isUselessDocsUrl(repoMeta.homepage)) {
2477
+ result.docsUrl = repoMeta.homepage;
2478
+ ctx.attempts.push({
2479
+ source: "github-meta",
2480
+ url: result.repoUrl,
2481
+ status: "success",
2482
+ message: `Found homepage: ${repoMeta.homepage}`
2483
+ });
2484
+ return { kind: "ok" };
2485
+ }
2486
+ ctx.attempts.push({
2487
+ source: "github-meta",
2488
+ url: result.repoUrl,
2489
+ status: "not-found",
2490
+ message: "No homepage in repo metadata"
2491
+ });
2492
+ return { kind: "skip" };
2493
+ }
2494
+ });
2495
+ const githubReadmeResolver = defineResolver({
2496
+ id: "readme",
2497
+ canResolve: (ctx) => !!ctx.result?.repoUrl?.includes("github.com"),
2498
+ async run(ctx) {
2499
+ const result = ctx.result;
2500
+ const gh = parseGitHubUrl(result.repoUrl);
2501
+ if (!gh) return { kind: "skip" };
2502
+ ctx.options.onProgress?.("readme");
2503
+ const readmeUrl = await fetchReadme(gh.owner, gh.repo, ctx.subdir, result.gitRef);
2504
+ if (readmeUrl) {
2505
+ result.readmeUrl = readmeUrl;
2506
+ ctx.attempts.push({
2507
+ source: "readme",
2508
+ url: readmeUrl,
2509
+ status: "success"
2510
+ });
2511
+ return { kind: "ok" };
2512
+ }
2513
+ ctx.attempts.push({
2514
+ source: "readme",
2515
+ url: `${result.repoUrl}/README.md`,
2516
+ status: "not-found",
2517
+ message: "No README found"
2518
+ });
2519
+ return { kind: "skip" };
2520
+ }
2521
+ });
2522
+ const githubSearchResolver = defineResolver({
2523
+ id: "github-search",
2524
+ canResolve: (ctx) => !!ctx.result && !ctx.result.repoUrl,
2525
+ async run(ctx) {
2526
+ const result = ctx.result;
2527
+ ctx.options.onProgress?.("github-search");
2528
+ const searchedUrl = await searchGitHubRepo(ctx.packageName);
2529
+ if (searchedUrl) {
2530
+ result.repoUrl = searchedUrl;
2531
+ ctx.attempts.push({
2532
+ source: "github-search",
2533
+ url: searchedUrl,
2534
+ status: "success",
2535
+ message: `Found via GitHub search: ${searchedUrl}`
2536
+ });
2537
+ return { kind: "ok" };
2538
+ }
2539
+ ctx.attempts.push({
2540
+ source: "github-search",
2541
+ status: "not-found",
2542
+ message: "No repository URL in package.json and GitHub search found no match"
2543
+ });
2544
+ return { kind: "skip" };
2545
+ }
2546
+ });
2547
+ const llmsTxtResolver = defineResolver({
2548
+ id: "llms.txt",
2549
+ canResolve: (ctx) => !!ctx.result?.docsUrl,
2550
+ async run(ctx) {
2551
+ const result = ctx.result;
2552
+ ctx.options.onProgress?.("llms.txt");
2553
+ const llmsUrl = await fetchLlmsUrl(result.docsUrl);
2554
+ if (llmsUrl) {
2555
+ result.llmsUrl = llmsUrl;
2556
+ ctx.attempts.push({
2557
+ source: "llms.txt",
2558
+ url: llmsUrl,
2559
+ status: "success"
2560
+ });
2561
+ } else ctx.attempts.push({
2562
+ source: "llms.txt",
2563
+ url: `${new URL(result.docsUrl).origin}/llms.txt`,
2564
+ status: "not-found",
2565
+ message: "No llms.txt at docs URL"
2566
+ });
2567
+ if (result.gitDocsUrl && result.llmsUrl && ctx.gitDocsAllFiles) {
2568
+ const llmsContent = await fetchLlmsTxt(result.llmsUrl);
2569
+ if (llmsContent && llmsContent.links.length > 0) {
2570
+ const validation = validateGitDocsWithLlms(llmsContent.links, ctx.gitDocsAllFiles);
2571
+ if (!validation.isValid) {
2572
+ ctx.attempts.push({
2573
+ source: "github-docs",
2574
+ url: result.gitDocsUrl,
2575
+ status: "not-found",
2576
+ message: `Heuristic git docs don't match llms.txt links (${Math.round(validation.matchRatio * 100)}% match), preferring llms.txt`
2577
+ });
2578
+ result.gitDocsUrl = void 0;
2579
+ result.gitRef = void 0;
2580
+ }
2581
+ }
2582
+ }
2583
+ return { kind: "ok" };
2584
+ }
2585
+ });
2586
+ const localReadmeResolver = defineResolver({
2587
+ id: "local",
2588
+ canResolve: (ctx) => {
2589
+ const r = ctx.result;
2590
+ return !!r && !!ctx.options.cwd && !r.docsUrl && !r.llmsUrl && !r.readmeUrl && !r.gitDocsUrl;
2591
+ },
2592
+ async run(ctx) {
2593
+ const result = ctx.result;
2594
+ ctx.options.onProgress?.("local");
2595
+ const pkgDir = join(ctx.options.cwd, "node_modules", ctx.packageName);
2596
+ const readmeFile = existsSync(pkgDir) && readdirSync(pkgDir).find((f) => README_FILENAME_RE.test(f));
2597
+ if (readmeFile) {
2598
+ const readmePath = join(pkgDir, readmeFile);
2599
+ result.readmeUrl = pathToFileURL(readmePath).href;
2600
+ ctx.attempts.push({
2601
+ source: "readme",
2602
+ url: readmePath,
2603
+ status: "success",
2604
+ message: "Found local readme in node_modules"
2605
+ });
2606
+ return { kind: "ok" };
2607
+ }
2608
+ return { kind: "skip" };
2609
+ }
2610
+ });
2611
+ const STATIC_REGEX_1 = /^github:/;
2612
+ const defaultResolvers = [
2613
+ defineResolver({
2614
+ id: "npm",
2615
+ async run(ctx) {
2616
+ ctx.options.onProgress?.("npm");
2617
+ const pkg = await fetchNpmPackage(ctx.packageName);
2618
+ if (!pkg) {
2619
+ ctx.attempts.push({
2620
+ source: "npm",
2621
+ url: `https://registry.npmjs.org/${ctx.packageName}/latest`,
2622
+ status: "not-found",
2623
+ message: "Package not found on npm registry"
2624
+ });
2625
+ return { kind: "fatal" };
2626
+ }
2627
+ ctx.attempts.push({
2628
+ source: "npm",
2629
+ url: `https://registry.npmjs.org/${ctx.packageName}/latest`,
2630
+ status: "success",
2631
+ message: `Found ${pkg.name}@${pkg.version}`
2632
+ });
2633
+ const registryMeta = pkg.version ? await fetchNpmRegistryMeta(ctx.packageName, pkg.version) : {};
2634
+ const result = {
2635
+ name: pkg.name,
2636
+ version: pkg.version,
2637
+ releasedAt: registryMeta.releasedAt,
2638
+ description: pkg.description,
2639
+ dependencies: pkg.dependencies,
2640
+ distTags: registryMeta.distTags
2641
+ };
2642
+ if (typeof pkg.repository === "object" && pkg.repository?.url) {
2643
+ ctx.rawRepoUrl = pkg.repository.url;
2644
+ const normalized = normalizeRepoUrl(pkg.repository.url);
2645
+ if (!normalized.includes("://") && normalized.includes("/") && !normalized.includes(":")) result.repoUrl = `https://github.com/${normalized}`;
2646
+ else result.repoUrl = normalized;
2647
+ ctx.subdir = pkg.repository.directory;
2648
+ } else if (typeof pkg.repository === "string") if (pkg.repository.includes("://")) {
2649
+ const gh = parseGitHubUrl(pkg.repository);
2650
+ if (gh) result.repoUrl = `https://github.com/${gh.owner}/${gh.repo}`;
2651
+ } else {
2652
+ const repo = pkg.repository.replace(STATIC_REGEX_1, "");
2653
+ if (repo.includes("/") && !repo.includes(":")) result.repoUrl = `https://github.com/${repo}`;
2654
+ }
2655
+ if (pkg.homepage && !isGitHubRepoUrl(pkg.homepage) && !isUselessDocsUrl(pkg.homepage)) result.docsUrl = pkg.homepage;
2656
+ ctx.npm = pkg;
2657
+ ctx.result = result;
2658
+ return { kind: "ok" };
2659
+ }
2660
+ }),
2661
+ githubSearchResolver,
2662
+ gitTagResolver,
2663
+ githubMetaResolver,
2664
+ githubReadmeResolver,
2665
+ crawlUrlResolver,
2666
+ llmsTxtResolver,
2667
+ localReadmeResolver
2668
+ ];
2669
+ function defineResolver(r) {
2670
+ return r;
2671
+ }
2672
+ function createContentResolver(opts) {
2673
+ return { async resolve(packageName, options = {}) {
2674
+ const ctx = {
2675
+ packageName,
2676
+ options,
2677
+ result: null,
2678
+ attempts: []
2679
+ };
2680
+ let registryVersion;
2681
+ for (const resolver of opts.resolvers) {
2682
+ if (resolver.canResolve && !resolver.canResolve(ctx)) continue;
2683
+ const outcome = await resolver.run(ctx);
2684
+ if (outcome.kind === "fatal") return {
2685
+ package: null,
2686
+ attempts: ctx.attempts,
2687
+ registryVersion: outcome.registryVersion ?? registryVersion
2688
+ };
2689
+ }
2690
+ registryVersion = ctx.npm?.version;
2691
+ const r = ctx.result;
2692
+ if (!r || !r.docsUrl && !r.llmsUrl && !r.readmeUrl && !r.gitDocsUrl) return {
2693
+ package: null,
2694
+ attempts: ctx.attempts,
2695
+ registryVersion
2696
+ };
2697
+ return {
2698
+ package: r,
2699
+ attempts: ctx.attempts,
2700
+ registryVersion
2701
+ };
2702
+ } };
2703
+ }
2704
+ const defaultContentResolver = createContentResolver({ resolvers: defaultResolvers });
2705
+ async function resolvePackageDocs(packageName, options = {}) {
2706
+ return (await defaultContentResolver.resolve(packageName, options)).package;
2707
+ }
2708
+ async function resolvePackageDocsWithAttempts(packageName, options = {}) {
2709
+ return defaultContentResolver.resolve(packageName, options);
2710
+ }
2711
+ const RESOLVE_STEP_LABELS = {
2712
+ "npm": "npm registry",
2713
+ "github-docs": "GitHub docs",
2714
+ "github-meta": "GitHub meta",
2715
+ "github-search": "GitHub search",
2716
+ "readme": "README",
2717
+ "llms.txt": "llms.txt",
2718
+ "crawl": "website crawl",
2719
+ "local": "node_modules"
2720
+ };
2721
+ async function resolvePackageOrCrate(packageSpec, opts) {
2722
+ const { cwd, onProgress } = opts;
2723
+ const isCrate = isCrateSpec(packageSpec);
2724
+ const normalizedSpec = isCrate ? packageSpec.slice(6).trim() : packageSpec;
2725
+ const { name: parsedName, tag: requestedTag } = parsePackageSpec(normalizedSpec);
2726
+ const packageName = isCrate ? parsedName.toLowerCase() : parsedName;
2727
+ const identityPackageName = isCrate ? toCrateIdentity(packageName) : packageName;
2728
+ const storagePackageName = toStoragePackageName(identityPackageName);
2729
+ const localDeps = isCrate ? [] : await readLocalDependencies(cwd).catch(() => []);
2730
+ const localVersion = isCrate ? void 0 : localDeps.find((d) => d.name === packageName)?.version;
2731
+ const resolveResult = isCrate ? await resolveCrateDocsWithAttempts(packageName, {
2732
+ version: requestedTag,
2733
+ onProgress
2734
+ }) : await resolvePackageDocsWithAttempts(requestedTag ? normalizedSpec : packageName, {
2735
+ version: localVersion,
2736
+ cwd,
2737
+ onProgress: (step) => onProgress?.(RESOLVE_STEP_LABELS[step] ?? step)
2738
+ });
2739
+ let resolved = resolveResult.package;
2740
+ if (!resolved && !isCrate) {
2741
+ onProgress?.(RESOLVE_STEP_LABELS.local);
2742
+ resolved = await resolveLocalDep(packageName, cwd);
2743
+ }
2744
+ return {
2745
+ packageName,
2746
+ identityPackageName,
2747
+ storagePackageName,
2748
+ isCrate,
2749
+ requestedTag,
2750
+ localVersion,
2751
+ resolved,
2752
+ attempts: resolveResult.attempts,
2753
+ registryVersion: resolveResult.registryVersion
2754
+ };
2755
+ }
2756
+ function semverValid(v) {
2757
+ return valid(v, true);
2758
+ }
2759
+ function semverGt(a, b) {
2760
+ return gt(a, b, true);
2761
+ }
2762
+ function semverDiff(a, b) {
2763
+ return diff(a, b);
2474
2764
  }
2475
- export { fetchText as $, filterFrameworkDocs as A, fetchGitHubIssues as B, toCrawlPattern as C, fetchGitHubRepoMeta as D, fetchGitDocs as E, extractSections as F, compareSemver as G, generateIssueIndex as H, fetchLlmsTxt as I, isPrerelease as J, fetchReleaseNotes as K, fetchLlmsUrl as L, resolveGitHubRepo as M, validateGitDocsWithLlms as N, fetchReadme as O, downloadLlmsDocs as P, fetchGitHubRaw as Q, normalizeLlmsLinks as R, fetchCrawledDocs as S, MIN_GIT_DOCS as T, isGhAvailable as U, formatIssueAsMarkdown as V, fetchBlogReleases as W, $fetch as X, parseSemver as Y, extractBranchHint as Z, resolveEntryFiles as _, getInstalledSkillVersion as a, verifyUrl as at, formatDiscussionAsMarkdown as b, readLocalPackageInfo as c, resolvePackageDocs as d, isGitHubRepoUrl as et, resolvePackageDocsWithAttempts as f, parseSkillFrontmatterName as g, parseGitSkillInput as h, fetchPkgDist as i, parsePackageSpec as it, isShallowGitDocs as j, fetchReadmeContent as k, resolveInstalledVersion as l, fetchGitSkills as m, fetchNpmPackage as n, parseGitHubRepoSlug as nt, parseVersionSpecifier as o, searchNpmPackages as p, generateReleaseIndex as q, fetchNpmRegistryMeta as r, parseGitHubUrl as rt, readLocalDependencies as s, fetchLatestVersion as t, normalizeRepoUrl as tt, resolveLocalPackageDocs as u, generateDocsIndex as v, resolveCrateDocsWithAttempts as w, generateDiscussionIndex as x, fetchGitHubDiscussions as y, parseMarkdownLinks as z };
2765
+ export { fetchGitHubIssues as A, fetchBlogReleases as B, fetchCrawledDocs as C, downloadLlmsDocs as D, resolveGitHubRepo as E, filterFrameworkDocs as F, generateReleaseIndex as H, isShallowGitDocs as I, parseGitHubRepoSlug as L, generateIssueIndex as M, isGhAvailable as N, fetchLlmsTxt as O, fetchGitDocs as P, parseGitHubUrl as R, generateDiscussionIndex as S, fetchReadmeContent as T, isPrerelease as U, fetchReleaseNotes as V, fetchGitHubRaw as W, fetchGitSkills as _, resolvePackageDocs as a, fetchGitHubDiscussions as b, resolveSkillName as c, fetchNpmPackage as d, fetchNpmRegistryMeta as f, readLocalPackageInfo as g, readLocalDependencies as h, resolvePackageOrCrate as i, formatIssueAsMarkdown as j, normalizeLlmsLinks as k, toStoragePackageName as l, searchNpmPackages as m, semverGt as n, isCrateSpec as o, fetchPkgDist as p, semverValid as r, parseSkillInput as s, semverDiff as t, fetchLatestVersion as u, resolveEntryFiles as v, toCrawlPattern as w, formatDiscussionAsMarkdown as x, generateDocsIndex as y, parsePackageSpec as z };
2476
2766
 
2477
- //# sourceMappingURL=sources.mjs.map
2767
+ //# sourceMappingURL=semver.mjs.map