skilld 1.5.4 → 1.6.2

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 (101) hide show
  1. package/README.md +3 -3
  2. package/dist/_chunks/agent.mjs +10 -78
  3. package/dist/_chunks/agent.mjs.map +1 -1
  4. package/dist/_chunks/assemble.mjs +0 -17
  5. package/dist/_chunks/assemble.mjs.map +1 -1
  6. package/dist/_chunks/author.mjs +0 -18
  7. package/dist/_chunks/author.mjs.map +1 -1
  8. package/dist/_chunks/cache.mjs +0 -72
  9. package/dist/_chunks/cache.mjs.map +1 -1
  10. package/dist/_chunks/cache2.mjs +84 -17
  11. package/dist/_chunks/cache2.mjs.map +1 -1
  12. package/dist/_chunks/cli-helpers.mjs +2 -49
  13. package/dist/_chunks/cli-helpers.mjs.map +1 -1
  14. package/dist/_chunks/cli-helpers2.mjs +0 -11
  15. package/dist/_chunks/config.mjs +0 -27
  16. package/dist/_chunks/config.mjs.map +1 -1
  17. package/dist/_chunks/core.mjs +9 -0
  18. package/dist/_chunks/detect.mjs +29 -226
  19. package/dist/_chunks/detect.mjs.map +1 -1
  20. package/dist/_chunks/embedding-cache.mjs +0 -5
  21. package/dist/_chunks/embedding-cache2.mjs +1 -2
  22. package/dist/_chunks/formatting.mjs +0 -6
  23. package/dist/_chunks/formatting.mjs.map +1 -1
  24. package/dist/_chunks/index.d.mts +0 -10
  25. package/dist/_chunks/index.d.mts.map +1 -1
  26. package/dist/_chunks/index2.d.mts +3 -6
  27. package/dist/_chunks/index2.d.mts.map +1 -1
  28. package/dist/_chunks/index3.d.mts +2 -31
  29. package/dist/_chunks/index3.d.mts.map +1 -1
  30. package/dist/_chunks/install.mjs +0 -15
  31. package/dist/_chunks/install.mjs.map +1 -1
  32. package/dist/_chunks/libs/@sinclair/typebox.mjs +0 -444
  33. package/dist/_chunks/libs/@sinclair/typebox.mjs.map +1 -1
  34. package/dist/_chunks/list.mjs +0 -16
  35. package/dist/_chunks/list.mjs.map +1 -1
  36. package/dist/_chunks/lockfile.mjs +1 -10
  37. package/dist/_chunks/lockfile.mjs.map +1 -1
  38. package/dist/_chunks/markdown.mjs +0 -9
  39. package/dist/_chunks/markdown.mjs.map +1 -1
  40. package/dist/_chunks/package-json.mjs +0 -25
  41. package/dist/_chunks/package-json.mjs.map +1 -1
  42. package/dist/_chunks/pool2.mjs +0 -2
  43. package/dist/_chunks/pool2.mjs.map +1 -1
  44. package/dist/_chunks/prepare.mjs +8 -7
  45. package/dist/_chunks/prepare.mjs.map +1 -1
  46. package/dist/_chunks/prepare2.mjs +0 -18
  47. package/dist/_chunks/prepare2.mjs.map +1 -1
  48. package/dist/_chunks/prompts.mjs +1 -102
  49. package/dist/_chunks/prompts.mjs.map +1 -1
  50. package/dist/_chunks/retriv.mjs +23 -24
  51. package/dist/_chunks/retriv.mjs.map +1 -1
  52. package/dist/_chunks/rolldown-runtime.mjs +0 -2
  53. package/dist/_chunks/sanitize.mjs +0 -78
  54. package/dist/_chunks/sanitize.mjs.map +1 -1
  55. package/dist/_chunks/search-interactive.mjs +0 -17
  56. package/dist/_chunks/search-interactive.mjs.map +1 -1
  57. package/dist/_chunks/search.mjs +0 -19
  58. package/dist/_chunks/search2.mjs +3 -12
  59. package/dist/_chunks/search2.mjs.map +1 -1
  60. package/dist/_chunks/setup.mjs +0 -13
  61. package/dist/_chunks/setup.mjs.map +1 -1
  62. package/dist/_chunks/shared.mjs +0 -10
  63. package/dist/_chunks/shared.mjs.map +1 -1
  64. package/dist/_chunks/skills.mjs +2 -2
  65. package/dist/_chunks/skills.mjs.map +1 -1
  66. package/dist/_chunks/sources.mjs +3 -453
  67. package/dist/_chunks/sources.mjs.map +1 -1
  68. package/dist/_chunks/sync-shared.mjs +0 -16
  69. package/dist/_chunks/sync-shared2.mjs +0 -42
  70. package/dist/_chunks/sync-shared2.mjs.map +1 -1
  71. package/dist/_chunks/sync.mjs +1 -21
  72. package/dist/_chunks/sync.mjs.map +1 -1
  73. package/dist/_chunks/sync2.mjs +0 -20
  74. package/dist/_chunks/types.d.mts +0 -2
  75. package/dist/_chunks/types.d.mts.map +1 -1
  76. package/dist/_chunks/uninstall.mjs +0 -25
  77. package/dist/_chunks/uninstall.mjs.map +1 -1
  78. package/dist/_chunks/validate.mjs +0 -7
  79. package/dist/_chunks/validate.mjs.map +1 -1
  80. package/dist/_chunks/wizard.mjs +15 -12
  81. package/dist/_chunks/wizard.mjs.map +1 -1
  82. package/dist/_chunks/yaml.mjs +0 -21
  83. package/dist/_chunks/yaml.mjs.map +1 -1
  84. package/dist/agent/index.d.mts +0 -24
  85. package/dist/agent/index.d.mts.map +1 -1
  86. package/dist/agent/index.mjs +0 -8
  87. package/dist/cache/index.mjs +0 -2
  88. package/dist/cli-entry.mjs +0 -6
  89. package/dist/cli-entry.mjs.map +1 -1
  90. package/dist/cli.mjs +37 -44
  91. package/dist/cli.mjs.map +1 -1
  92. package/dist/index.mjs +0 -6
  93. package/dist/prepare.mjs +0 -12
  94. package/dist/prepare.mjs.map +1 -1
  95. package/dist/retriv/index.mjs +0 -2
  96. package/dist/retriv/worker.d.mts +0 -3
  97. package/dist/retriv/worker.d.mts.map +1 -1
  98. package/dist/retriv/worker.mjs +0 -2
  99. package/dist/retriv/worker.mjs.map +1 -1
  100. package/dist/sources/index.mjs +0 -4
  101. package/package.json +17 -17
@@ -7,19 +7,15 @@ import { tmpdir } from "node:os";
7
7
  import { basename, dirname, join, resolve } from "pathe";
8
8
  import { createWriteStream, existsSync, mkdirSync, readFileSync, readdirSync, rmSync } from "node:fs";
9
9
  import { htmlToMarkdown } from "mdream";
10
+ import pLimit from "p-limit";
10
11
  import { spawnSync } from "node:child_process";
11
12
  import { ofetch } from "ofetch";
12
13
  import { crawlAndGenerate } from "@mdream/crawl";
13
14
  import { glob } from "tinyglobby";
14
15
  import { downloadTemplate } from "giget";
15
16
  import { fileURLToPath, pathToFileURL } from "node:url";
16
- import pLimit from "p-limit";
17
17
  import { Writable } from "node:stream";
18
18
  import { resolvePathSync } from "mlly";
19
- //#region src/sources/github-common.ts
20
- /**
21
- * Shared constants and helpers for GitHub source modules (issues, discussions, releases)
22
- */
23
19
  const BOT_USERS = new Set([
24
20
  "renovate[bot]",
25
21
  "dependabot[bot]",
@@ -27,25 +23,17 @@ const BOT_USERS = new Set([
27
23
  "dependabot",
28
24
  "github-actions[bot]"
29
25
  ]);
30
- /** Extract YYYY-MM-DD date from an ISO timestamp */
31
26
  const isoDate = (iso) => iso.split("T")[0];
32
- /** Build YAML frontmatter from a key-value object, auto-quoting strings with special chars */
33
27
  function buildFrontmatter(fields) {
34
28
  const lines = ["---"];
35
29
  for (const [k, v] of Object.entries(fields)) if (v !== void 0) lines.push(`${k}: ${typeof v === "string" ? yamlEscape(v) : v}`);
36
30
  lines.push("---");
37
31
  return lines.join("\n");
38
32
  }
39
- /** Check if body contains a code block */
40
33
  function hasCodeBlock(text) {
41
34
  return /```[\s\S]*?```/.test(text) || /`[^`]+`/.test(text);
42
35
  }
43
- /** Noise patterns in comments — filter these out */
44
36
  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;
45
- /**
46
- * Smart body truncation — preserves code blocks and error messages.
47
- * Instead of slicing at a char limit, finds a safe break point.
48
- */
49
37
  function truncateBody(body, limit) {
50
38
  if (body.length <= limit) return body;
51
39
  const codeBlockRe = /```[\s\S]*?```/g;
@@ -66,10 +54,6 @@ function truncateBody(body, limit) {
66
54
  return `${slice}...`;
67
55
  }
68
56
  let _ghToken;
69
- /**
70
- * Get GitHub auth token from gh CLI (cached).
71
- * Returns null if gh CLI is not available or not authenticated.
72
- */
73
57
  function getGitHubToken() {
74
58
  if (_ghToken !== void 0) return _ghToken;
75
59
  try {
@@ -88,13 +72,10 @@ function getGitHubToken() {
88
72
  }
89
73
  return _ghToken;
90
74
  }
91
- /** Repos where ungh.cc failed but gh api succeeded (likely private) */
92
75
  const _needsAuth = /* @__PURE__ */ new Set();
93
- /** Mark a repo as needing authenticated access */
94
76
  function markRepoPrivate(owner, repo) {
95
77
  _needsAuth.add(`${owner}/${repo}`);
96
78
  }
97
- /** Check if a repo is known to need authenticated access */
98
79
  function isKnownPrivateRepo(owner, repo) {
99
80
  return _needsAuth.has(`${owner}/${repo}`);
100
81
  }
@@ -106,24 +87,15 @@ const ghApiFetch = ofetch.create({
106
87
  headers: { "User-Agent": "skilld/1.0" }
107
88
  });
108
89
  const LINK_NEXT_RE = /<([^>]+)>;\s*rel="next"/;
109
- /** Parse GitHub Link header for next page URL */
110
90
  function parseLinkNext(header) {
111
91
  if (!header) return null;
112
92
  return header.match(LINK_NEXT_RE)?.[1] ?? null;
113
93
  }
114
- /**
115
- * Authenticated fetch against api.github.com. Returns null if no token or request fails.
116
- * Endpoint should be relative, e.g. `repos/owner/repo/releases`.
117
- */
118
94
  async function ghApi(endpoint) {
119
95
  const token = getGitHubToken();
120
96
  if (!token) return null;
121
97
  return ghApiFetch(`${GH_API}/${endpoint}`, { headers: { Authorization: `token ${token}` } }).catch(() => null);
122
98
  }
123
- /**
124
- * Paginated GitHub API fetch. Follows Link headers, returns concatenated arrays.
125
- * Endpoint should return a JSON array, e.g. `repos/owner/repo/releases`.
126
- */
127
99
  async function ghApiPaginated(endpoint) {
128
100
  const token = getGitHubToken();
129
101
  if (!token) return [];
@@ -138,25 +110,16 @@ async function ghApiPaginated(endpoint) {
138
110
  }
139
111
  return results;
140
112
  }
141
- //#endregion
142
- //#region src/sources/utils.ts
143
- /**
144
- * Shared utilities for doc resolution
145
- */
146
113
  const $fetch = ofetch.create({
147
114
  retry: 3,
148
115
  retryDelay: 500,
149
116
  timeout: 15e3,
150
117
  headers: { "User-Agent": "skilld/1.0" }
151
118
  });
152
- /**
153
- * Fetch text content from URL
154
- */
155
119
  async function fetchText(url) {
156
120
  return $fetch(url, { responseType: "text" }).catch(() => null);
157
121
  }
158
122
  const RAW_GH_RE = /raw\.githubusercontent\.com\/([^/]+)\/([^/]+)/;
159
- /** Extract owner/repo from a GitHub raw content URL */
160
123
  function extractGitHubRepo(url) {
161
124
  const match = url.match(RAW_GH_RE);
162
125
  return match ? {
@@ -164,14 +127,6 @@ function extractGitHubRepo(url) {
164
127
  repo: match[2]
165
128
  } : null;
166
129
  }
167
- /**
168
- * Fetch text from a GitHub raw URL with auth fallback for private repos.
169
- * Tries unauthenticated first (fast path), falls back to authenticated
170
- * request when the repo is known to be private or unauthenticated fails.
171
- *
172
- * Only sends auth tokens to raw.githubusercontent.com — returns null for
173
- * non-GitHub URLs that fail unauthenticated to prevent token leakage.
174
- */
175
130
  async function fetchGitHubRaw(url) {
176
131
  const gh = extractGitHubRepo(url);
177
132
  if (!(gh ? isKnownPrivateRepo(gh.owner, gh.repo) : false)) {
@@ -188,17 +143,11 @@ async function fetchGitHubRaw(url) {
188
143
  if (content) markRepoPrivate(gh.owner, gh.repo);
189
144
  return content;
190
145
  }
191
- /**
192
- * Verify URL exists and is not HTML (likely 404 page)
193
- */
194
146
  async function verifyUrl(url) {
195
147
  const res = await $fetch.raw(url, { method: "HEAD" }).catch(() => null);
196
148
  if (!res) return false;
197
149
  return !(res.headers.get("content-type") || "").includes("text/html");
198
150
  }
199
- /**
200
- * Check if URL points to a social media or package registry site (not real docs)
201
- */
202
151
  const USELESS_HOSTS = new Set([
203
152
  "twitter.com",
204
153
  "x.com",
@@ -218,9 +167,6 @@ function isUselessDocsUrl(url) {
218
167
  return false;
219
168
  }
220
169
  }
221
- /**
222
- * Check if URL is a GitHub repo URL (not a docs site)
223
- */
224
170
  function isGitHubRepoUrl(url) {
225
171
  try {
226
172
  const parsed = new URL(url);
@@ -229,9 +175,6 @@ function isGitHubRepoUrl(url) {
229
175
  return false;
230
176
  }
231
177
  }
232
- /**
233
- * Parse owner/repo from GitHub URL
234
- */
235
178
  function parseGitHubUrl(url) {
236
179
  const match = url.match(/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?(?:[/#]|$)/);
237
180
  if (!match) return null;
@@ -240,16 +183,9 @@ function parseGitHubUrl(url) {
240
183
  repo: match[2]
241
184
  };
242
185
  }
243
- /**
244
- * Normalize git repo URL to https
245
- */
246
186
  function normalizeRepoUrl(url) {
247
187
  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/");
248
188
  }
249
- /**
250
- * Parse package spec with optional dist-tag or version: "vue@beta" → { name: "vue", tag: "beta" }
251
- * Handles scoped packages: "@vue/reactivity@beta" → { name: "@vue/reactivity", tag: "beta" }
252
- */
253
189
  function parsePackageSpec(spec) {
254
190
  if (spec.startsWith("@")) {
255
191
  const slashIdx = spec.indexOf("/");
@@ -269,9 +205,6 @@ function parsePackageSpec(spec) {
269
205
  };
270
206
  return { name: spec };
271
207
  }
272
- /**
273
- * Extract branch hint from URL fragment (e.g. "git+https://...#main" → "main")
274
- */
275
208
  function extractBranchHint(url) {
276
209
  const hash = url.indexOf("#");
277
210
  if (hash === -1) return void 0;
@@ -279,11 +212,6 @@ function extractBranchHint(url) {
279
212
  if (!fragment || fragment === "readme") return void 0;
280
213
  return fragment;
281
214
  }
282
- //#endregion
283
- //#region src/sources/releases.ts
284
- /**
285
- * GitHub release notes fetching via GitHub API (preferred) with ungh.cc fallback
286
- */
287
215
  function parseSemver(version) {
288
216
  const clean = version.replace(/^v/, "");
289
217
  const match = clean.match(/^(\d+)(?:\.(\d+))?(?:\.(\d+))?/);
@@ -295,13 +223,6 @@ function parseSemver(version) {
295
223
  raw: clean
296
224
  };
297
225
  }
298
- /**
299
- * Extract version from a release tag, handling monorepo formats:
300
- * - `pkg@1.2.3` → `1.2.3`
301
- * - `pkg-v1.2.3` → `1.2.3`
302
- * - `v1.2.3` → `1.2.3`
303
- * - `1.2.3` → `1.2.3`
304
- */
305
226
  function extractVersion(tag, packageName) {
306
227
  if (packageName) {
307
228
  const atMatch = tag.match(new RegExp(`^${escapeRegex(packageName)}@(.+)$`));
@@ -314,15 +235,9 @@ function extractVersion(tag, packageName) {
314
235
  function escapeRegex(str) {
315
236
  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
316
237
  }
317
- /**
318
- * Check if a release tag belongs to a specific package
319
- */
320
238
  function tagMatchesPackage(tag, packageName) {
321
239
  return tag.startsWith(`${packageName}@`) || tag.startsWith(`${packageName}-v`) || tag.startsWith(`${packageName}-`);
322
240
  }
323
- /**
324
- * Check if a version string contains a prerelease suffix (e.g. 6.0.0-beta, 1.2.3-rc.1)
325
- */
326
241
  function isPrerelease(version) {
327
242
  return /^\d+\.\d+\.\d+-.+/.test(version.replace(/^v/, ""));
328
243
  }
@@ -331,7 +246,6 @@ function compareSemver(a, b) {
331
246
  if (a.minor !== b.minor) return a.minor - b.minor;
332
247
  return a.patch - b.patch;
333
248
  }
334
- /** Map GitHub API release to our GitHubRelease shape */
335
249
  function mapApiRelease(r) {
336
250
  return {
337
251
  id: r.id,
@@ -343,20 +257,11 @@ function mapApiRelease(r) {
343
257
  markdown: r.body
344
258
  };
345
259
  }
346
- /**
347
- * Fetch all releases — GitHub API first (authenticated, async), ungh.cc fallback
348
- */
349
260
  async function fetchAllReleases(owner, repo) {
350
261
  const apiReleases = await ghApiPaginated(`repos/${owner}/${repo}/releases`);
351
262
  if (apiReleases.length > 0) return apiReleases.map(mapApiRelease);
352
263
  return (await $fetch(`https://ungh.cc/repos/${owner}/${repo}/releases`, { signal: AbortSignal.timeout(15e3) }).catch(() => null))?.releases ?? [];
353
264
  }
354
- /**
355
- * Select last 20 stable releases for a package, sorted newest first.
356
- * For monorepos, filters to package-specific tags (pkg@version).
357
- * Falls back to generic tags (v1.2.3) only if no package-specific found.
358
- * If installedVersion is provided, filters out releases newer than it.
359
- */
360
265
  function selectReleases(releases, packageName, installedVersion, fromDate) {
361
266
  const hasMonorepoTags = packageName && releases.some((r) => tagMatchesPackage(r.tag, packageName));
362
267
  const installedSv = installedVersion ? parseSemver(installedVersion) : null;
@@ -386,9 +291,6 @@ function selectReleases(releases, packageName, installedVersion, fromDate) {
386
291
  });
387
292
  return fromDate ? sorted : sorted.slice(0, 20);
388
293
  }
389
- /**
390
- * Format a release as markdown with YAML frontmatter
391
- */
392
294
  function formatRelease(release, packageName) {
393
295
  const date = isoDate(release.publishedAt || release.createdAt);
394
296
  const version = extractVersion(release.tag, packageName) || release.tag;
@@ -402,10 +304,6 @@ function formatRelease(release, packageName) {
402
304
  fm.push("---");
403
305
  return `${fm.join("\n")}\n\n# ${release.name || release.tag}\n\n${release.markdown}`;
404
306
  }
405
- /**
406
- * Generate a unified summary index of all releases for quick LLM scanning.
407
- * Includes GitHub releases, blog release posts, and CHANGELOG link.
408
- */
409
307
  function generateReleaseIndex(releasesOrOpts, packageName) {
410
308
  const opts = Array.isArray(releasesOrOpts) ? {
411
309
  releases: releasesOrOpts,
@@ -447,18 +345,10 @@ function generateReleaseIndex(releasesOrOpts, packageName) {
447
345
  }
448
346
  return lines.join("\n");
449
347
  }
450
- /**
451
- * Check if a single release is a stub redirecting to CHANGELOG.md.
452
- * Short body (<500 chars) that mentions CHANGELOG indicates no real content.
453
- */
454
348
  function isStubRelease(release) {
455
349
  const body = (release.markdown || "").trim();
456
350
  return body.length < 500 && /changelog\.md/i.test(body);
457
351
  }
458
- /**
459
- * Fetch CHANGELOG.md from a GitHub repo at a specific ref as fallback.
460
- * For monorepos, also checks packages/{shortName}/CHANGELOG.md.
461
- */
462
352
  async function fetchChangelog(owner, repo, ref, packageName) {
463
353
  const paths = [];
464
354
  if (packageName) {
@@ -474,13 +364,6 @@ async function fetchChangelog(owner, repo, ref, packageName) {
474
364
  }
475
365
  return null;
476
366
  }
477
- /**
478
- * Fetch release notes for a package. Returns CachedDoc[] with releases/{tag}.md files.
479
- *
480
- * Strategy:
481
- * 1. Fetch GitHub releases, filter to package-specific tags for monorepos
482
- * 2. If no releases found, try CHANGELOG.md as fallback
483
- */
484
367
  async function fetchReleaseNotes(owner, repo, installedVersion, gitRef, packageName, fromDate, changelogRef) {
485
368
  const selected = selectReleases(await fetchAllReleases(owner, repo), packageName, installedVersion, fromDate);
486
369
  if (selected.length > 0) {
@@ -504,11 +387,6 @@ async function fetchReleaseNotes(owner, repo, installedVersion, gitRef, packageN
504
387
  content: changelog
505
388
  }];
506
389
  }
507
- //#endregion
508
- //#region src/sources/blog-releases.ts
509
- /**
510
- * Format a blog release as markdown with YAML frontmatter
511
- */
512
390
  function formatBlogRelease(release) {
513
391
  return `${[
514
392
  "---",
@@ -520,9 +398,6 @@ function formatBlogRelease(release) {
520
398
  "---"
521
399
  ].join("\n")}\n\n# ${release.title}\n\n${release.markdown}`;
522
400
  }
523
- /**
524
- * Fetch and parse a single blog post using preset metadata for version/date
525
- */
526
401
  async function fetchBlogPost(entry) {
527
402
  try {
528
403
  const html = await $fetch(entry.url, {
@@ -550,11 +425,6 @@ async function fetchBlogPost(entry) {
550
425
  return null;
551
426
  }
552
427
  }
553
- /**
554
- * Filter blog releases by installed version
555
- * Only includes releases where version <= installedVersion
556
- * Returns all releases if version parsing fails (fail-safe)
557
- */
558
428
  function filterBlogsByVersion(entries, installedVersion) {
559
429
  const installedSv = parseSemver(installedVersion);
560
430
  if (!installedSv) return entries;
@@ -564,23 +434,13 @@ function filterBlogsByVersion(entries, installedVersion) {
564
434
  return compareSemver(entrySv, installedSv) <= 0;
565
435
  });
566
436
  }
567
- /**
568
- * Fetch blog release notes from package presets
569
- * Filters to only releases matching or older than the installed version
570
- * Returns CachedDoc[] with releases/blog-{version}.md files
571
- */
572
437
  async function fetchBlogReleases(packageName, installedVersion) {
573
438
  const preset = getBlogPreset(packageName);
574
439
  if (!preset) return [];
575
440
  const filteredReleases = filterBlogsByVersion(preset.releases, installedVersion);
576
441
  if (filteredReleases.length === 0) return [];
577
- const releases = [];
578
- const batchSize = 3;
579
- for (let i = 0; i < filteredReleases.length; i += batchSize) {
580
- const batch = filteredReleases.slice(i, i + batchSize);
581
- const results = await Promise.all(batch.map((entry) => fetchBlogPost(entry)));
582
- for (const result of results) if (result) releases.push(result);
583
- }
442
+ const limit = pLimit(3);
443
+ const releases = (await Promise.all(filteredReleases.map((entry) => limit(() => fetchBlogPost(entry))))).filter((r) => r !== null);
584
444
  if (releases.length === 0) return [];
585
445
  releases.sort((a, b) => {
586
446
  const aVer = a.version.split(".").map(Number);
@@ -596,19 +456,6 @@ async function fetchBlogReleases(packageName, installedVersion) {
596
456
  content: formatBlogRelease(r)
597
457
  }));
598
458
  }
599
- //#endregion
600
- //#region src/sources/crawl.ts
601
- /**
602
- * Website crawl doc source — fetches docs by crawling a URL pattern
603
- */
604
- /**
605
- * Crawl a URL pattern and return docs as cached doc format.
606
- * Uses HTTP crawler (no browser needed) with sitemap discovery + glob filtering.
607
- *
608
- * @param url - URL with optional glob pattern (e.g. 'https://example.com/docs/**')
609
- * @param onProgress - Optional progress callback
610
- * @param maxPages - Max pages to crawl (default 200)
611
- */
612
459
  async function fetchCrawledDocs(url, onProgress, maxPages = 200) {
613
460
  const outputDir = join(tmpdir(), "skilld-crawl", Date.now().toString());
614
461
  onProgress?.(`Crawling ${url}`);
@@ -664,11 +511,9 @@ async function fetchCrawledDocs(url, onProgress, maxPages = 200) {
664
511
  return docs;
665
512
  }
666
513
  const HTML_LANG_RE = /<html[^>]*\slang=["']([^"']+)["']/i;
667
- /** Extract lang attribute from <html> tag */
668
514
  function extractHtmlLang(html) {
669
515
  return HTML_LANG_RE.exec(html)?.[1]?.toLowerCase();
670
516
  }
671
- /** Common ISO 639-1 locale codes for i18n'd doc sites */
672
517
  const LOCALE_CODES = new Set([
673
518
  "ar",
674
519
  "de",
@@ -691,39 +536,25 @@ const LOCALE_CODES = new Set([
691
536
  "zh-cn",
692
537
  "zh-tw"
693
538
  ]);
694
- /** Check if a URL path segment is a known locale prefix foreign to both English and user's locale */
695
539
  function isForeignPathPrefix(segment, userLang) {
696
540
  if (!segment) return false;
697
541
  const lower = segment.toLowerCase();
698
542
  if (lower === "en" || lower.startsWith(userLang)) return false;
699
543
  return LOCALE_CODES.has(lower);
700
544
  }
701
- /** Detect user's 2-letter language code from env (e.g. 'ja' from LANG=ja_JP.UTF-8) */
702
545
  function getUserLang() {
703
546
  const code = (process.env.LC_ALL || process.env.LANG || process.env.LANGUAGE || "").split(/[_.:-]/)[0]?.toLowerCase() || "";
704
547
  return code.length >= 2 ? code.slice(0, 2) : "en";
705
548
  }
706
- /** Append glob pattern to a docs URL for crawling */
707
549
  function toCrawlPattern(docsUrl) {
708
550
  return `${docsUrl.replace(/\/+$/, "")}/**`;
709
551
  }
710
- //#endregion
711
- //#region src/sources/issues.ts
712
- /**
713
- * GitHub issues fetching via gh CLI Search API
714
- * Freshness-weighted scoring, type quotas, comment quality filtering
715
- * Categorized by labels, noise filtered out, non-technical issues detected
716
- */
717
552
  let _ghAvailable;
718
- /**
719
- * Check if gh CLI is installed and authenticated (cached)
720
- */
721
553
  function isGhAvailable() {
722
554
  if (_ghAvailable !== void 0) return _ghAvailable;
723
555
  const { status } = spawnSync("gh", ["auth", "status"], { stdio: "ignore" });
724
556
  return _ghAvailable = status === 0;
725
557
  }
726
- /** Labels that indicate noise — filter these out entirely */
727
558
  const NOISE_LABELS = new Set([
728
559
  "duplicate",
729
560
  "stale",
@@ -735,7 +566,6 @@ const NOISE_LABELS = new Set([
735
566
  "needs triage",
736
567
  "triage"
737
568
  ]);
738
- /** Labels that indicate feature requests — deprioritize */
739
569
  const FEATURE_LABELS = new Set([
740
570
  "enhancement",
741
571
  "feature",
@@ -771,7 +601,6 @@ const DOCS_LABELS = new Set([
771
601
  "doc",
772
602
  "typo"
773
603
  ]);
774
- /** Cache compiled word-boundary regexes per keyword set */
775
604
  const labelRegexCache = /* @__PURE__ */ new WeakMap();
776
605
  function getLabelRegex(keywords) {
777
606
  let re = labelRegexCache.get(keywords);
@@ -782,18 +611,10 @@ function getLabelRegex(keywords) {
782
611
  }
783
612
  return re;
784
613
  }
785
- /**
786
- * Check if a label matches any keyword from a set using word boundaries.
787
- * Handles emoji-prefixed labels like ":sparkles: feature request" or ":lady_beetle: bug"
788
- * without false positives on substrings (e.g. "debug" should not match "bug").
789
- */
790
614
  function labelMatchesAny(label, keywords) {
791
615
  if (keywords.has(label)) return true;
792
616
  return getLabelRegex(keywords).test(label);
793
617
  }
794
- /**
795
- * Classify an issue by its labels into a type useful for skill generation
796
- */
797
618
  function classifyIssue(labels) {
798
619
  const lower = labels.map((l) => l.toLowerCase());
799
620
  if (lower.some((l) => labelMatchesAny(l, BUG_LABELS))) return "bug";
@@ -802,37 +623,20 @@ function classifyIssue(labels) {
802
623
  if (lower.some((l) => labelMatchesAny(l, FEATURE_LABELS))) return "feature";
803
624
  return "other";
804
625
  }
805
- /**
806
- * Check if an issue should be filtered out entirely
807
- */
808
626
  function isNoiseIssue(issue) {
809
627
  if (issue.labels.map((l) => l.toLowerCase()).some((l) => labelMatchesAny(l, NOISE_LABELS))) return true;
810
628
  if (issue.title.startsWith("☂️") || issue.title.startsWith("[META]") || issue.title.startsWith("[Tracking]")) return true;
811
629
  return false;
812
630
  }
813
- /**
814
- * Detect non-technical issues: fan mail, showcases, sentiment.
815
- * Short body + no code + high reactions = likely non-technical.
816
- * Note: roadmap/tracking issues are NOT filtered — they get score-boosted instead.
817
- */
818
631
  function isNonTechnical(issue) {
819
632
  const body = (issue.body || "").trim();
820
633
  if (body.length < 200 && !hasCodeBlock(body) && issue.reactions > 50) return true;
821
634
  if (/\b(?:love|thank|awesome|great work)\b/i.test(issue.title) && !hasCodeBlock(body)) return true;
822
635
  return false;
823
636
  }
824
- /**
825
- * Freshness-weighted score: reactions * decay(age_in_years)
826
- * Steep decay so recent issues dominate over old high-reaction ones.
827
- * At 0.6: 1yr=0.63x, 2yr=0.45x, 4yr=0.29x, 6yr=0.22x
828
- */
829
637
  function freshnessScore(reactions, createdAt) {
830
638
  return reactions * (1 / (1 + (Date.now() - new Date(createdAt).getTime()) / (365.25 * 24 * 60 * 60 * 1e3) * .6));
831
639
  }
832
- /**
833
- * Type quotas — guarantee a mix of issue types.
834
- * Bugs and questions get priority; feature requests are hard-capped.
835
- */
836
640
  function applyTypeQuotas(issues, limit) {
837
641
  const byType = /* @__PURE__ */ new Map();
838
642
  for (const issue of issues) mapInsert(byType, issue.type, () => []).push(issue);
@@ -866,17 +670,11 @@ function applyTypeQuotas(issues, limit) {
866
670
  }
867
671
  return selected.sort((a, b) => b.score - a.score);
868
672
  }
869
- /**
870
- * Body truncation limit based on reactions — high-reaction issues deserve more space
871
- */
872
673
  function bodyLimit(reactions) {
873
674
  if (reactions >= 10) return 2e3;
874
675
  if (reactions >= 5) return 1500;
875
676
  return 800;
876
677
  }
877
- /**
878
- * Fetch issues for a state using GitHub Search API sorted by reactions
879
- */
880
678
  function fetchIssuesByState(owner, repo, state, count, releasedAt, fromDate) {
881
679
  const fetchCount = Math.min(count * 3, 100);
882
680
  let datePart = "";
@@ -922,12 +720,6 @@ function oneYearAgo() {
922
720
  d.setFullYear(d.getFullYear() - 1);
923
721
  return isoDate(d.toISOString());
924
722
  }
925
- /**
926
- * Batch-fetch top comments for issues via GraphQL.
927
- * Enriches the top N highest-score issues with their best comments.
928
- * Prioritizes: comments with code blocks, from maintainers, with high reactions.
929
- * Filters out "+1", "any updates?", "same here" noise.
930
- */
931
723
  function enrichWithComments(owner, repo, issues, topN = 15) {
932
724
  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);
933
725
  if (worth.length === 0) return;
@@ -975,10 +767,6 @@ function enrichWithComments(owner, repo, issues, topN = 15) {
975
767
  }
976
768
  } catch {}
977
769
  }
978
- /**
979
- * Try to detect which version fixed a closed issue from maintainer comments.
980
- * Looks for version patterns in maintainer/collaborator comments.
981
- */
982
770
  function detectResolvedVersion(comments) {
983
771
  const maintainerComments = comments.filter((c) => c.isMaintainer);
984
772
  for (const c of maintainerComments.reverse()) {
@@ -990,11 +778,6 @@ function detectResolvedVersion(comments) {
990
778
  }
991
779
  }
992
780
  }
993
- /**
994
- * Fetch issues from a GitHub repo with freshness-weighted scoring and type quotas.
995
- * Returns a balanced mix: bugs > questions > docs > other > features.
996
- * Filters noise, non-technical content, and enriches with quality comments.
997
- */
998
781
  async function fetchGitHubIssues(owner, repo, limit = 30, releasedAt, fromDate) {
999
782
  if (!isGhAvailable()) return [];
1000
783
  const openCount = Math.ceil(limit * .75);
@@ -1009,9 +792,6 @@ async function fetchGitHubIssues(owner, repo, limit = 30, releasedAt, fromDate)
1009
792
  return [];
1010
793
  }
1011
794
  }
1012
- /**
1013
- * Format a single issue as markdown with YAML frontmatter
1014
- */
1015
795
  function formatIssueAsMarkdown(issue) {
1016
796
  const limit = bodyLimit(issue.reactions);
1017
797
  const fmFields = {
@@ -1046,10 +826,6 @@ function formatIssueAsMarkdown(issue) {
1046
826
  }
1047
827
  return lines.join("\n");
1048
828
  }
1049
- /**
1050
- * Generate a summary index of all issues for quick LLM scanning.
1051
- * Groups by type so the LLM can quickly find bugs vs questions.
1052
- */
1053
829
  function generateIssueIndex(issues) {
1054
830
  const byType = /* @__PURE__ */ new Map();
1055
831
  for (const issue of issues) mapInsert(byType, issue.type, () => []).push(issue);
@@ -1094,14 +870,6 @@ function generateIssueIndex(issues) {
1094
870
  }
1095
871
  return sections.join("\n");
1096
872
  }
1097
- //#endregion
1098
- //#region src/sources/discussions.ts
1099
- /**
1100
- * GitHub discussions fetching via gh CLI GraphQL
1101
- * Prioritizes Q&A and Help categories, includes accepted answers
1102
- * Comment quality filtering, smart truncation, noise removal
1103
- */
1104
- /** Categories most useful for skill generation (in priority order) */
1105
873
  const HIGH_VALUE_CATEGORIES = new Set([
1106
874
  "q&a",
1107
875
  "help",
@@ -1113,21 +881,11 @@ const LOW_VALUE_CATEGORIES = new Set([
1113
881
  "ideas",
1114
882
  "polls"
1115
883
  ]);
1116
- /** Off-topic or spam title patterns — instant reject */
1117
884
  const TITLE_NOISE_RE = /looking .*(?:developer|engineer|freelanc)|hiring|job post|guide me to (?:complete|finish|build)|help me (?:complete|finish|build)|seeking .* tutorial|recommend.* course/i;
1118
- /** Minimum score for a discussion to be included */
1119
885
  const MIN_DISCUSSION_SCORE = 3;
1120
- /**
1121
- * Score a comment for quality. Higher = more useful for skill generation.
1122
- * Maintainers 3x, code blocks 2x, reactions linear.
1123
- */
1124
886
  function scoreComment(c) {
1125
887
  return (c.isMaintainer ? 3 : 1) * (hasCodeBlock(c.body) ? 2 : 1) * (1 + c.reactions);
1126
888
  }
1127
- /**
1128
- * Score a discussion for overall quality. Used for filtering and sorting.
1129
- * Returns -1 for instant-reject (spam/off-topic).
1130
- */
1131
889
  function scoreDiscussion(d) {
1132
890
  if (TITLE_NOISE_RE.test(d.title)) return -1;
1133
891
  let score = 0;
@@ -1146,11 +904,6 @@ function scoreDiscussion(d) {
1146
904
  if (d.topComments.some((c) => c.reactions > 0)) score += 1;
1147
905
  return score;
1148
906
  }
1149
- /**
1150
- * Fetch discussions from a GitHub repo using gh CLI GraphQL.
1151
- * Prioritizes Q&A and Help categories. Includes accepted answer body for answered discussions.
1152
- * Fetches extra comments and scores them for quality.
1153
- */
1154
907
  async function fetchGitHubDiscussions(owner, repo, limit = 20, releasedAt, fromDate) {
1155
908
  if (!isGhAvailable()) return [];
1156
909
  if (!fromDate && releasedAt) {
@@ -1233,9 +986,6 @@ async function fetchGitHubDiscussions(owner, repo, limit = 20, releasedAt, fromD
1233
986
  return [];
1234
987
  }
1235
988
  }
1236
- /**
1237
- * Format a single discussion as markdown with YAML frontmatter
1238
- */
1239
989
  function formatDiscussionAsMarkdown(d) {
1240
990
  const fm = buildFrontmatter({
1241
991
  number: d.number,
@@ -1265,10 +1015,6 @@ function formatDiscussionAsMarkdown(d) {
1265
1015
  }
1266
1016
  return lines.join("\n");
1267
1017
  }
1268
- /**
1269
- * Generate a summary index of all discussions for quick LLM scanning.
1270
- * Groups by category so the LLM can quickly find Q&A vs general discussions.
1271
- */
1272
1018
  function generateDiscussionIndex(discussions) {
1273
1019
  const byCategory = /* @__PURE__ */ new Map();
1274
1020
  for (const d of discussions) mapInsert(byCategory, d.category || "Uncategorized", () => []).push(d);
@@ -1300,16 +1046,6 @@ function generateDiscussionIndex(discussions) {
1300
1046
  }
1301
1047
  return sections.join("\n");
1302
1048
  }
1303
- //#endregion
1304
- //#region src/sources/docs.ts
1305
- /**
1306
- * Docs index generation — creates _INDEX.md for docs directory
1307
- */
1308
- /**
1309
- * Generate a _INDEX.md for a docs/ directory.
1310
- * Input: array of cached docs with paths like `docs/api/reactivity.md`.
1311
- * Output: markdown index grouped by directory with title + description per page.
1312
- */
1313
1049
  function generateDocsIndex(docs) {
1314
1050
  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));
1315
1051
  if (docFiles.length === 0) return "";
@@ -1354,12 +1090,6 @@ function generateDocsIndex(docs) {
1354
1090
  }
1355
1091
  return sections.join("\n");
1356
1092
  }
1357
- //#endregion
1358
- //#region src/sources/entries.ts
1359
- /**
1360
- * Globs .d.ts type definition files from a package for search indexing.
1361
- * Only types — source code is too verbose.
1362
- */
1363
1093
  const SKIP_DIRS = [
1364
1094
  "node_modules",
1365
1095
  "_vendor",
@@ -1387,9 +1117,6 @@ const SKIP_PATTERNS = [
1387
1117
  "README*"
1388
1118
  ];
1389
1119
  const MAX_FILE_SIZE = 500 * 1024;
1390
- /**
1391
- * Glob .d.ts type definition files from a package directory, skipping junk.
1392
- */
1393
1120
  async function resolveEntryFiles(packageDir) {
1394
1121
  if (!existsSync(join(packageDir, "package.json"))) return [];
1395
1122
  const files = await glob(["**/*.d.{ts,mts,cts}"], {
@@ -1416,18 +1143,6 @@ async function resolveEntryFiles(packageDir) {
1416
1143
  }
1417
1144
  return entries;
1418
1145
  }
1419
- //#endregion
1420
- //#region src/sources/git-skills.ts
1421
- /**
1422
- * Git repo skill source — parse inputs + fetch pre-authored skills from repos
1423
- *
1424
- * Supports GitHub shorthand (owner/repo), full URLs, SSH, GitLab, and local paths.
1425
- * Skills are pre-authored SKILL.md files — no doc resolution or LLM generation needed.
1426
- */
1427
- /**
1428
- * Detect whether an input string is a git skill source.
1429
- * Returns null for npm package names (including scoped @scope/pkg).
1430
- */
1431
1146
  function parseGitSkillInput(input) {
1432
1147
  const trimmed = input.trim();
1433
1148
  if (trimmed.startsWith("@")) return null;
@@ -1489,9 +1204,6 @@ function parseGitUrl(url) {
1489
1204
  return null;
1490
1205
  }
1491
1206
  }
1492
- /**
1493
- * Parse name and description from SKILL.md frontmatter.
1494
- */
1495
1207
  function parseSkillFrontmatterName(content) {
1496
1208
  const fm = parseFrontmatter(content);
1497
1209
  return {
@@ -1499,7 +1211,6 @@ function parseSkillFrontmatterName(content) {
1499
1211
  description: fm.description
1500
1212
  };
1501
1213
  }
1502
- /** Recursively collect all files in a directory, returning relative paths */
1503
1214
  function collectFiles(dir, prefix = "") {
1504
1215
  const files = [];
1505
1216
  if (!existsSync(dir)) return files;
@@ -1514,9 +1225,6 @@ function collectFiles(dir, prefix = "") {
1514
1225
  }
1515
1226
  return files;
1516
1227
  }
1517
- /**
1518
- * Fetch skills from a git source. Returns list of discovered skills.
1519
- */
1520
1228
  async function fetchGitSkills(source, onProgress) {
1521
1229
  if (source.type === "local") return fetchLocalSkills(source);
1522
1230
  if (source.type === "github") return fetchGitHubSkills(source, onProgress);
@@ -1666,19 +1374,11 @@ async function fetchGitLabSkills(source, onProgress) {
1666
1374
  });
1667
1375
  }
1668
1376
  }
1669
- //#endregion
1670
- //#region src/sources/llms.ts
1671
- /**
1672
- * Check for llms.txt at a docs URL, returns the llms.txt URL if found
1673
- */
1674
1377
  async function fetchLlmsUrl(docsUrl) {
1675
1378
  const llmsUrl = `${new URL(docsUrl).origin}/llms.txt`;
1676
1379
  if (await verifyUrl(llmsUrl)) return llmsUrl;
1677
1380
  return null;
1678
1381
  }
1679
- /**
1680
- * Fetch and parse llms.txt content
1681
- */
1682
1382
  async function fetchLlmsTxt(url) {
1683
1383
  const content = await fetchText(url);
1684
1384
  if (!content || content.length < 50) return null;
@@ -1687,16 +1387,9 @@ async function fetchLlmsTxt(url) {
1687
1387
  links: parseMarkdownLinks(content)
1688
1388
  };
1689
1389
  }
1690
- /**
1691
- * Parse markdown links from llms.txt to get .md file paths
1692
- */
1693
1390
  function parseMarkdownLinks(content) {
1694
1391
  return extractLinks(content).filter((l) => l.url.endsWith(".md"));
1695
1392
  }
1696
- /**
1697
- * Download all .md files referenced in llms.txt
1698
- */
1699
- /** Reject non-https URLs and private/link-local IPs */
1700
1393
  function isSafeUrl(url) {
1701
1394
  try {
1702
1395
  const parsed = new URL(url);
@@ -1726,10 +1419,6 @@ async function downloadLlmsDocs(llmsContent, baseUrl, onProgress) {
1726
1419
  return null;
1727
1420
  })))).filter((d) => d !== null);
1728
1421
  }
1729
- /**
1730
- * Normalize llms.txt links to relative paths for local access
1731
- * Handles: absolute URLs, root-relative paths, and relative paths
1732
- */
1733
1422
  function normalizeLlmsLinks(content, baseUrl) {
1734
1423
  let normalized = content;
1735
1424
  if (baseUrl) {
@@ -1739,10 +1428,6 @@ function normalizeLlmsLinks(content, baseUrl) {
1739
1428
  normalized = normalized.replace(/\]\(\/([^)]+\.md)\)/g, "](./docs/$1)");
1740
1429
  return normalized;
1741
1430
  }
1742
- /**
1743
- * Extract sections from llms-full.txt by URL patterns
1744
- * Format: ---\nurl: /path.md\n---\n<content>\n\n---\nurl: ...
1745
- */
1746
1431
  function extractSections(content, patterns) {
1747
1432
  const sections = [];
1748
1433
  const parts = content.split(/\n---\n/);
@@ -1758,16 +1443,8 @@ function extractSections(content, patterns) {
1758
1443
  if (sections.length === 0) return null;
1759
1444
  return sections.join("\n\n---\n\n");
1760
1445
  }
1761
- //#endregion
1762
- //#region src/sources/github.ts
1763
- /** Minimum git-doc file count to prefer over llms.txt */
1764
1446
  const MIN_GIT_DOCS = 5;
1765
- /** True when git-docs exist but are too few to be useful (< MIN_GIT_DOCS) */
1766
1447
  const isShallowGitDocs = (n) => n > 0 && n < 5;
1767
- /**
1768
- * List files at a git ref. Tries ungh.cc first (fast, no rate limits),
1769
- * falls back to GitHub API for private repos.
1770
- */
1771
1448
  async function listFilesAtRef(owner, repo, ref) {
1772
1449
  if (!isKnownPrivateRepo(owner, repo)) {
1773
1450
  const data = await $fetch(`https://ungh.cc/repos/${owner}/${repo}/files/${ref}`).catch(() => null);
@@ -1780,10 +1457,6 @@ async function listFilesAtRef(owner, repo, ref) {
1780
1457
  }
1781
1458
  return [];
1782
1459
  }
1783
- /**
1784
- * Find git tag for a version by checking if ungh can list files at that ref.
1785
- * Tries v{version}, {version}, and optionally {packageName}@{version} (changeset convention).
1786
- */
1787
1460
  async function findGitTag(owner, repo, version, packageName, branchHint) {
1788
1461
  const candidates = [`v${version}`, version];
1789
1462
  if (packageName) candidates.push(`${packageName}@${version}`);
@@ -1815,9 +1488,6 @@ async function findGitTag(owner, repo, version, packageName, branchHint) {
1815
1488
  }
1816
1489
  return null;
1817
1490
  }
1818
- /**
1819
- * Fetch releases from ungh.cc first, fall back to GitHub API for private repos.
1820
- */
1821
1491
  async function fetchUnghReleases(owner, repo) {
1822
1492
  if (!isKnownPrivateRepo(owner, repo)) {
1823
1493
  const data = await $fetch(`https://ungh.cc/repos/${owner}/${repo}/releases`).catch(() => null);
@@ -1833,16 +1503,10 @@ async function fetchUnghReleases(owner, repo) {
1833
1503
  }
1834
1504
  return [];
1835
1505
  }
1836
- /**
1837
- * Find the latest release tag matching `{packageName}@*`.
1838
- */
1839
1506
  async function findLatestReleaseTag(owner, repo, packageName) {
1840
1507
  const prefix = `${packageName}@`;
1841
1508
  return (await fetchUnghReleases(owner, repo)).find((r) => r.tag.startsWith(prefix))?.tag ?? null;
1842
1509
  }
1843
- /**
1844
- * Filter file paths by prefix and md/mdx extension
1845
- */
1846
1510
  function filterDocFiles(files, pathPrefix) {
1847
1511
  return files.filter((f) => f.startsWith(pathPrefix) && /\.(?:md|mdx)$/.test(f));
1848
1512
  }
@@ -1856,12 +1520,6 @@ const FRAMEWORK_NAMES = new Set([
1856
1520
  "lit",
1857
1521
  "qwik"
1858
1522
  ]);
1859
- /**
1860
- * Filter out docs for other frameworks when the package targets a specific one.
1861
- * e.g. @tanstack/vue-query → keep vue + shared docs, exclude react/solid/angular
1862
- * Uses word-boundary matching to catch all path conventions:
1863
- * framework/react/, 0.react/, api/ai-react.md, react-native.mdx, etc.
1864
- */
1865
1523
  function filterFrameworkDocs(files, packageName) {
1866
1524
  if (!packageName) return files;
1867
1525
  const shortName = packageName.replace(/^@.*\//, "");
@@ -1871,14 +1529,12 @@ function filterFrameworkDocs(files, packageName) {
1871
1529
  const excludePattern = new RegExp(`\\b(?:${otherFrameworks.join("|")})\\b`);
1872
1530
  return files.filter((f) => !excludePattern.test(f));
1873
1531
  }
1874
- /** Known noise paths to exclude from doc discovery */
1875
1532
  const NOISE_PATTERNS = [
1876
1533
  /^\.changeset\//,
1877
1534
  /CHANGELOG\.md$/i,
1878
1535
  /CONTRIBUTING\.md$/i,
1879
1536
  /^\.github\//
1880
1537
  ];
1881
- /** Directories to exclude from "best directory" heuristic */
1882
1538
  const EXCLUDE_DIRS = new Set([
1883
1539
  "test",
1884
1540
  "tests",
@@ -1897,7 +1553,6 @@ const EXCLUDE_DIRS = new Set([
1897
1553
  "mocks",
1898
1554
  "__mocks__"
1899
1555
  ]);
1900
- /** Directory names that suggest documentation */
1901
1556
  const DOC_DIR_BONUS = new Set([
1902
1557
  "docs",
1903
1558
  "documentation",
@@ -1910,38 +1565,19 @@ const DOC_DIR_BONUS = new Set([
1910
1565
  "manual",
1911
1566
  "api"
1912
1567
  ]);
1913
- /**
1914
- * Check if a path contains any excluded directory
1915
- */
1916
1568
  function hasExcludedDir(path) {
1917
1569
  return path.split("/").some((p) => EXCLUDE_DIRS.has(p.toLowerCase()));
1918
1570
  }
1919
- /**
1920
- * Get the depth of a path (number of directory levels)
1921
- */
1922
1571
  function getPathDepth(path) {
1923
1572
  return path.split("/").filter(Boolean).length;
1924
1573
  }
1925
- /**
1926
- * Check if path contains a doc-related directory name
1927
- */
1928
1574
  function hasDocDirBonus(path) {
1929
1575
  return path.split("/").some((p) => DOC_DIR_BONUS.has(p.toLowerCase()));
1930
1576
  }
1931
- /**
1932
- * Score a directory for doc likelihood.
1933
- * Higher = better. Formula: count * nameBonus / depth
1934
- */
1935
1577
  function scoreDocDir(dir, fileCount) {
1936
1578
  const depth = getPathDepth(dir) || 1;
1937
1579
  return fileCount * (hasDocDirBonus(dir) ? 1.5 : 1) / depth;
1938
1580
  }
1939
- /**
1940
- * Discover doc files in non-standard locations.
1941
- * First tries to scope to sub-package dir in monorepos.
1942
- * Then looks for clusters of md/mdx files in paths containing /docs/.
1943
- * Falls back to finding the directory with the most markdown files (≥5).
1944
- */
1945
1581
  function discoverDocFiles(allFiles, packageName) {
1946
1582
  const mdFiles = allFiles.filter((f) => /\.(?:md|mdx)$/.test(f)).filter((f) => !NOISE_PATTERNS.some((p) => p.test(f))).filter((f) => f.includes("/"));
1947
1583
  if (packageName?.includes("/")) {
@@ -1990,16 +1626,9 @@ function discoverDocFiles(allFiles, packageName) {
1990
1626
  prefix: best.dir
1991
1627
  };
1992
1628
  }
1993
- /**
1994
- * List markdown files in a folder at a specific git ref
1995
- */
1996
1629
  async function listDocsAtRef(owner, repo, ref, pathPrefix = "docs/") {
1997
1630
  return filterDocFiles(await listFilesAtRef(owner, repo, ref), pathPrefix);
1998
1631
  }
1999
- /**
2000
- * Fetch versioned docs from GitHub repo's docs/ folder.
2001
- * Pass packageName to check doc overrides (e.g. vue -> vuejs/docs).
2002
- */
2003
1632
  async function fetchGitDocs(owner, repo, version, packageName, repoUrl) {
2004
1633
  const override = packageName ? getDocOverride(packageName) : void 0;
2005
1634
  if (override) {
@@ -2039,18 +1668,9 @@ async function fetchGitDocs(owner, repo, version, packageName, repoUrl) {
2039
1668
  fallback: tag.fallback
2040
1669
  };
2041
1670
  }
2042
- /**
2043
- * Strip file extension (.md, .mdx) and leading slash from a path
2044
- */
2045
1671
  function normalizePath(p) {
2046
1672
  return p.replace(/^\//, "").replace(/\.(?:md|mdx)$/, "");
2047
1673
  }
2048
- /**
2049
- * Validate that discovered git docs are relevant by cross-referencing llms.txt links
2050
- * against the repo file tree. Uses extensionless suffix matching to handle monorepo nesting.
2051
- *
2052
- * Returns { isValid, matchRatio } where isValid = matchRatio >= 0.3
2053
- */
2054
1674
  function validateGitDocsWithLlms(llmsLinks, repoFiles) {
2055
1675
  if (llmsLinks.length === 0) return {
2056
1676
  isValid: true,
@@ -2076,10 +1696,6 @@ function validateGitDocsWithLlms(llmsLinks, repoFiles) {
2076
1696
  matchRatio
2077
1697
  };
2078
1698
  }
2079
- /**
2080
- * Verify a GitHub repo is the source for an npm package by checking package.json name field.
2081
- * Checks root first, then common monorepo paths (packages/{shortName}, packages/{name}).
2082
- */
2083
1699
  async function verifyNpmRepo(owner, repo, packageName) {
2084
1700
  const base = `https://raw.githubusercontent.com/${owner}/${repo}/HEAD`;
2085
1701
  const paths = [
@@ -2138,19 +1754,12 @@ async function searchGitHubRepo(packageName) {
2138
1754
  }
2139
1755
  return null;
2140
1756
  }
2141
- /**
2142
- * Fetch GitHub repo metadata to get website URL.
2143
- * Pass packageName to check doc overrides first (avoids API call).
2144
- */
2145
1757
  async function fetchGitHubRepoMeta(owner, repo, packageName) {
2146
1758
  const override = packageName ? getDocOverride(packageName) : void 0;
2147
1759
  if (override?.homepage) return { homepage: override.homepage };
2148
1760
  const data = await ghApi(`repos/${owner}/${repo}`) ?? await $fetch(`https://api.github.com/repos/${owner}/${repo}`).catch(() => null);
2149
1761
  return data?.homepage ? { homepage: data.homepage } : null;
2150
1762
  }
2151
- /**
2152
- * Resolve README URL for a GitHub repo, returns ungh:// pseudo-URL or raw URL
2153
- */
2154
1763
  async function fetchReadme(owner, repo, subdir, ref) {
2155
1764
  const branch = ref || "main";
2156
1765
  if (!isKnownPrivateRepo(owner, repo)) {
@@ -2177,9 +1786,6 @@ async function fetchReadme(owner, repo, subdir, ref) {
2177
1786
  }
2178
1787
  return null;
2179
1788
  }
2180
- /**
2181
- * Fetch README content from ungh:// pseudo-URL, file:// URL, or regular URL
2182
- */
2183
1789
  async function fetchReadmeContent(url) {
2184
1790
  if (url.startsWith("file://")) {
2185
1791
  const filePath = fileURLToPath(url);
@@ -2210,10 +1816,6 @@ async function fetchReadmeContent(url) {
2210
1816
  if (url.includes("raw.githubusercontent.com")) return fetchGitHubRaw(url);
2211
1817
  return fetchText(url);
2212
1818
  }
2213
- /**
2214
- * Resolve a GitHub repo into a ResolvedPackage (no npm registry needed).
2215
- * Fetches repo meta, latest release version, git docs, README, and llms.txt.
2216
- */
2217
1819
  async function resolveGitHubRepo(owner, repo, onProgress) {
2218
1820
  onProgress?.("Fetching repo metadata");
2219
1821
  const repoUrl = `https://github.com/${owner}/${repo}`;
@@ -2255,12 +1857,6 @@ async function resolveGitHubRepo(owner, repo, onProgress) {
2255
1857
  llmsUrl
2256
1858
  };
2257
1859
  }
2258
- //#endregion
2259
- //#region src/sources/npm.ts
2260
- /**
2261
- * Search npm registry for packages matching a query.
2262
- * Used as a fallback when direct package lookup fails.
2263
- */
2264
1860
  async function searchNpmPackages(query, size = 5) {
2265
1861
  const data = await $fetch(`https://registry.npmjs.org/-/v1/search?text=${encodeURIComponent(query)}&size=${size}`).catch(() => null);
2266
1862
  if (!data?.objects?.length) return [];
@@ -2270,17 +1866,11 @@ async function searchNpmPackages(query, size = 5) {
2270
1866
  version: o.package.version
2271
1867
  }));
2272
1868
  }
2273
- /**
2274
- * Fetch package info from npm registry
2275
- */
2276
1869
  async function fetchNpmPackage(packageName) {
2277
1870
  const data = await $fetch(`https://unpkg.com/${packageName}/package.json`).catch(() => null);
2278
1871
  if (data) return data;
2279
1872
  return $fetch(`https://registry.npmjs.org/${packageName}/latest`).catch(() => null);
2280
1873
  }
2281
- /**
2282
- * Fetch release date and dist-tags from npm registry
2283
- */
2284
1874
  async function fetchNpmRegistryMeta(packageName, version) {
2285
1875
  const { name: barePackageName } = parsePackageSpec(packageName);
2286
1876
  const data = await $fetch(`https://registry.npmjs.org/${barePackageName}`, { headers: { Accept: "application/vnd.npm.install-v1+json" } }).catch(() => null);
@@ -2294,10 +1884,6 @@ async function fetchNpmRegistryMeta(packageName, version) {
2294
1884
  distTags
2295
1885
  };
2296
1886
  }
2297
- /**
2298
- * Shared GitHub resolution cascade: git docs → repo meta (homepage) → README.
2299
- * Used for both "repo URL found in package.json" and "repo URL found via search" paths.
2300
- */
2301
1887
  async function resolveGitHub(gh, targetVersion, pkg, result, attempts, onProgress, opts) {
2302
1888
  let allFiles;
2303
1889
  if (targetVersion) {
@@ -2356,15 +1942,9 @@ async function resolveGitHub(gh, targetVersion, pkg, result, attempts, onProgres
2356
1942
  });
2357
1943
  return allFiles;
2358
1944
  }
2359
- /**
2360
- * Resolve documentation URL for a package (legacy - returns null on failure)
2361
- */
2362
1945
  async function resolvePackageDocs(packageName, options = {}) {
2363
1946
  return (await resolvePackageDocsWithAttempts(packageName, options)).package;
2364
1947
  }
2365
- /**
2366
- * Resolve documentation URL for a package with attempt tracking
2367
- */
2368
1948
  async function resolvePackageDocsWithAttempts(packageName, options = {}) {
2369
1949
  const attempts = [];
2370
1950
  const { onProgress } = options;
@@ -2500,9 +2080,6 @@ async function resolvePackageDocsWithAttempts(packageName, options = {}) {
2500
2080
  registryVersion: pkg.version
2501
2081
  };
2502
2082
  }
2503
- /**
2504
- * Parse version specifier, handling protocols like link:, workspace:, npm:, file:
2505
- */
2506
2083
  function parseVersionSpecifier(name, version, cwd) {
2507
2084
  if (version.startsWith("link:")) {
2508
2085
  const linkedPkg = readPackageJsonSafe(join(resolve(cwd, version.slice(5)), "package.json"));
@@ -2537,10 +2114,6 @@ function parseVersionSpecifier(name, version, cwd) {
2537
2114
  };
2538
2115
  return null;
2539
2116
  }
2540
- /**
2541
- * Resolve the actual installed version of a package by finding its package.json
2542
- * via mlly's resolvePathSync. Works regardless of package manager or version protocol.
2543
- */
2544
2117
  function resolveInstalledVersion(name, cwd) {
2545
2118
  try {
2546
2119
  return readPackageJsonSafe(resolvePathSync(`${name}/package.json`, { url: cwd }))?.parsed.version || null;
@@ -2558,9 +2131,6 @@ function resolveInstalledVersion(name, cwd) {
2558
2131
  return null;
2559
2132
  }
2560
2133
  }
2561
- /**
2562
- * Read package.json dependencies with versions
2563
- */
2564
2134
  async function readLocalDependencies(cwd) {
2565
2135
  const result = readPackageJsonSafe(join(cwd, "package.json"));
2566
2136
  if (!result) throw new Error("No package.json found in current directory");
@@ -2576,9 +2146,6 @@ async function readLocalDependencies(cwd) {
2576
2146
  }
2577
2147
  return results;
2578
2148
  }
2579
- /**
2580
- * Read package info from a local path (for link: deps)
2581
- */
2582
2149
  function readLocalPackageInfo(localPath) {
2583
2150
  const result = readPackageJsonSafe(join(localPath, "package.json"));
2584
2151
  if (!result) return null;
@@ -2594,9 +2161,6 @@ function readLocalPackageInfo(localPath) {
2594
2161
  localPath
2595
2162
  };
2596
2163
  }
2597
- /**
2598
- * Resolve docs for a local package (link: dependency)
2599
- */
2600
2164
  async function resolveLocalPackageDocs(localPath) {
2601
2165
  const info = readLocalPackageInfo(localPath);
2602
2166
  if (!info) return null;
@@ -2626,13 +2190,6 @@ async function resolveLocalPackageDocs(localPath) {
2626
2190
  if (!result.readmeUrl && !result.gitDocsUrl) return null;
2627
2191
  return result;
2628
2192
  }
2629
- /**
2630
- * Download and extract npm package tarball to cache directory.
2631
- * Used when the package isn't available in node_modules.
2632
- *
2633
- * Extracts to: ~/.skilld/references/<pkg>@<version>/pkg/
2634
- * Returns the extracted directory path, or null on failure.
2635
- */
2636
2193
  async function fetchPkgDist(name, version) {
2637
2194
  const cacheDir = getCacheDir(name, version);
2638
2195
  const pkgDir = join(cacheDir, "pkg");
@@ -2700,23 +2257,16 @@ async function fetchPkgDist(name, version) {
2700
2257
  } catch {}
2701
2258
  }
2702
2259
  }
2703
- /**
2704
- * Fetch just the latest version string from npm (lightweight)
2705
- */
2706
2260
  async function fetchLatestVersion(packageName) {
2707
2261
  const data = await $fetch(`https://unpkg.com/${packageName}/package.json`).catch(() => null);
2708
2262
  if (data?.version) return data.version;
2709
2263
  return (await $fetch(`https://registry.npmjs.org/${packageName}`, { headers: { Accept: "application/vnd.npm.install-v1+json" } }).catch(() => null))?.["dist-tags"]?.latest || null;
2710
2264
  }
2711
- /**
2712
- * Get installed skill version from SKILL.md
2713
- */
2714
2265
  function getInstalledSkillVersion(skillDir) {
2715
2266
  const skillPath = join(skillDir, "SKILL.md");
2716
2267
  if (!existsSync(skillPath)) return null;
2717
2268
  return readFileSync(skillPath, "utf-8").match(/^version:\s*"?([^"\n]+)"?/m)?.[1] || null;
2718
2269
  }
2719
- //#endregion
2720
2270
  export { isGitHubRepoUrl as $, parseGitSkillInput as A, isGhAvailable as B, downloadLlmsDocs as C, normalizeLlmsLinks as D, fetchLlmsUrl as E, formatDiscussionAsMarkdown as F, fetchReleaseNotes as G, toCrawlPattern as H, generateDiscussionIndex as I, parseSemver as J, generateReleaseIndex as K, fetchGitHubIssues as L, resolveEntryFiles as M, generateDocsIndex as N, parseMarkdownLinks as O, fetchGitHubDiscussions as P, fetchText as Q, formatIssueAsMarkdown as R, validateGitDocsWithLlms as S, fetchLlmsTxt as T, fetchBlogReleases as U, fetchCrawledDocs as V, compareSemver as W, extractBranchHint as X, $fetch as Y, fetchGitHubRaw as Z, fetchReadme as _, getInstalledSkillVersion as a, isShallowGitDocs as b, readLocalPackageInfo as c, resolvePackageDocs as d, normalizeRepoUrl as et, resolvePackageDocsWithAttempts as f, fetchGitHubRepoMeta as g, fetchGitDocs as h, fetchPkgDist as i, parseSkillFrontmatterName as j, fetchGitSkills as k, resolveInstalledVersion as l, MIN_GIT_DOCS as m, fetchNpmPackage as n, parsePackageSpec as nt, parseVersionSpecifier as o, searchNpmPackages as p, isPrerelease as q, fetchNpmRegistryMeta as r, verifyUrl as rt, readLocalDependencies as s, fetchLatestVersion as t, parseGitHubUrl as tt, resolveLocalPackageDocs as u, fetchReadmeContent as v, extractSections as w, resolveGitHubRepo as x, filterFrameworkDocs as y, generateIssueIndex as z };
2721
2271
 
2722
2272
  //# sourceMappingURL=sources.mjs.map