skilld 1.7.4 → 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.
- package/dist/_chunks/add.mjs +66 -0
- package/dist/_chunks/add.mjs.map +1 -0
- package/dist/_chunks/agent-prompt.mjs +88 -0
- package/dist/_chunks/agent-prompt.mjs.map +1 -0
- package/dist/_chunks/agent.mjs +81 -57
- package/dist/_chunks/agent.mjs.map +1 -1
- package/dist/_chunks/args.mjs +42 -0
- package/dist/_chunks/args.mjs.map +1 -0
- package/dist/_chunks/assemble.mjs +10 -7
- package/dist/_chunks/assemble.mjs.map +1 -1
- package/dist/_chunks/author.mjs +33 -17
- package/dist/_chunks/author.mjs.map +1 -1
- package/dist/_chunks/cache.mjs +143 -183
- package/dist/_chunks/cache.mjs.map +1 -1
- package/dist/_chunks/cache2.mjs +7 -6
- package/dist/_chunks/cache2.mjs.map +1 -1
- package/dist/_chunks/client.mjs +117 -0
- package/dist/_chunks/client.mjs.map +1 -0
- package/dist/_chunks/core.mjs +5 -5
- package/dist/_chunks/detect.mjs +53 -43
- package/dist/_chunks/detect.mjs.map +1 -1
- package/dist/_chunks/eject.mjs +69 -0
- package/dist/_chunks/eject.mjs.map +1 -0
- package/dist/_chunks/embedding-cache2.mjs +1 -1
- package/dist/_chunks/env.mjs +19 -0
- package/dist/_chunks/env.mjs.map +1 -0
- package/dist/_chunks/install-many.mjs +376 -0
- package/dist/_chunks/install-many.mjs.map +1 -0
- package/dist/_chunks/install.mjs +81 -326
- package/dist/_chunks/install.mjs.map +1 -1
- package/dist/_chunks/intro.mjs +63 -0
- package/dist/_chunks/intro.mjs.map +1 -0
- package/dist/_chunks/list.mjs +2 -2
- package/dist/_chunks/list.mjs.map +1 -1
- package/dist/_chunks/lockfile.mjs +3 -2
- package/dist/_chunks/lockfile.mjs.map +1 -1
- package/dist/_chunks/login.mjs +233 -0
- package/dist/_chunks/login.mjs.map +1 -0
- package/dist/_chunks/logout.mjs +27 -0
- package/dist/_chunks/logout.mjs.map +1 -0
- package/dist/_chunks/map.mjs +11 -0
- package/dist/_chunks/map.mjs.map +1 -0
- package/dist/_chunks/markdown.mjs +79 -54
- package/dist/_chunks/markdown.mjs.map +1 -1
- package/dist/_chunks/menu.mjs +33 -0
- package/dist/_chunks/menu.mjs.map +1 -0
- package/dist/_chunks/model-picker.mjs +61 -0
- package/dist/_chunks/model-picker.mjs.map +1 -0
- package/dist/_chunks/monorepo.mjs +4 -2
- package/dist/_chunks/monorepo.mjs.map +1 -1
- package/dist/_chunks/package-json.mjs.map +1 -1
- package/dist/_chunks/paths.mjs +3 -5
- package/dist/_chunks/paths.mjs.map +1 -1
- package/dist/_chunks/{sync-pipeline.mjs → pipeline.mjs} +346 -313
- package/dist/_chunks/pipeline.mjs.map +1 -0
- package/dist/_chunks/pool2.mjs +1 -1
- package/dist/_chunks/portable.mjs +151 -0
- package/dist/_chunks/portable.mjs.map +1 -0
- package/dist/_chunks/prepare-hook.mjs +2 -0
- package/dist/_chunks/prepare-hook2.mjs +61 -0
- package/dist/_chunks/prepare-hook2.mjs.map +1 -0
- package/dist/_chunks/prepare.mjs +47 -3
- package/dist/_chunks/prepare.mjs.map +1 -1
- package/dist/_chunks/prepare2.mjs +7 -6
- package/dist/_chunks/prepare2.mjs.map +1 -1
- package/dist/_chunks/prompts.mjs +484 -74
- package/dist/_chunks/prompts.mjs.map +1 -1
- package/dist/_chunks/pull.mjs +219 -0
- package/dist/_chunks/pull.mjs.map +1 -0
- package/dist/_chunks/regex.mjs +19 -0
- package/dist/_chunks/regex.mjs.map +1 -0
- package/dist/_chunks/retriv.mjs +2 -171
- package/dist/_chunks/retriv2.mjs +159 -0
- package/dist/_chunks/retriv2.mjs.map +1 -0
- package/dist/_chunks/sanitize.mjs +12 -9
- package/dist/_chunks/sanitize.mjs.map +1 -1
- package/dist/_chunks/search-helpers.mjs +8 -6
- package/dist/_chunks/search-helpers.mjs.map +1 -1
- package/dist/_chunks/search-interactive.mjs +23 -20
- package/dist/_chunks/search-interactive.mjs.map +1 -1
- package/dist/_chunks/search.mjs +3 -3
- package/dist/_chunks/search.mjs.map +1 -1
- package/dist/_chunks/semver.mjs +2755 -1
- package/dist/_chunks/semver.mjs.map +1 -1
- package/dist/_chunks/skill-installer2.mjs +10 -11
- package/dist/_chunks/skill-installer2.mjs.map +1 -1
- package/dist/_chunks/skills.mjs +6 -7
- package/dist/_chunks/skills.mjs.map +1 -1
- package/dist/_chunks/store.mjs +107 -0
- package/dist/_chunks/store.mjs.map +1 -0
- package/dist/_chunks/sync.mjs +411 -910
- package/dist/_chunks/sync.mjs.map +1 -1
- package/dist/_chunks/sync2.mjs +2 -5
- package/dist/_chunks/telemetry.mjs +26 -0
- package/dist/_chunks/telemetry.mjs.map +1 -0
- package/dist/_chunks/uninstall.mjs +12 -9
- package/dist/_chunks/uninstall.mjs.map +1 -1
- package/dist/_chunks/update.mjs +171 -0
- package/dist/_chunks/update.mjs.map +1 -0
- package/dist/_chunks/upload.mjs +3 -3
- package/dist/_chunks/validate.mjs +1 -1
- package/dist/_chunks/version.mjs +16 -17
- package/dist/_chunks/version.mjs.map +1 -1
- package/dist/_chunks/whoami.mjs +21 -0
- package/dist/_chunks/whoami.mjs.map +1 -0
- package/dist/_chunks/wizard.mjs +2 -190
- package/dist/_chunks/wizard2.mjs +200 -0
- package/dist/_chunks/wizard2.mjs.map +1 -0
- package/dist/cli.mjs +72 -53
- package/dist/cli.mjs.map +1 -1
- package/dist/prepare.mjs +4 -3
- package/dist/prepare.mjs.map +1 -1
- package/dist/retriv/worker.d.mts +5 -1
- package/dist/retriv/worker.d.mts.map +1 -1
- package/dist/retriv/worker.mjs +1 -1
- package/package.json +19 -28
- package/dist/_chunks/author-group.mjs +0 -17
- package/dist/_chunks/author-group.mjs.map +0 -1
- package/dist/_chunks/cli-helpers.mjs +0 -335
- package/dist/_chunks/cli-helpers.mjs.map +0 -1
- package/dist/_chunks/cli-helpers2.mjs +0 -2
- package/dist/_chunks/index.d.mts +0 -344
- package/dist/_chunks/index.d.mts.map +0 -1
- package/dist/_chunks/index2.d.mts +0 -279
- package/dist/_chunks/index2.d.mts.map +0 -1
- package/dist/_chunks/index3.d.mts +0 -44
- package/dist/_chunks/index3.d.mts.map +0 -1
- package/dist/_chunks/index4.d.mts +0 -553
- package/dist/_chunks/index4.d.mts.map +0 -1
- package/dist/_chunks/package-registry.mjs +0 -465
- package/dist/_chunks/package-registry.mjs.map +0 -1
- package/dist/_chunks/retriv.mjs.map +0 -1
- package/dist/_chunks/setup.mjs +0 -17
- package/dist/_chunks/setup.mjs.map +0 -1
- package/dist/_chunks/sources.mjs +0 -2654
- package/dist/_chunks/sources.mjs.map +0 -1
- package/dist/_chunks/sync-pipeline.mjs.map +0 -1
- package/dist/_chunks/sync-registry.mjs +0 -65
- package/dist/_chunks/sync-registry.mjs.map +0 -1
- package/dist/_chunks/types.d.mts +0 -76
- package/dist/_chunks/types.d.mts.map +0 -1
- package/dist/_chunks/types2.d.mts +0 -88
- package/dist/_chunks/types2.d.mts.map +0 -1
- package/dist/_chunks/wizard.mjs.map +0 -1
- package/dist/agent/index.d.mts +0 -2
- package/dist/agent/index.mjs +0 -4
- package/dist/cache/index.d.mts +0 -2
- package/dist/cache/index.mjs +0 -5
- package/dist/index.d.mts +0 -6
- package/dist/index.mjs +0 -6
- package/dist/retriv/index.d.mts +0 -3
- package/dist/retriv/index.mjs +0 -2
- package/dist/sources/index.d.mts +0 -3
- package/dist/sources/index.mjs +0 -3
- package/dist/types.d.mts +0 -4
- package/dist/types.mjs +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"semver.mjs","names":["_valid","_gt","_diff"],"sources":["../../src/core/semver.ts"],"sourcesContent":["/**\n * Thin semver wrappers that pin `loose: true` at every callsite.\n * Centralized so the loose flag stays consistent across the project.\n */\n\nimport { diff as _diff, gt as _gt, valid as _valid } from 'semver'\n\n/** Returns the cleaned version if valid semver, null otherwise. */\nexport function semverValid(v: string): string | null {\n return _valid(v, true)\n}\n\n/** Compare two semver strings: returns true if a > b. Handles prereleases. */\nexport function semverGt(a: string, b: string): boolean {\n return _gt(a, b, true)\n}\n\n/** Returns the semver diff type between two versions, or null if equal/invalid. */\nexport function semverDiff(a: string, b: string): string | null {\n return _diff(a, b)\n}\n"],"mappings":";;;;SASSA,SAAU,GAAA,GAAK;;;SAKfC,WAAU,GAAK,GAAA;;;SAKfC,YAAW,GAAA,eAAA,GAAA,cAAA"}
|
|
1
|
+
{"version":3,"file":"semver.mjs","names":["STATIC_REGEX_1","STATIC_REGEX_2","STATIC_REGEX_2","STATIC_REGEX_3","STATIC_REGEX_4","STATIC_REGEX_1","STATIC_REGEX_2","STATIC_REGEX_1","STATIC_REGEX_3","STATIC_REGEX_1","STATIC_REGEX_1","STATIC_REGEX_2","STATIC_REGEX_3","fsExistsSync","fsReadFileSync","STATIC_REGEX_1","STATIC_REGEX_1","STATIC_REGEX_1","STATIC_REGEX_1","_exhaustive","_valid","_gt","_diff"],"sources":["../../src/sources/github-common.ts","../../src/sources/utils.ts","../../src/sources/releases.ts","../../src/sources/blog-releases.ts","../../src/core/url.ts","../../src/sources/github-tags.ts","../../src/sources/github-docs.ts","../../src/sources/issues.ts","../../src/sources/llms.ts","../../src/sources/github.ts","../../src/sources/crates.ts","../../src/sources/crawl.ts","../../src/sources/discussions.ts","../../src/sources/docs.ts","../../src/sources/entries.ts","../../src/sources/git-skills.ts","../../src/sources/local-package.ts","../../src/sources/npm-registry.ts","../../src/core/prefix.ts","../../src/sources/resolvers/crawl-url.ts","../../src/sources/resolvers/git-tag.ts","../../src/sources/resolvers/github-meta.ts","../../src/sources/resolvers/github-readme.ts","../../src/sources/resolvers/github-search.ts","../../src/sources/resolvers/llms-txt.ts","../../src/sources/resolvers/local-readme.ts","../../src/sources/resolvers/npm.ts","../../src/sources/resolvers/default.ts","../../src/sources/resolver-registry.ts","../../src/sources/resolve-package.ts","../../src/core/semver.ts"],"sourcesContent":["/**\n * Shared constants and helpers for GitHub source modules (issues, discussions, releases)\n */\n\nimport { spawnSync } from 'node:child_process'\nimport { ofetch } from 'ofetch'\nimport { yamlEscape } from '../core/yaml.ts'\n\nconst STATIC_REGEX_1 = /```[\\s\\S]*?```/\nconst STATIC_REGEX_2 = /`[^`]+`/\n\nexport const BOT_USERS = new Set([\n 'renovate[bot]',\n 'dependabot[bot]',\n 'renovate-bot',\n 'dependabot',\n 'github-actions[bot]',\n])\n\n/** Extract YYYY-MM-DD date from an ISO timestamp */\nexport const isoDate = (iso: string) => iso.split('T')[0]\n\n/** Build YAML frontmatter from a key-value object, auto-quoting strings with special chars */\nexport function buildFrontmatter(fields: Record<string, string | number | boolean | undefined>): string {\n const lines = ['---']\n for (const [k, v] of Object.entries(fields)) {\n if (v !== undefined)\n lines.push(`${k}: ${typeof v === 'string' ? yamlEscape(v) : v}`)\n }\n lines.push('---')\n return lines.join('\\n')\n}\n\n// ── Content Processing ──\n\n/** Check if body contains a code block */\nexport function hasCodeBlock(text: string): boolean {\n return STATIC_REGEX_1.test(text) || STATIC_REGEX_2.test(text)\n}\n\n/** Noise patterns in comments — filter these out */\nexport 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\n\n/**\n * Smart body truncation — preserves code blocks and error messages.\n * Instead of slicing at a char limit, finds a safe break point.\n */\nexport function truncateBody(body: string, limit: number): string {\n if (body.length <= limit)\n return body\n\n // Find code block boundaries so we don't cut mid-block\n const codeBlockRe = /```[\\s\\S]*?```/g\n let lastSafeEnd = limit\n let match: RegExpExecArray | null\n\n // eslint-disable-next-line no-cond-assign\n while ((match = codeBlockRe.exec(body)) !== null) {\n const blockStart = match.index\n const blockEnd = blockStart + match[0].length\n\n // If the limit falls inside a code block, move limit to after the block\n // (if not too far) or before the block\n if (blockStart < limit && blockEnd > limit) {\n if (blockEnd <= limit + 500) {\n // Block ends reasonably close — include it\n lastSafeEnd = blockEnd\n }\n else {\n // Block is too long — cut before it\n lastSafeEnd = blockStart\n }\n break\n }\n }\n\n // Try to break at a paragraph boundary\n const slice = body.slice(0, lastSafeEnd)\n const lastParagraph = slice.lastIndexOf('\\n\\n')\n if (lastParagraph > lastSafeEnd * 0.6)\n return `${slice.slice(0, lastParagraph)}\\n\\n...`\n\n return `${slice}...`\n}\n\n// ── GitHub Auth ──\n\nlet _ghToken: string | null | undefined\n\n/**\n * Get GitHub auth token from gh CLI (cached).\n * Returns null if gh CLI is not available or not authenticated.\n */\nexport function getGitHubToken(): string | null {\n if (_ghToken !== undefined)\n return _ghToken\n try {\n const { stdout } = spawnSync('gh', ['auth', 'token'], {\n encoding: 'utf-8',\n timeout: 5_000,\n stdio: ['ignore', 'pipe', 'ignore'],\n })\n _ghToken = stdout?.trim() || null\n }\n catch {\n _ghToken = null\n }\n return _ghToken\n}\n\n// ── Private Repo Tracking ──\n\n/** Repos where ungh.cc failed but gh api succeeded (likely private) */\nconst _needsAuth = new Set<string>()\n\n/** Mark a repo as needing authenticated access */\nexport function markRepoPrivate(owner: string, repo: string): void {\n _needsAuth.add(`${owner}/${repo}`)\n}\n\n/** Check if a repo is known to need authenticated access */\nexport function isKnownPrivateRepo(owner: string, repo: string): boolean {\n return _needsAuth.has(`${owner}/${repo}`)\n}\n\n/**\n * Try `ungh()` first (skipped for repos already learned to be private);\n * fall back to `api()` and mark the repo private when the API path produced\n * the result. Both branches return `null` if they couldn't produce a value.\n *\n * Encapsulates the \"ungh-or-api-with-private-learning\" pattern so the\n * mark-on-success bookkeeping lives in exactly one place.\n */\nexport async function fetchUnghOrApi<T>(\n owner: string,\n repo: string,\n ungh: () => Promise<T | null>,\n api: () => Promise<T | null>,\n): Promise<T | null> {\n if (!isKnownPrivateRepo(owner, repo)) {\n const r = await ungh().catch(() => null)\n if (r)\n return r\n }\n const r = await api()\n if (r)\n markRepoPrivate(owner, repo)\n return r\n}\n\n// ── GitHub API (async, no process spawn) ──\n\nconst GH_API = 'https://api.github.com'\n\nconst ghApiFetch = ofetch.create({\n retry: 2,\n retryDelay: 500,\n timeout: 15_000,\n headers: { 'User-Agent': 'skilld/1.0' },\n})\n\nconst LINK_NEXT_RE = /<([^>]+)>;\\s*rel=\"next\"/\n\n/** Parse GitHub Link header for next page URL */\nfunction parseLinkNext(header: string | null): string | null {\n if (!header)\n return null\n return header.match(LINK_NEXT_RE)?.[1] ?? null\n}\n\n/**\n * Authenticated fetch against api.github.com. Returns null if no token or request fails.\n * Endpoint should be relative, e.g. `repos/owner/repo/releases`.\n */\nexport async function ghApi<T>(endpoint: string): Promise<T | null> {\n const token = getGitHubToken()\n if (!token)\n return null\n return ghApiFetch<T>(`${GH_API}/${endpoint}`, {\n headers: { Authorization: `token ${token}` },\n }).catch(() => null)\n}\n\n/**\n * Paginated GitHub API fetch. Follows Link headers, returns concatenated arrays.\n * Endpoint should return a JSON array, e.g. `repos/owner/repo/releases`.\n */\nexport async function ghApiPaginated<T>(endpoint: string): Promise<T[]> {\n const token = getGitHubToken()\n if (!token)\n return []\n\n const headers = { Authorization: `token ${token}` }\n const results: T[] = []\n let url: string | null = `${GH_API}/${endpoint}`\n\n while (url) {\n const res = await ghApiFetch.raw<T[]>(url, { headers }).catch(() => null)\n if (!res?.ok || !Array.isArray(res._data))\n break\n results.push(...res._data)\n url = parseLinkNext(res.headers.get('link'))\n }\n\n return results\n}\n","/**\n * Shared utilities for doc resolution\n */\n\nimport { ofetch } from 'ofetch'\nimport { getGitHubToken, isKnownPrivateRepo, markRepoPrivate } from './github-common.ts'\n\nexport const SKILLD_USER_AGENT = 'skilld/1.0 (+https://github.com/harlan-zw/skilld)'\n\nexport const $fetch = ofetch.create({\n retry: 3,\n retryDelay: 1000,\n retryStatusCodes: [408, 429, 500, 502, 503, 504],\n timeout: 15_000,\n headers: { 'User-Agent': SKILLD_USER_AGENT },\n})\n\n/**\n * Create a rate-limited runner that enforces a minimum gap between task starts.\n * Queues tasks serially so consumers don't need to coordinate.\n */\nexport function createRateLimitedRunner(intervalMs: number): <T>(task: () => Promise<T>) => Promise<T> {\n let queue: Promise<void> = Promise.resolve()\n let lastRunAt = 0\n\n return async function runRateLimited<T>(task: () => Promise<T>): Promise<T> {\n const run = async (): Promise<T> => {\n const elapsed = Date.now() - lastRunAt\n const waitMs = intervalMs - elapsed\n if (waitMs > 0)\n await new Promise(resolve => setTimeout(resolve, waitMs))\n\n lastRunAt = Date.now()\n return task()\n }\n\n const request = queue.then(run, run)\n queue = request.then(() => undefined, () => undefined)\n return request\n }\n}\n\n/**\n * Fetch text content from URL\n */\nexport async function fetchText(url: string): Promise<string | null> {\n return $fetch(url, { responseType: 'text' }).catch(() => null)\n}\n\nconst RAW_GH_RE = /raw\\.githubusercontent\\.com\\/([^/]+)\\/([^/]+)/\n\n/** Extract owner/repo from a GitHub raw content URL */\nfunction extractGitHubRepo(url: string): { owner: string, repo: string } | null {\n const match = url.match(RAW_GH_RE)\n return match ? { owner: match[1]!, repo: match[2]! } : null\n}\n\n/**\n * Fetch text from a GitHub raw URL with auth fallback for private repos.\n * Tries unauthenticated first (fast path), falls back to authenticated\n * request when the repo is known to be private or unauthenticated fails.\n *\n * Only sends auth tokens to raw.githubusercontent.com — returns null for\n * non-GitHub URLs that fail unauthenticated to prevent token leakage.\n */\nexport async function fetchGitHubRaw(url: string): Promise<string | null> {\n const gh = extractGitHubRepo(url)\n const isKnownPrivate = gh ? isKnownPrivateRepo(gh.owner, gh.repo) : false\n\n // Fast path: skip unauthenticated attempt for known private repos\n if (!isKnownPrivate) {\n const content = await fetchText(url)\n if (content)\n return content\n }\n\n // Only send auth tokens to raw.githubusercontent.com\n if (!gh)\n return null\n\n // Fallback: authenticated request for private repos\n const token = getGitHubToken()\n if (!token)\n return null\n\n const content = await $fetch(url, {\n responseType: 'text',\n headers: { Authorization: `token ${token}` },\n }).catch(() => null) as string | null\n if (content)\n markRepoPrivate(gh.owner, gh.repo)\n return content\n}\n\n/**\n * Verify URL exists and is not HTML (likely 404 page)\n */\nexport async function verifyUrl(url: string): Promise<boolean> {\n const res = await $fetch.raw(url, { method: 'HEAD' }).catch(() => null)\n if (!res)\n return false\n const contentType = res.headers.get('content-type') || ''\n return !contentType.includes('text/html')\n}\n","/**\n * GitHub release notes fetching via GitHub API (preferred) with ungh.cc fallback\n */\n\nimport { NPM_SCOPE_PREFIX_RE, NPM_SCOPE_WITH_SLASH_RE, V_PREFIX_RE } from '../core/regex.ts'\nimport { yamlEscape } from '../core/yaml.ts'\nimport { ghApiPaginated, isoDate } from './github-common.ts'\nimport { $fetch, fetchGitHubRaw } from './utils.ts'\n\nconst STATIC_REGEX_2 = /^(\\d+)(?:\\.(\\d+))?(?:\\.(\\d+))?/\nconst STATIC_REGEX_3 = /^\\d+\\.\\d+\\.\\d+-.+/\nconst STATIC_REGEX_4 = /changelog\\.md/i\n\nexport interface GitHubRelease {\n id: number\n tag: string\n name: string\n prerelease: boolean\n createdAt: string\n publishedAt: string\n markdown: string\n}\n\ninterface UnghReleasesResponse {\n releases: GitHubRelease[]\n}\n\ninterface CachedDoc {\n path: string\n content: string\n}\n\nexport interface SemVer {\n major: number\n minor: number\n patch: number\n raw: string\n}\n\nexport function parseSemver(version: string): SemVer | null {\n const clean = version.replace(V_PREFIX_RE, '')\n const match = clean.match(STATIC_REGEX_2)\n if (!match)\n return null\n return {\n major: +match[1]!,\n minor: match[2] ? +match[2] : 0,\n patch: match[3] ? +match[3] : 0,\n raw: clean,\n }\n}\n\n/**\n * Extract version from a release tag, handling monorepo formats:\n * - `pkg@1.2.3` → `1.2.3`\n * - `pkg-v1.2.3` → `1.2.3`\n * - `v1.2.3` → `1.2.3`\n * - `1.2.3` → `1.2.3`\n */\nfunction extractVersion(tag: string, packageName?: string): string | null {\n if (packageName) {\n // Monorepo: pkg@version or pkg-vversion\n const atMatch = tag.match(new RegExp(`^${escapeRegex(packageName)}@(.+)$`))\n if (atMatch)\n return atMatch[1]!\n const dashMatch = tag.match(new RegExp(`^${escapeRegex(packageName)}-v?(.+)$`))\n if (dashMatch)\n return dashMatch[1]!\n }\n // Standard: v1.2.3 or 1.2.3\n return tag.replace(V_PREFIX_RE, '')\n}\n\nfunction escapeRegex(str: string): string {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n\n/**\n * Check if a release tag belongs to a specific package\n */\nfunction tagMatchesPackage(tag: string, packageName: string): boolean {\n // Exact match: pkg@version or pkg-vversion\n return tag.startsWith(`${packageName}@`) || tag.startsWith(`${packageName}-v`) || tag.startsWith(`${packageName}-`)\n}\n\n/**\n * Check if a version string contains a prerelease suffix (e.g. 6.0.0-beta, 1.2.3-rc.1)\n */\nexport function isPrerelease(version: string): boolean {\n return STATIC_REGEX_3.test(version.replace(V_PREFIX_RE, ''))\n}\n\nexport function compareSemver(a: SemVer, b: SemVer): number {\n if (a.major !== b.major)\n return a.major - b.major\n if (a.minor !== b.minor)\n return a.minor - b.minor\n return a.patch - b.patch\n}\n\ninterface GitHubApiRelease {\n id: number\n tag_name: string\n name: string\n prerelease: boolean\n created_at: string\n published_at: string\n body: string\n}\n\n/** Map GitHub API release to our GitHubRelease shape */\nfunction mapApiRelease(r: GitHubApiRelease): GitHubRelease {\n return {\n id: r.id,\n tag: r.tag_name,\n name: r.name,\n prerelease: r.prerelease,\n createdAt: r.created_at,\n publishedAt: r.published_at,\n markdown: r.body,\n }\n}\n\n/**\n * Fetch all releases — GitHub API first (authenticated, async), ungh.cc fallback\n */\nasync function fetchAllReleases(owner: string, repo: string): Promise<GitHubRelease[]> {\n // Try authenticated GitHub API first (no rate limits, works for private repos)\n const apiReleases = await ghApiPaginated<GitHubApiRelease>(`repos/${owner}/${repo}/releases`)\n if (apiReleases.length > 0)\n return apiReleases.map(mapApiRelease)\n\n // Fallback: ungh.cc (fast, no auth needed for public repos)\n const data = await $fetch<UnghReleasesResponse>(\n `https://ungh.cc/repos/${owner}/${repo}/releases`,\n { signal: AbortSignal.timeout(15_000) },\n ).catch(() => null)\n return data?.releases ?? []\n}\n\n/**\n * Select last 20 stable releases for a package, sorted newest first.\n * For monorepos, filters to package-specific tags (pkg@version).\n * Falls back to generic tags (v1.2.3) only if no package-specific found.\n * If installedVersion is provided, filters out releases newer than it.\n */\nexport function selectReleases(releases: GitHubRelease[], packageName?: string, installedVersion?: string, fromDate?: string): GitHubRelease[] {\n // Check if this looks like a monorepo (has package-prefixed tags)\n const hasMonorepoTags = packageName && releases.some(r => tagMatchesPackage(r.tag, packageName))\n const installedSv = installedVersion ? parseSemver(installedVersion) : null\n const installedIsPrerelease = installedVersion ? isPrerelease(installedVersion) : false\n const fromTs = fromDate ? new Date(fromDate).getTime() : null\n\n const filtered = releases.filter((r) => {\n const ver = extractVersion(r.tag, hasMonorepoTags ? packageName : undefined)\n if (!ver)\n return false\n\n const sv = parseSemver(ver)\n if (!sv)\n return false\n\n // Monorepo: only include tags for this package\n if (hasMonorepoTags && packageName && !tagMatchesPackage(r.tag, packageName))\n return false\n\n // Date lower bound: skip releases published before fromDate\n if (fromTs) {\n const pubDate = r.publishedAt || r.createdAt\n if (pubDate && new Date(pubDate).getTime() < fromTs)\n return false\n }\n\n // Prerelease handling: include only when installed is also prerelease and same major.minor\n if (r.prerelease) {\n if (!installedIsPrerelease || !installedSv)\n return false\n return sv.major === installedSv.major && sv.minor === installedSv.minor\n }\n\n // Filter out stable releases newer than installed version\n if (installedSv && compareSemver(sv, installedSv) > 0)\n return false\n\n return true\n })\n\n const sorted = filtered\n .sort((a, b) => {\n const verA = extractVersion(a.tag, hasMonorepoTags ? packageName : undefined)\n const verB = extractVersion(b.tag, hasMonorepoTags ? packageName : undefined)\n if (!verA || !verB)\n return 0\n return compareSemver(parseSemver(verB)!, parseSemver(verA)!)\n })\n\n // No cap when fromDate is set — include all matching releases\n return fromDate ? sorted : sorted.slice(0, 20)\n}\n\n/**\n * Format a release as markdown with YAML frontmatter\n */\nfunction formatRelease(release: GitHubRelease, packageName?: string): string {\n const date = isoDate(release.publishedAt || release.createdAt)\n const version = extractVersion(release.tag, packageName) || release.tag\n\n const fm = [\n '---',\n `tag: ${yamlEscape(release.tag)}`,\n `version: ${yamlEscape(version)}`,\n `published: ${date}`,\n ]\n if (release.name && release.name !== release.tag)\n fm.push(`name: ${yamlEscape(release.name)}`)\n fm.push('---')\n\n return `${fm.join('\\n')}\\n\\n# ${release.name || release.tag}\\n\\n${release.markdown}`\n}\n\nexport interface ReleaseIndexOptions {\n releases: GitHubRelease[]\n packageName?: string\n blogReleases?: Array<{ version: string, title: string, date: string }>\n hasChangelog?: boolean\n}\n\n/**\n * Generate a unified summary index of all releases for quick LLM scanning.\n * Includes GitHub releases, blog release posts, and CHANGELOG link.\n */\nexport function generateReleaseIndex(releasesOrOpts: GitHubRelease[] | ReleaseIndexOptions, packageName?: string): string {\n // Support both old signature and new options object\n const opts: ReleaseIndexOptions = Array.isArray(releasesOrOpts)\n ? { releases: releasesOrOpts, packageName }\n : releasesOrOpts\n\n const { releases, blogReleases, hasChangelog } = opts\n const pkg = opts.packageName\n\n const total = releases.length + (blogReleases?.length ?? 0)\n const fm = [\n '---',\n `total: ${total}`,\n `latest: ${yamlEscape(releases[0]?.tag || 'unknown')}`,\n '---',\n ]\n\n const lines: string[] = [fm.join('\\n'), '', '# Releases Index', '']\n\n // Blog release posts (major version announcements)\n if (blogReleases && blogReleases.length > 0) {\n lines.push('## Blog Releases', '')\n for (const b of blogReleases) {\n lines.push(`- [${b.version}](./blog-${b.version}.md): ${b.title} (${b.date})`)\n }\n lines.push('')\n }\n\n // GitHub release notes\n if (releases.length > 0) {\n if (blogReleases && blogReleases.length > 0)\n lines.push('## Release Notes', '')\n for (const r of releases) {\n const date = isoDate(r.publishedAt || r.createdAt)\n const filename = r.tag.includes('@') || r.tag.startsWith('v') ? r.tag : `v${r.tag}`\n const version = extractVersion(r.tag, pkg) || r.tag\n const sv = parseSemver(version)\n const label = sv?.patch === 0 && sv.minor === 0 ? ' **[MAJOR]**' : sv?.patch === 0 ? ' **[MINOR]**' : ''\n lines.push(`- [${r.tag}](./${filename}.md): ${r.name || r.tag} (${date})${label}`)\n }\n lines.push('')\n }\n\n // CHANGELOG link\n if (hasChangelog) {\n lines.push('## Changelog', '')\n lines.push('- [CHANGELOG.md](./CHANGELOG.md)')\n lines.push('')\n }\n\n return lines.join('\\n')\n}\n\n/**\n * Check if a single release is a stub redirecting to CHANGELOG.md.\n * Short body (<500 chars) that mentions CHANGELOG indicates no real content.\n */\nexport function isStubRelease(release: GitHubRelease): boolean {\n const body = (release.markdown || '').trim()\n return body.length < 500 && STATIC_REGEX_4.test(body)\n}\n\n/**\n * Fetch CHANGELOG.md from a GitHub repo at a specific ref as fallback.\n * For monorepos, also checks packages/{shortName}/CHANGELOG.md.\n */\nasync function fetchChangelog(owner: string, repo: string, ref: string, packageName?: string): Promise<string | null> {\n const paths: string[] = []\n\n // Monorepo: try package-specific paths first (e.g. packages/pinia/CHANGELOG.md)\n if (packageName) {\n const shortName = packageName.replace(NPM_SCOPE_WITH_SLASH_RE, '')\n const scopeless = packageName.replace(NPM_SCOPE_PREFIX_RE, '').replace('/', '-')\n const candidates = [...new Set([shortName, scopeless])]\n for (const name of candidates) {\n paths.push(`packages/${name}/CHANGELOG.md`)\n }\n }\n\n // Root-level changelog\n paths.push('CHANGELOG.md', 'changelog.md', 'CHANGES.md')\n\n for (const path of paths) {\n const url = `https://raw.githubusercontent.com/${owner}/${repo}/${ref}/${path}`\n const content = await fetchGitHubRaw(url)\n if (content)\n return content\n }\n return null\n}\n\n/**\n * Fetch release notes for a package. Returns CachedDoc[] with releases/{tag}.md files.\n *\n * Strategy:\n * 1. Fetch GitHub releases, filter to package-specific tags for monorepos\n * 2. If no releases found, try CHANGELOG.md as fallback\n */\nexport async function fetchReleaseNotes(\n owner: string,\n repo: string,\n installedVersion: string,\n gitRef?: string,\n packageName?: string,\n fromDate?: string,\n changelogRef?: string,\n): Promise<CachedDoc[]> {\n const releases = await fetchAllReleases(owner, repo)\n const selected = selectReleases(releases, packageName, installedVersion, fromDate)\n\n if (selected.length > 0) {\n // Filter out individual stub releases that just say \"see CHANGELOG\"\n const substantive = selected.filter(r => !isStubRelease(r))\n\n const docs = substantive.map((r) => {\n const filename = r.tag.includes('@') || r.tag.startsWith('v')\n ? r.tag\n : `v${r.tag}`\n return {\n path: `releases/${filename}.md`,\n content: formatRelease(r, packageName),\n }\n })\n\n // Always fetch CHANGELOG.md alongside substantive releases\n const ref = changelogRef || gitRef || selected[0]!.tag\n const changelog = await fetchChangelog(owner, repo, ref, packageName)\n if (changelog && changelog.length < 500_000) {\n docs.push({ path: 'releases/CHANGELOG.md', content: changelog })\n }\n\n return docs\n }\n\n // Fallback: CHANGELOG.md (indexed as single file)\n const ref = changelogRef || gitRef || 'main'\n const changelog = await fetchChangelog(owner, repo, ref, packageName)\n if (!changelog)\n return []\n\n return [{ path: 'releases/CHANGELOG.md', content: changelog }]\n}\n","/**\n * Blog release notes fetching for packages with curated blog releases\n * Supports version filtering and extensible for multiple packages\n */\n\nimport type { BlogRelease } from './package-registry.ts'\nimport { htmlToMarkdown } from 'mdream'\nimport pLimit from 'p-limit'\nimport { yamlEscape } from '../core/yaml.ts'\nimport { getBlogPreset } from './package-registry.ts'\nimport { compareSemver, parseSemver } from './releases.ts'\nimport { $fetch } from './utils.ts'\n\nconst STATIC_REGEX_1 = /<h1[^>]*>([^<]+)<\\/h1>/\nconst STATIC_REGEX_2 = /<title>([^<]+)<\\/title>/\n\nexport interface BlogReleasePost {\n version: string\n title: string\n date: string\n markdown: string\n url: string\n}\n\ninterface CachedDoc {\n path: string\n content: string\n}\n\n/**\n * Format a blog release as markdown with YAML frontmatter\n */\nfunction formatBlogRelease(release: BlogReleasePost): string {\n const fm = [\n '---',\n `version: ${yamlEscape(release.version)}`,\n `title: ${yamlEscape(release.title)}`,\n `date: ${release.date}`,\n `url: ${yamlEscape(release.url)}`,\n `source: blog-release`,\n '---',\n ]\n\n return `${fm.join('\\n')}\\n\\n# ${release.title}\\n\\n${release.markdown}`\n}\n\n/**\n * Fetch and parse a single blog post using preset metadata for version/date\n */\nasync function fetchBlogPost(entry: BlogRelease): Promise<BlogReleasePost | null> {\n try {\n const html = await $fetch(entry.url, { responseType: 'text', signal: AbortSignal.timeout(10_000) }).catch(() => null)\n if (!html)\n return null\n\n // Extract title from <h1> or <title>, fallback to preset title\n let title = ''\n const titleMatch = html.match(STATIC_REGEX_1)\n if (titleMatch)\n title = titleMatch[1]!.trim()\n\n if (!title) {\n const metaTitleMatch = html.match(STATIC_REGEX_2)\n if (metaTitleMatch)\n title = metaTitleMatch[1]!.trim()\n }\n\n const markdown = htmlToMarkdown(html)\n if (!markdown)\n return null\n\n return {\n version: entry.version,\n title: title || entry.title || `Release ${entry.version}`,\n date: entry.date,\n markdown,\n url: entry.url,\n }\n }\n catch {\n return null\n }\n}\n\n/**\n * Filter blog releases by installed version\n * Only includes releases where version <= installedVersion\n * Returns all releases if version parsing fails (fail-safe)\n */\nfunction filterBlogsByVersion(entries: BlogRelease[], installedVersion: string): BlogRelease[] {\n const installedSv = parseSemver(installedVersion)\n if (!installedSv)\n return entries // Fail-safe: include all if version parsing fails\n\n return entries.filter((entry) => {\n const entrySv = parseSemver(entry.version)\n if (!entrySv)\n return false\n // Include only releases where version <= installed version\n return compareSemver(entrySv, installedSv) <= 0\n })\n}\n\n/**\n * Fetch blog release notes from package presets\n * Filters to only releases matching or older than the installed version\n * Returns CachedDoc[] with releases/blog-{version}.md files\n */\nexport async function fetchBlogReleases(\n packageName: string,\n installedVersion: string,\n): Promise<CachedDoc[]> {\n const preset = getBlogPreset(packageName)\n if (!preset)\n return []\n\n const filteredReleases = filterBlogsByVersion(preset.releases, installedVersion)\n if (filteredReleases.length === 0)\n return []\n\n // Fetch all blog posts with controlled concurrency\n const limit = pLimit(3)\n const results = await Promise.all(\n filteredReleases.map(entry => limit(() => fetchBlogPost(entry))),\n )\n const releases = results.filter((r): r is BlogReleasePost => r !== null)\n\n if (releases.length === 0)\n return []\n\n // Sort by version descending (newest first)\n releases.sort((a, b) => {\n const aVer = a.version.split('.').map(Number)\n const bVer = b.version.split('.').map(Number)\n for (let i = 0; i < Math.max(aVer.length, bVer.length); i++) {\n const diff = (bVer[i] ?? 0) - (aVer[i] ?? 0)\n if (diff !== 0)\n return diff\n }\n return 0\n })\n\n // Format as cached docs — stored in releases/ alongside regular releases\n return releases.map(r => ({\n path: `releases/blog-${r.version}.md`,\n content: formatBlogRelease(r),\n }))\n}\n","import { GIT_PLUS_PREFIX_RE, GIT_PROTOCOL_PREFIX_RE, GIT_SUFFIX_RE, GITHUB_SSH_URL_PREFIX_RE } from './regex.ts'\n\nconst STATIC_REGEX_1 = /github\\.com\\/([^/]+)\\/([^/]+?)(?:\\.git)?(?:[/#]|$)/\nconst STATIC_REGEX_3 = /#.*$/\nconst STATIC_REGEX_7 = /^git@github\\.com:/\nconst STATIC_REGEX_8 = /^(?:127\\.|10\\.|172\\.(?:1[6-9]|2\\d|3[01])\\.|192\\.168\\.|169\\.254\\.)/\nconst STATIC_REGEX_9 = /^\\[(?:f[cd]|fe[89ab]|::ffff:)/i\n\n/**\n * Pure URL and package-spec parsers (no fetching, no I/O).\n *\n * Moved from `src/sources/utils.ts` to keep the sources barrel focused on\n * fetching primitives and resolvers.\n */\n\n/**\n * Parse owner/repo from GitHub URL\n */\nexport function parseGitHubUrl(url: string): { owner: string, repo: string } | null {\n const match = url.match(STATIC_REGEX_1)\n if (!match)\n return null\n return { owner: match[1]!, repo: match[2]! }\n}\n\n/** Parse owner/repo slug from GitHub URL */\nexport function parseGitHubRepoSlug(url: string | undefined): string | undefined {\n if (!url)\n return undefined\n const parsed = parseGitHubUrl(url)\n return parsed ? `${parsed.owner}/${parsed.repo}` : undefined\n}\n\n/**\n * Parse package spec with optional dist-tag or version: \"vue@beta\" → { name: \"vue\", tag: \"beta\" }\n * Handles scoped packages: \"@vue/reactivity@beta\" → { name: \"@vue/reactivity\", tag: \"beta\" }\n */\nexport function parsePackageSpec(spec: string): { name: string, tag?: string } {\n // Scoped: @scope/pkg@tag — find the second @\n if (spec.startsWith('@')) {\n const slashIdx = spec.indexOf('/')\n if (slashIdx !== -1) {\n const atIdx = spec.indexOf('@', slashIdx + 1)\n if (atIdx !== -1)\n return { name: spec.slice(0, atIdx), tag: spec.slice(atIdx + 1) }\n }\n return { name: spec }\n }\n // Unscoped: pkg@tag\n const atIdx = spec.indexOf('@')\n if (atIdx !== -1)\n return { name: spec.slice(0, atIdx), tag: spec.slice(atIdx + 1) }\n return { name: spec }\n}\n\n/**\n * Normalize git repo URL to https\n */\nexport function normalizeRepoUrl(url: string): string {\n return url\n .replace(GIT_PLUS_PREFIX_RE, '')\n .replace(STATIC_REGEX_3, '')\n .replace(GIT_SUFFIX_RE, '')\n .replace(GIT_PROTOCOL_PREFIX_RE, 'https://')\n .replace(GITHUB_SSH_URL_PREFIX_RE, 'https://github.com')\n // SSH format: git@github.com:owner/repo\n .replace(STATIC_REGEX_7, 'https://github.com/')\n}\n\n/**\n * Extract branch hint from URL fragment (e.g. \"git+https://...#main\" → \"main\")\n */\nexport function extractBranchHint(url: string): string | undefined {\n const hash = url.indexOf('#')\n if (hash === -1)\n return undefined\n const fragment = url.slice(hash + 1)\n // Ignore non-branch fragments like \"readme\"\n if (!fragment || fragment === 'readme')\n return undefined\n return fragment\n}\n\n/**\n * Check if URL is a GitHub repo URL (not a docs site)\n */\nexport function isGitHubRepoUrl(url: string): boolean {\n try {\n const parsed = new URL(url)\n return parsed.hostname === 'github.com' || parsed.hostname === 'www.github.com'\n }\n catch {\n return false\n }\n}\n\n/** Check if URL points to a code hosting provider (GitHub/GitLab) rather than a docs site */\nexport function isLikelyCodeHostUrl(url: string | undefined): boolean {\n if (!url)\n return false\n try {\n const parsed = new URL(url)\n return ['github.com', 'www.github.com', 'gitlab.com', 'www.gitlab.com'].includes(parsed.hostname)\n }\n catch {\n return false\n }\n}\n\n/**\n * Check if URL points to a social media or package registry site (not real docs)\n */\nconst USELESS_HOSTS = new Set([\n 'twitter.com',\n 'x.com',\n 'facebook.com',\n 'linkedin.com',\n 'youtube.com',\n 'instagram.com',\n 'npmjs.com',\n 'www.npmjs.com',\n 'yarnpkg.com',\n])\n\nexport function isUselessDocsUrl(url: string): boolean {\n try {\n const { hostname } = new URL(url)\n return USELESS_HOSTS.has(hostname)\n }\n catch { return false }\n}\n\n/** Reject non-https URLs and private/link-local IPs */\nexport function isSafeUrl(url: string): boolean {\n try {\n const parsed = new URL(url)\n if (parsed.protocol !== 'https:')\n return false\n const host = parsed.hostname\n // Reject private/link-local/loopback\n if (host === 'localhost' || host === '0.0.0.0' || host === '[::1]')\n return false\n if (STATIC_REGEX_8.test(host))\n return false\n // IPv6 private/link-local — hostname keeps brackets in Node.js\n if (STATIC_REGEX_9.test(host))\n return false\n return true\n }\n catch { return false }\n}\n","/**\n * GitHub tag/version resolution and release listing.\n *\n * Owns the \"find a usable git ref for this version\" cascade plus the lower-level\n * `listFilesAtRef` primitive (shared with doc-discovery / source fetch).\n */\n\nimport { fetchUnghOrApi, ghApi, ghApiPaginated } from './github-common.ts'\nimport { $fetch } from './utils.ts'\n\ninterface UnghFilesResponse {\n meta: { sha: string }\n files: Array<{ path: string, mode: string, sha: string, size: number }>\n}\n\n/**\n * List files at a git ref. Tries ungh.cc first (fast, no rate limits),\n * falls back to GitHub API for private repos.\n */\nexport async function listFilesAtRef(owner: string, repo: string, ref: string): Promise<string[]> {\n const files = await fetchUnghOrApi<string[]>(\n owner,\n repo,\n async () => {\n const data = await $fetch<UnghFilesResponse>(`https://ungh.cc/repos/${owner}/${repo}/files/${ref}`)\n return data.files?.length ? data.files.map(f => f.path) : null\n },\n async () => {\n const tree = await ghApi<{ tree?: Array<{ path: string }> }>(`repos/${owner}/${repo}/git/trees/${ref}?recursive=1`)\n return tree?.tree?.length ? tree.tree.map(f => f.path) : null\n },\n )\n return files ?? []\n}\n\nexport interface TagResult {\n ref: string\n files: string[]\n /** True when ref is a branch fallback (main/master) rather than a version tag */\n fallback?: boolean\n}\n\n/**\n * Find git tag for a version by checking if ungh can list files at that ref.\n * Tries v{version}, {version}, and optionally {packageName}@{version} (changeset convention).\n */\nexport async function findGitTag(owner: string, repo: string, version: string, packageName?: string, branchHint?: string): Promise<TagResult | null> {\n const candidates = [`v${version}`, version]\n if (packageName)\n candidates.push(`${packageName}@${version}`)\n\n for (const tag of candidates) {\n const files = await listFilesAtRef(owner, repo, tag)\n if (files.length > 0)\n return { ref: tag, files }\n }\n\n // Fallback: find latest release tag matching {packageName}@* (version mismatch in monorepos)\n if (packageName) {\n const latestTag = await findLatestReleaseTag(owner, repo, packageName)\n if (latestTag) {\n const files = await listFilesAtRef(owner, repo, latestTag)\n if (files.length > 0)\n return { ref: latestTag, files }\n }\n }\n\n // Last resort: try default branch (prefer hint from repo URL fragment)\n const branches = branchHint\n ? [branchHint, ...['main', 'master'].filter(b => b !== branchHint)]\n : ['main', 'master']\n for (const branch of branches) {\n const files = await listFilesAtRef(owner, repo, branch)\n if (files.length > 0)\n return { ref: branch, files, fallback: true }\n }\n\n return null\n}\n\ninterface GitHubApiRelease {\n tag_name: string\n published_at?: string\n}\n\n/** Fetch releases from ungh.cc first, fall back to GitHub API for private repos. */\nexport async function fetchUnghReleases(owner: string, repo: string): Promise<Array<{ tag: string, publishedAt?: string }>> {\n const releases = await fetchUnghOrApi<Array<{ tag: string, publishedAt?: string }>>(\n owner,\n repo,\n async () => {\n const data = await $fetch<{ releases?: Array<{ tag: string, publishedAt?: string }> }>(`https://ungh.cc/repos/${owner}/${repo}/releases`)\n return data.releases?.length ? data.releases : null\n },\n async () => {\n const raw = await ghApiPaginated<GitHubApiRelease>(`repos/${owner}/${repo}/releases`)\n return raw.length > 0 ? raw.map(r => ({ tag: r.tag_name, publishedAt: r.published_at })) : null\n },\n )\n return releases ?? []\n}\n\n/** Find the latest release tag matching `{packageName}@*`. */\nexport async function findLatestReleaseTag(owner: string, repo: string, packageName: string): Promise<string | null> {\n const prefix = `${packageName}@`\n const releases = await fetchUnghReleases(owner, repo)\n return releases.find(r => r.tag.startsWith(prefix))?.tag ?? null\n}\n","/**\n * GitHub versioned doc discovery, fetch, and validation.\n *\n * Owns: doc-dir scoring heuristic, monorepo prefix discovery, framework-specific\n * filtering, llms.txt cross-validation. This is where \"wrong docs picked\"\n * regressions live; isolating it from repo/source/readme concerns localizes the\n * bug surface.\n */\n\nimport type { LlmsLink } from './types.ts'\nimport { mapInsert } from '../core/map.ts'\nimport { LEADING_SLASH_RE, NPM_SCOPE_WITH_SLASH_RE } from '../core/regex.ts'\nimport { extractBranchHint } from '../core/url.ts'\nimport { findGitTag, listFilesAtRef } from './github-tags.ts'\nimport { getDocOverride } from './package-registry.ts'\n\nconst STATIC_REGEX_1 = /\\.(?:md|mdx)$/\n\n/** Minimum git-doc file count to prefer over llms.txt */\nexport const MIN_GIT_DOCS = 5\n\n/** True when git-docs exist but are too few to be useful (< MIN_GIT_DOCS) */\nexport const isShallowGitDocs = (n: number) => n > 0 && n < MIN_GIT_DOCS\n\nexport interface GitDocsResult {\n /** URL pattern for fetching docs (use with ref) */\n baseUrl: string\n /** Git ref (tag) used */\n ref: string\n /** List of doc file paths relative to repo root */\n files: string[]\n /** Prefix to strip when normalizing paths to docs/ (e.g. 'apps/evalite-docs/src/content/') for nested monorepo docs */\n docsPrefix?: string\n /** Full repo file tree — only set when discoverDocFiles() heuristic was used (not standard docs/ prefix) */\n allFiles?: string[]\n /** True when ref is a branch (main/master) rather than a version-specific tag */\n fallback?: boolean\n}\n\n/** Filter file paths by prefix and md/mdx extension */\nfunction filterDocFiles(files: string[], pathPrefix: string): string[] {\n return files.filter(f => f.startsWith(pathPrefix) && STATIC_REGEX_1.test(f))\n}\n\nconst FRAMEWORK_NAMES = new Set(['vue', 'react', 'solid', 'angular', 'svelte', 'preact', 'lit', 'qwik'])\n\n/**\n * Filter out docs for other frameworks when the package targets a specific one.\n * e.g. @tanstack/vue-query → keep vue + shared docs, exclude react/solid/angular\n * Uses word-boundary matching to catch all path conventions:\n * framework/react/, 0.react/, api/ai-react.md, react-native.mdx, etc.\n */\nexport function filterFrameworkDocs(files: string[], packageName?: string): string[] {\n if (!packageName)\n return files\n const shortName = packageName.replace(NPM_SCOPE_WITH_SLASH_RE, '')\n const targetFramework = [...FRAMEWORK_NAMES].find(fw => shortName.includes(fw))\n if (!targetFramework)\n return files\n\n const otherFrameworks = [...FRAMEWORK_NAMES].filter(fw => fw !== targetFramework)\n const excludePattern = new RegExp(`\\\\b(?:${otherFrameworks.join('|')})\\\\b`)\n return files.filter(f => !excludePattern.test(f))\n}\n\n// ── Doc-dir discovery heuristic ──────────────────────────────────────\n\n/** Known noise paths to exclude from doc discovery */\nconst NOISE_PATTERNS = [\n /^\\.changeset\\//,\n /CHANGELOG\\.md$/i,\n /CONTRIBUTING\\.md$/i,\n /^\\.github\\//,\n]\n\n/** Directories to exclude from \"best directory\" heuristic */\nconst EXCLUDE_DIRS = new Set([\n 'test',\n 'tests',\n '__tests__',\n 'fixtures',\n 'fixture',\n 'examples',\n 'example',\n 'node_modules',\n '.git',\n 'dist',\n 'build',\n 'coverage',\n 'e2e',\n 'spec',\n 'mocks',\n '__mocks__',\n])\n\n/** Directory names that suggest documentation */\nconst DOC_DIR_BONUS = new Set([\n 'docs',\n 'documentation',\n 'pages',\n 'content',\n 'website',\n 'guide',\n 'guides',\n 'wiki',\n 'manual',\n 'api',\n])\n\ninterface DiscoveredDocs {\n files: string[]\n /** Prefix before 'docs/' to strip when normalizing (e.g. 'apps/evalite-docs/src/content/') */\n prefix: string\n}\n\nfunction hasExcludedDir(path: string): boolean {\n const parts = path.split('/')\n return parts.some(p => EXCLUDE_DIRS.has(p.toLowerCase()))\n}\n\nfunction getPathDepth(path: string): number {\n return path.split('/').filter(Boolean).length\n}\n\nfunction hasDocDirBonus(path: string): boolean {\n const parts = path.split('/')\n return parts.some(p => DOC_DIR_BONUS.has(p.toLowerCase()))\n}\n\n/**\n * Score a directory for doc likelihood.\n * Higher = better. Formula: count * nameBonus / depth\n */\nfunction scoreDocDir(dir: string, fileCount: number): number {\n const depth = getPathDepth(dir) || 1\n const nameBonus = hasDocDirBonus(dir) ? 1.5 : 1\n return (fileCount * nameBonus) / depth\n}\n\n/**\n * Discover doc files in non-standard locations.\n * First tries to scope to sub-package dir in monorepos.\n * Then looks for clusters of md/mdx files in paths containing /docs/.\n * Falls back to finding the directory with the most markdown files (≥5).\n */\nfunction discoverDocFiles(allFiles: string[], packageName?: string): DiscoveredDocs | null {\n const mdFiles = allFiles\n .filter(f => STATIC_REGEX_1.test(f))\n .filter(f => !NOISE_PATTERNS.some(p => p.test(f)))\n .filter(f => f.includes('/'))\n\n // Strategy 0: Scope to sub-package in monorepos\n if (packageName?.includes('/')) {\n const shortName = packageName.split('/').pop()!.toLowerCase()\n const subPkgPrefix = `packages/${shortName}/`\n const subPkgFiles = mdFiles.filter(f => f.startsWith(subPkgPrefix))\n if (subPkgFiles.length >= 3)\n return { files: subPkgFiles, prefix: subPkgPrefix }\n }\n\n // Strategy 1: Look for /docs/ clusters (existing behavior)\n const docsGroups = new Map<string, string[]>()\n\n for (const file of mdFiles) {\n const docsIdx = file.lastIndexOf('/docs/')\n if (docsIdx === -1)\n continue\n\n const prefix = file.slice(0, docsIdx + '/docs/'.length)\n mapInsert(docsGroups, prefix, () => []).push(file)\n }\n\n if (docsGroups.size > 0) {\n const largest = [...docsGroups.entries()].sort((a, b) => b[1].length - a[1].length)[0]!\n if (largest[1].length >= 3) {\n const fullPrefix = largest[0]\n const docsIdx = fullPrefix.lastIndexOf('docs/')\n const stripPrefix = docsIdx > 0 ? fullPrefix.slice(0, docsIdx) : ''\n return { files: largest[1], prefix: stripPrefix }\n }\n }\n\n // Strategy 2: Find best directory by file count (for non-standard structures)\n const dirGroups = new Map<string, string[]>()\n\n for (const file of mdFiles) {\n if (hasExcludedDir(file))\n continue\n\n const lastSlash = file.lastIndexOf('/')\n if (lastSlash === -1)\n continue\n\n const dir = file.slice(0, lastSlash + 1)\n mapInsert(dirGroups, dir, () => []).push(file)\n }\n\n if (dirGroups.size === 0)\n return null\n\n const scored = Array.from(dirGroups.entries(), ([dir, files]) => ({ dir, files, score: scoreDocDir(dir, files.length) }))\n .filter(d => d.files.length >= 5)\n .sort((a, b) => b.score - a.score)\n\n if (scored.length === 0)\n return null\n\n const best = scored[0]!\n return { files: best.files, prefix: best.dir }\n}\n\n/** List markdown files in a folder at a specific git ref */\nasync function listDocsAtRef(owner: string, repo: string, ref: string, pathPrefix = 'docs/'): Promise<string[]> {\n const files = await listFilesAtRef(owner, repo, ref)\n return filterDocFiles(files, pathPrefix)\n}\n\n// ── Public fetch ─────────────────────────────────────────────────────\n\n/**\n * Fetch versioned docs from GitHub repo's docs/ folder.\n * Pass packageName to check doc overrides (e.g. vue -> vuejs/docs).\n */\nexport async function fetchGitDocs(owner: string, repo: string, version: string, packageName?: string, repoUrl?: string): Promise<GitDocsResult | null> {\n const override = packageName ? getDocOverride(packageName) : undefined\n if (override) {\n const ref = override.ref || 'main'\n const fallback = !override.ref\n const files = await listDocsAtRef(override.owner, override.repo, ref, `${override.path}/`)\n if (files.length === 0)\n return null\n return {\n baseUrl: `https://raw.githubusercontent.com/${override.owner}/${override.repo}/${ref}`,\n ref,\n files,\n fallback,\n docsPrefix: `${override.path}/` !== 'docs/' ? `${override.path}/` : undefined,\n }\n }\n\n const branchHint = repoUrl ? extractBranchHint(repoUrl) : undefined\n const tag = await findGitTag(owner, repo, version, packageName, branchHint)\n if (!tag)\n return null\n\n let docs = filterDocFiles(tag.files, 'docs/')\n let docsPrefix: string | undefined\n let allFiles: string[] | undefined\n\n if (docs.length === 0) {\n const discovered = discoverDocFiles(tag.files, packageName)\n if (discovered) {\n docs = discovered.files\n docsPrefix = discovered.prefix || undefined\n allFiles = tag.files\n }\n }\n\n docs = filterFrameworkDocs(docs, packageName)\n\n if (docs.length === 0)\n return null\n\n return {\n baseUrl: `https://raw.githubusercontent.com/${owner}/${repo}/${tag.ref}`,\n ref: tag.ref,\n files: docs,\n docsPrefix,\n allFiles,\n fallback: tag.fallback,\n }\n}\n\n// ── llms.txt cross-validation ────────────────────────────────────────\n\n/** Strip file extension (.md, .mdx) and leading slash from a path */\nfunction normalizePath(p: string): string {\n return p.replace(LEADING_SLASH_RE, '').replace(STATIC_REGEX_1, '')\n}\n\n/**\n * Validate that discovered git docs are relevant by cross-referencing llms.txt links\n * against the repo file tree. Uses extensionless suffix matching to handle monorepo nesting.\n *\n * Returns { isValid, matchRatio } where isValid = matchRatio >= 0.3\n */\nexport function validateGitDocsWithLlms(\n llmsLinks: LlmsLink[],\n repoFiles: string[],\n): { isValid: boolean, matchRatio: number } {\n if (llmsLinks.length === 0)\n return { isValid: true, matchRatio: 1 }\n\n const sample = llmsLinks.slice(0, 10)\n\n const normalizedLinks = sample.map((link) => {\n let path = link.url\n if (path.startsWith('http')) {\n try {\n path = new URL(path).pathname\n }\n catch { /* keep as-is */ }\n }\n return normalizePath(path)\n })\n\n const repoNormalized = new Set(repoFiles.map(normalizePath))\n\n let matches = 0\n for (const linkPath of normalizedLinks) {\n for (const repoPath of repoNormalized) {\n if (repoPath === linkPath || repoPath.endsWith(`/${linkPath}`)) {\n matches++\n break\n }\n }\n }\n\n const matchRatio = matches / sample.length\n return { isValid: matchRatio >= 0.3, matchRatio }\n}\n","/**\n * GitHub issues fetching via gh CLI Search API\n * Freshness-weighted scoring, type quotas, comment quality filtering\n * Categorized by labels, noise filtered out, non-technical issues detected\n */\n\nimport { spawnSync } from 'node:child_process'\n\nimport { mapInsert } from '../core/map.ts'\nimport { BOT_USERS, buildFrontmatter, COMMENT_NOISE_RE, hasCodeBlock, isoDate, truncateBody } from './github-common.ts'\n\nconst STATIC_REGEX_1 = /\\b(?:love|thank|awesome|great work)\\b/i\nconst STATIC_REGEX_2 = /\\broadmap\\b/i\nconst STATIC_REGEX_3 = /roadmap/i\nconst STATIC_REGEX_4 = /(?:fixed|landed|released|available|shipped|resolved|included)\\s+in\\s+v?(\\d+\\.\\d+(?:\\.\\d+)?)/i\nconst STATIC_REGEX_5 = /\\bv?(\\d+\\.\\d+\\.\\d+)\\b/\n\nexport type IssueType = 'bug' | 'question' | 'docs' | 'feature' | 'other'\n\nexport interface IssueComment {\n body: string\n author: string\n reactions: number\n isMaintainer?: boolean\n}\n\nexport interface GitHubIssue {\n number: number\n title: string\n state: string\n labels: string[]\n body: string\n createdAt: string\n url: string\n reactions: number\n comments: number\n type: IssueType\n topComments: IssueComment[]\n /** Freshness-weighted score: reactions * decay(age) */\n score: number\n /** For closed issues: version where fix landed, if detectable */\n resolvedIn?: string\n}\n\nlet _ghAvailable: boolean | undefined\n\n/**\n * Check if gh CLI is installed and authenticated (cached)\n */\nexport function isGhAvailable(): boolean {\n if (_ghAvailable !== undefined)\n return _ghAvailable\n const { status } = spawnSync('gh', ['auth', 'status'], { stdio: 'ignore' })\n return (_ghAvailable = status === 0)\n}\n\n/** Labels that indicate noise — filter these out entirely */\nconst NOISE_LABELS = new Set([\n 'duplicate',\n 'stale',\n 'invalid',\n 'wontfix',\n 'won\\'t fix',\n 'spam',\n 'off-topic',\n 'needs triage',\n 'triage',\n])\n\n/** Labels that indicate feature requests — deprioritize */\nconst FEATURE_LABELS = new Set([\n 'enhancement',\n 'feature',\n 'feature request',\n 'feature-request',\n 'proposal',\n 'rfc',\n 'idea',\n 'suggestion',\n])\n\nconst BUG_LABELS = new Set([\n 'bug',\n 'defect',\n 'regression',\n 'error',\n 'crash',\n 'fix',\n 'confirmed',\n 'verified',\n])\n\nconst QUESTION_LABELS = new Set([\n 'question',\n 'help wanted',\n 'support',\n 'usage',\n 'how-to',\n 'help',\n 'assistance',\n])\n\nconst DOCS_LABELS = new Set([\n 'documentation',\n 'docs',\n 'doc',\n 'typo',\n])\n\n/** Cache compiled word-boundary regexes per keyword set */\nconst labelRegexCache = new WeakMap<Set<string>, RegExp>()\n\nfunction getLabelRegex(keywords: Set<string>): RegExp {\n let re = labelRegexCache.get(keywords)\n if (!re) {\n const escaped = Array.from(keywords, k => k.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&'))\n re = new RegExp(`\\\\b(?:${escaped.join('|')})\\\\b`)\n labelRegexCache.set(keywords, re)\n }\n return re\n}\n\n/**\n * Check if a label matches any keyword from a set using word boundaries.\n * Handles emoji-prefixed labels like \":sparkles: feature request\" or \":lady_beetle: bug\"\n * without false positives on substrings (e.g. \"debug\" should not match \"bug\").\n */\nexport function labelMatchesAny(label: string, keywords: Set<string>): boolean {\n if (keywords.has(label))\n return true\n return getLabelRegex(keywords).test(label)\n}\n\n/**\n * Classify an issue by its labels into a type useful for skill generation\n */\nexport function classifyIssue(labels: string[]): IssueType {\n const lower = labels.map(l => l.toLowerCase())\n if (lower.some(l => labelMatchesAny(l, BUG_LABELS)))\n return 'bug'\n if (lower.some(l => labelMatchesAny(l, QUESTION_LABELS)))\n return 'question'\n if (lower.some(l => labelMatchesAny(l, DOCS_LABELS)))\n return 'docs'\n if (lower.some(l => labelMatchesAny(l, FEATURE_LABELS)))\n return 'feature'\n return 'other'\n}\n\n/**\n * Check if an issue should be filtered out entirely\n */\nfunction isNoiseIssue(issue: { labels: string[], title: string, body: string }): boolean {\n const lower = issue.labels.map(l => l.toLowerCase())\n if (lower.some(l => labelMatchesAny(l, NOISE_LABELS)))\n return true\n // Tracking/umbrella issues — low signal for skill generation\n if (issue.title.startsWith('☂️') || issue.title.startsWith('[META]') || issue.title.startsWith('[Tracking]'))\n return true\n return false\n}\n\n/**\n * Detect non-technical issues: fan mail, showcases, sentiment.\n * Short body + no code + high reactions = likely non-technical.\n * Note: roadmap/tracking issues are NOT filtered — they get score-boosted instead.\n */\nexport function isNonTechnical(issue: { body: string, title: string, reactions: number }): boolean {\n const body = (issue.body || '').trim()\n // Very short body with no code — probably sentiment/meta\n if (body.length < 200 && !hasCodeBlock(body) && issue.reactions > 50)\n return true\n // Sentiment patterns (love letters, fan mail)\n if (STATIC_REGEX_1.test(issue.title) && !hasCodeBlock(body))\n return true\n return false\n}\n\n/**\n * Freshness-weighted score: reactions * decay(age_in_years)\n * Steep decay so recent issues dominate over old high-reaction ones.\n * At 0.6: 1yr=0.63x, 2yr=0.45x, 4yr=0.29x, 6yr=0.22x\n */\nexport function freshnessScore(reactions: number, createdAt: string): number {\n const ageMs = Date.now() - new Date(createdAt).getTime()\n const ageYears = ageMs / (365.25 * 24 * 60 * 60 * 1000)\n return reactions * (1 / (1 + ageYears * 0.6))\n}\n\n/**\n * Type quotas — guarantee a mix of issue types.\n * Bugs and questions get priority; feature requests are hard-capped.\n */\nfunction applyTypeQuotas(issues: GitHubIssue[], limit: number): GitHubIssue[] {\n const byType = new Map<IssueType, GitHubIssue[]>()\n for (const issue of issues) {\n mapInsert(byType, issue.type, () => []).push(issue)\n }\n\n // Sort each group by score\n for (const group of byType.values())\n group.sort((a, b) => b.score - a.score)\n\n // Allocate slots: bugs 40%, questions 30%, docs 15%, features 10%, other 5%\n const quotas: [IssueType, number][] = [\n ['bug', Math.ceil(limit * 0.40)],\n ['question', Math.ceil(limit * 0.30)],\n ['docs', Math.ceil(limit * 0.15)],\n ['feature', Math.ceil(limit * 0.10)],\n ['other', Math.ceil(limit * 0.05)],\n ]\n\n const selected: GitHubIssue[] = []\n const used = new Set<number>()\n let remaining = limit\n\n // First pass: fill each type up to its quota\n for (const [type, quota] of quotas) {\n const group = byType.get(type) || []\n const take = Math.min(quota, group.length, remaining)\n for (let i = 0; i < take; i++) {\n selected.push(group[i]!)\n used.add(group[i]!.number)\n remaining--\n }\n }\n\n // Second pass: fill remaining slots from best-scored unused issues (any type except feature)\n if (remaining > 0) {\n const unused = issues\n .filter(i => !used.has(i.number) && i.type !== 'feature')\n .sort((a, b) => b.score - a.score)\n for (const issue of unused) {\n if (remaining <= 0)\n break\n selected.push(issue)\n remaining--\n }\n }\n\n return selected.sort((a, b) => b.score - a.score)\n}\n\n/**\n * Body truncation limit based on reactions — high-reaction issues deserve more space\n */\nfunction bodyLimit(reactions: number): number {\n if (reactions >= 10)\n return 2000\n if (reactions >= 5)\n return 1500\n return 800\n}\n\n/**\n * Fetch issues for a state using GitHub Search API sorted by reactions\n */\nfunction fetchIssuesByState(\n owner: string,\n repo: string,\n state: 'open' | 'closed',\n count: number,\n releasedAt?: string,\n fromDate?: string,\n): GitHubIssue[] {\n const fetchCount = Math.min(count * 3, 100)\n let datePart = ''\n if (fromDate) {\n // Explicit lower bound: only issues from this date onward\n datePart = state === 'closed'\n ? `+closed:>=${fromDate}`\n : `+created:>=${fromDate}`\n }\n else if (state === 'closed') {\n if (releasedAt) {\n // Lower bound: skip issues already fixed before this release\n datePart = `+closed:>=${isoDate(releasedAt)}`\n // Upper cap for old versions so we don't pull hundreds of issues\n const cap = new Date(releasedAt)\n cap.setMonth(cap.getMonth() + 6)\n if (cap < new Date()) {\n datePart += `+closed:<=${isoDate(cap.toISOString())}`\n }\n }\n else {\n datePart = `+closed:>${oneYearAgo()}`\n }\n }\n else if (releasedAt) {\n // For older versions, only include issues created around or before release\n const date = new Date(releasedAt)\n date.setMonth(date.getMonth() + 6)\n datePart = `+created:<=${isoDate(date.toISOString())}`\n }\n\n const q = `repo:${owner}/${repo}+is:issue+is:${state}${datePart}`\n\n const { stdout: result } = spawnSync('gh', [\n 'api',\n `search/issues?q=${q}&sort=reactions&order=desc&per_page=${fetchCount}`,\n '-q',\n '.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}',\n ], { encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024 })\n\n if (!result)\n return []\n\n return result\n .trim()\n .split('\\n')\n .filter(Boolean)\n .map(line => JSON.parse(line) as GitHubIssue & { user: string, userType: string, authorAssociation: string })\n .filter(issue => !BOT_USERS.has(issue.user) && issue.userType !== 'Bot')\n .filter(issue => !isNoiseIssue(issue))\n .filter(issue => !isNonTechnical(issue))\n .map(({ user: _, userType: __, authorAssociation, ...issue }) => {\n const isMaintainer = ['OWNER', 'MEMBER', 'COLLABORATOR'].includes(authorAssociation)\n const isRoadmap = STATIC_REGEX_2.test(issue.title) || issue.labels.some(l => STATIC_REGEX_3.test(l))\n return {\n ...issue,\n type: classifyIssue(issue.labels),\n topComments: [] as IssueComment[],\n score: freshnessScore(issue.reactions, issue.createdAt) * (isMaintainer && isRoadmap ? 5 : 1),\n }\n })\n .sort((a, b) => b.score - a.score)\n .slice(0, count)\n}\n\nfunction oneYearAgo(): string {\n const d = new Date()\n d.setFullYear(d.getFullYear() - 1)\n return isoDate(d.toISOString())!\n}\n\n/**\n * Batch-fetch top comments for issues via GraphQL.\n * Enriches the top N highest-score issues with their best comments.\n * Prioritizes: comments with code blocks, from maintainers, with high reactions.\n * Filters out \"+1\", \"any updates?\", \"same here\" noise.\n */\nfunction enrichWithComments(owner: string, repo: string, issues: GitHubIssue[], topN = 15): void {\n // Only fetch comments for issues worth enriching\n const worth = issues\n .filter(i => i.comments > 0 && (i.type === 'bug' || i.type === 'question' || i.reactions >= 3))\n .sort((a, b) => b.score - a.score)\n .slice(0, topN)\n\n if (worth.length === 0)\n return\n\n // Build a single GraphQL query fetching comments for all selected issues\n // Fetch more comments (10) so we can filter noise and pick the best\n const fragments = worth.map((issue, i) =>\n `i${i}: issue(number: ${issue.number}) { comments(first: 10) { nodes { body author { login } authorAssociation reactions { totalCount } } } }`,\n ).join(' ')\n\n const query = `query($owner: String!, $repo: String!) { repository(owner: $owner, name: $repo) { ${fragments} } }`\n\n try {\n const { stdout: result } = spawnSync('gh', [\n 'api',\n 'graphql',\n '-f',\n `query=${query}`,\n '-f',\n `owner=${owner}`,\n '-f',\n `repo=${repo}`,\n ], { encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024 })\n\n if (!result)\n return\n\n const data = JSON.parse(result)\n const repo_ = data?.data?.repository\n if (!repo_)\n return\n\n for (let i = 0; i < worth.length; i++) {\n const nodes = repo_[`i${i}`]?.comments?.nodes\n if (!Array.isArray(nodes))\n continue\n\n const issue = worth[i]!\n\n const comments: (IssueComment & { _score: number })[] = nodes\n .filter((c: any) => c.author && !BOT_USERS.has(c.author.login))\n .filter((c: any) => !COMMENT_NOISE_RE.test((c.body || '').trim()))\n .map((c: any) => {\n const isMaintainer = ['OWNER', 'MEMBER', 'COLLABORATOR'].includes(c.authorAssociation)\n const body = c.body || ''\n const reactions = c.reactions?.totalCount || 0\n // Score: maintainers get 3x, code blocks get 2x, reactions add linearly\n const _score = (isMaintainer ? 3 : 1) * (hasCodeBlock(body) ? 2 : 1) * (1 + reactions)\n return { body, author: c.author.login, reactions, isMaintainer, _score }\n })\n .sort((a: any, b: any) => b._score - a._score)\n\n // Take top 3 quality comments\n issue.topComments = comments.slice(0, 3).map(({ _score: _, ...c }) => c)\n\n // For closed issues: try to detect fix version from maintainer comments\n if (issue.state === 'closed') {\n issue.resolvedIn = detectResolvedVersion(comments)\n }\n }\n }\n catch {\n // Non-critical — issues still useful without comments\n }\n}\n\n/**\n * Try to detect which version fixed a closed issue from maintainer comments.\n * Looks for version patterns in maintainer/collaborator comments.\n */\nfunction detectResolvedVersion(comments: IssueComment[]): string | undefined {\n const maintainerComments = comments.filter(c => c.isMaintainer)\n // Check from last to first (fix announcements tend to be later)\n for (const c of maintainerComments.reverse()) {\n // \"Fixed in v5.2\", \"landed in 4.1.0\", \"released in v3.0\", \"available in 2.1\"\n const match = c.body.match(STATIC_REGEX_4)\n if (match)\n return match[1]\n // \"v5.2.0\" or \"5.2.0\" at start of a short comment (release note style)\n if (c.body.length < 100) {\n const vMatch = c.body.match(STATIC_REGEX_5)\n if (vMatch)\n return vMatch[1]\n }\n }\n return undefined\n}\n\n/**\n * Fetch issues from a GitHub repo with freshness-weighted scoring and type quotas.\n * Returns a balanced mix: bugs > questions > docs > other > features.\n * Filters noise, non-technical content, and enriches with quality comments.\n */\nexport async function fetchGitHubIssues(\n owner: string,\n repo: string,\n limit = 30,\n releasedAt?: string,\n fromDate?: string,\n): Promise<GitHubIssue[]> {\n if (!isGhAvailable())\n return []\n\n const openCount = Math.ceil(limit * 0.75)\n const closedCount = limit - openCount\n\n try {\n // Fetch more than needed so type quotas have a pool to draw from\n const open = fetchIssuesByState(owner, repo, 'open', Math.min(openCount * 2, 100), releasedAt, fromDate)\n const closed = fetchIssuesByState(owner, repo, 'closed', Math.min(closedCount * 2, 50), releasedAt, fromDate)\n const all = [...open, ...closed]\n const selected = applyTypeQuotas(all, limit)\n enrichWithComments(owner, repo, selected)\n return selected\n }\n catch {\n return []\n }\n}\n\n/**\n * Format a single issue as markdown with YAML frontmatter\n */\nexport function formatIssueAsMarkdown(issue: GitHubIssue): string {\n const limit = bodyLimit(issue.reactions)\n const fmFields: Record<string, string | number | boolean | undefined> = {\n number: issue.number,\n title: issue.title,\n type: issue.type,\n state: issue.state,\n created: isoDate(issue.createdAt),\n url: issue.url,\n reactions: issue.reactions,\n comments: issue.comments,\n }\n if (issue.resolvedIn)\n fmFields.resolvedIn = issue.resolvedIn\n if (issue.labels.length > 0)\n fmFields.labels = `[${issue.labels.join(', ')}]`\n const fm = buildFrontmatter(fmFields)\n\n const lines = [fm, '', `# ${issue.title}`]\n\n if (issue.body) {\n const body = truncateBody(issue.body, limit)\n lines.push('', body)\n }\n\n if (issue.topComments.length > 0) {\n lines.push('', '---', '', '## Top Comments')\n for (const c of issue.topComments) {\n const reactions = c.reactions > 0 ? ` (+${c.reactions})` : ''\n const maintainer = c.isMaintainer ? ' [maintainer]' : ''\n const commentBody = truncateBody(c.body, 600)\n lines.push('', `**@${c.author}**${maintainer}${reactions}:`, '', commentBody)\n }\n }\n\n return lines.join('\\n')\n}\n\n/**\n * Generate a summary index of all issues for quick LLM scanning.\n * Groups by type so the LLM can quickly find bugs vs questions.\n */\nexport function generateIssueIndex(issues: GitHubIssue[]): string {\n const byType = new Map<IssueType, GitHubIssue[]>()\n for (const issue of issues) {\n mapInsert(byType, issue.type, () => []).push(issue)\n }\n\n const typeLabels: Record<IssueType, string> = {\n bug: 'Bugs & Regressions',\n question: 'Questions & Usage Help',\n docs: 'Documentation',\n feature: 'Feature Requests',\n other: 'Other',\n }\n\n const typeOrder: IssueType[] = ['bug', 'question', 'docs', 'other', 'feature']\n\n const fm = [\n '---',\n `total: ${issues.length}`,\n `open: ${issues.filter(i => i.state === 'open').length}`,\n `closed: ${issues.filter(i => i.state !== 'open').length}`,\n '---',\n ]\n\n const sections: string[] = [fm.join('\\n'), '', '# Issues Index', '']\n\n for (const type of typeOrder) {\n const group = byType.get(type)\n if (!group?.length)\n continue\n sections.push(`## ${typeLabels[type]} (${group.length})`, '')\n for (const issue of group) {\n const reactions = issue.reactions > 0 ? ` (+${issue.reactions})` : ''\n const state = issue.state === 'open' ? '' : ' [closed]'\n const resolved = issue.resolvedIn ? ` [fixed in ${issue.resolvedIn}]` : ''\n const date = isoDate(issue.createdAt)\n sections.push(`- [#${issue.number}](./issue-${issue.number}.md): ${issue.title}${reactions}${state}${resolved} (${date})`)\n }\n sections.push('')\n }\n\n return sections.join('\\n')\n}\n","/**\n * llms.txt fetching and parsing\n */\n\nimport type { FetchedDoc, LlmsContent, LlmsLink } from './types.ts'\nimport pLimit from 'p-limit'\nimport { extractLinks } from '../core/markdown.ts'\nimport { TRAILING_SLASH_RE } from '../core/regex.ts'\nimport { isSafeUrl } from '../core/url.ts'\nimport { fetchText, verifyUrl } from './utils.ts'\n\nconst STATIC_REGEX_2 = /\\n---\\n/\nconst STATIC_REGEX_3 = /^url: *(\\S.*)$/m\n\n/**\n * Check for llms.txt at a docs URL, returns the llms.txt URL if found\n */\nexport async function fetchLlmsUrl(docsUrl: string): Promise<string | null> {\n const origin = new URL(docsUrl).origin\n const llmsUrl = `${origin}/llms.txt`\n if (await verifyUrl(llmsUrl))\n return llmsUrl\n return null\n}\n\n/**\n * Fetch and parse llms.txt content\n */\nexport async function fetchLlmsTxt(url: string): Promise<LlmsContent | null> {\n const content = await fetchText(url)\n if (!content || content.length < 50)\n return null\n\n return {\n raw: content,\n links: parseMarkdownLinks(content),\n }\n}\n\n/**\n * Parse markdown links from llms.txt to get .md file paths\n */\nexport function parseMarkdownLinks(content: string): LlmsLink[] {\n return extractLinks(content).filter(l => l.url.endsWith('.md'))\n}\n\nexport async function downloadLlmsDocs(\n llmsContent: LlmsContent,\n baseUrl: string,\n onProgress?: (url: string, index: number, total: number) => void,\n): Promise<FetchedDoc[]> {\n const limit = pLimit(5)\n let completed = 0\n\n const results = await Promise.all(\n llmsContent.links.map(link => limit(async () => {\n const url = link.url.startsWith('http')\n ? link.url\n : `${baseUrl.replace(TRAILING_SLASH_RE, '')}${link.url.startsWith('/') ? '' : '/'}${link.url}`\n\n if (!isSafeUrl(url))\n return null\n\n const content = await fetchText(url)\n onProgress?.(link.url, ++completed, llmsContent.links.length)\n if (content && content.length > 100) {\n // Normalize full URLs to just the pathname for cache paths\n const docUrl = link.url.startsWith('http') ? new URL(link.url).pathname : link.url\n return { url: docUrl, title: link.title, content } as FetchedDoc\n }\n return null\n })),\n )\n\n return results.filter((d): d is FetchedDoc => d !== null)\n}\n\n/**\n * Normalize llms.txt links to relative paths for local access\n * Handles: absolute URLs, root-relative paths, and relative paths\n */\nexport function normalizeLlmsLinks(content: string, baseUrl?: string): string {\n let normalized = content\n\n // Handle absolute URLs: https://example.com/docs/foo.md → ./docs/foo.md\n if (baseUrl) {\n const base = baseUrl.replace(TRAILING_SLASH_RE, '')\n const escaped = base.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n normalized = normalized.replace(\n new RegExp(`\\\\]\\\\(${escaped}(/[^)]+\\\\.md)\\\\)`, 'g'),\n '](./docs$1)',\n )\n }\n\n // Handle root-relative paths: /foo.md → ./docs/foo.md\n normalized = normalized.replace(/\\]\\(\\/([^)]+\\.md)\\)/g, '](./docs/$1)')\n\n return normalized\n}\n\n/**\n * Extract sections from llms-full.txt by URL patterns\n * Format: ---\\nurl: /path.md\\n---\\n<content>\\n\\n---\\nurl: ...\n */\nexport function extractSections(content: string, patterns: string[]): string | null {\n const sections: string[] = []\n const parts = content.split(STATIC_REGEX_2)\n\n for (const part of parts) {\n const urlMatch = part.match(STATIC_REGEX_3)\n if (!urlMatch)\n continue\n\n const url = urlMatch[1]!\n if (patterns.some(p => url.includes(p))) {\n const contentStart = part.indexOf('\\n', part.indexOf('url:'))\n if (contentStart > -1) {\n sections.push(part.slice(contentStart + 1))\n }\n }\n }\n\n if (sections.length === 0)\n return null\n return sections.join('\\n\\n---\\n\\n')\n}\n","/**\n * GitHub repo resolution: search, metadata, README, source files, full ResolvedPackage build.\n *\n * Tag/version logic lives in `./github-tags.ts`; doc discovery + fetch in `./github-docs.ts`.\n * Re-exports the doc-side public surface for back-compat with existing importers.\n */\n\nimport type { ResolvedPackage } from './types.ts'\nimport { spawnSync } from 'node:child_process'\nimport { existsSync as fsExistsSync, readFileSync as fsReadFileSync } from 'node:fs'\nimport { fileURLToPath } from 'node:url'\nimport { NPM_SCOPE_PREFIX_RE, NPM_SCOPE_WITH_SLASH_RE, V_PREFIX_RE } from '../core/regex.ts'\nimport { parseGitHubUrl } from '../core/url.ts'\nimport { getGitHubToken, ghApi, isKnownPrivateRepo, markRepoPrivate } from './github-common.ts'\nimport { fetchGitDocs } from './github-docs.ts'\nimport { fetchUnghReleases } from './github-tags.ts'\nimport { isGhAvailable } from './issues.ts'\nimport { fetchLlmsUrl } from './llms.ts'\nimport { getDocOverride } from './package-registry.ts'\nimport { $fetch, fetchGitHubRaw, fetchText } from './utils.ts'\n\n// Re-export doc-side public surface (callers used to import these from here)\nexport type { GitDocsResult } from './github-docs.ts'\nexport { fetchGitDocs, filterFrameworkDocs, isShallowGitDocs, MIN_GIT_DOCS, validateGitDocsWithLlms } from './github-docs.ts'\n\n/**\n * Verify a GitHub repo is the source for an npm package by checking package.json name field.\n * Checks root first, then common monorepo paths (packages/{shortName}, packages/{name}).\n */\nasync function verifyNpmRepo(owner: string, repo: string, packageName: string): Promise<boolean> {\n const base = `https://raw.githubusercontent.com/${owner}/${repo}/HEAD`\n const shortName = packageName.replace(NPM_SCOPE_WITH_SLASH_RE, '')\n const paths = [\n 'package.json',\n `packages/${shortName}/package.json`,\n `packages/${packageName.replace(NPM_SCOPE_PREFIX_RE, '').replace('/', '-')}/package.json`,\n ]\n for (const path of paths) {\n const text = await fetchGitHubRaw(`${base}/${path}`)\n if (!text)\n continue\n try {\n const pkg = JSON.parse(text) as { name?: string }\n if (pkg.name === packageName)\n return true\n }\n catch {}\n }\n return false\n}\n\nexport async function searchGitHubRepo(packageName: string): Promise<string | null> {\n // Try ungh heuristic first — check if repo name matches package name\n const shortName = packageName.replace(NPM_SCOPE_WITH_SLASH_RE, '')\n for (const candidate of [packageName.replace(NPM_SCOPE_PREFIX_RE, '').replace('/', '/'), shortName]) {\n if (!candidate.includes('/')) {\n const unghRes = await $fetch.raw(`https://ungh.cc/repos/${shortName}/${shortName}`).catch(() => null)\n if (unghRes?.ok)\n return `https://github.com/${shortName}/${shortName}`\n continue\n }\n const unghRes = await $fetch.raw(`https://ungh.cc/repos/${candidate}`).catch(() => null)\n if (unghRes?.ok)\n return `https://github.com/${candidate}`\n }\n\n // Try gh CLI — strip @ to avoid GitHub search syntax issues\n const searchTerm = packageName.replace(NPM_SCOPE_PREFIX_RE, '')\n if (isGhAvailable()) {\n try {\n const { stdout: json } = spawnSync('gh', ['search', 'repos', searchTerm, '--json', 'fullName', '--limit', '5'], {\n encoding: 'utf-8',\n timeout: 15_000,\n })\n if (!json)\n throw new Error('no output')\n const repos = JSON.parse(json) as Array<{ fullName: string }>\n const match = repos.find(r =>\n r.fullName.toLowerCase().endsWith(`/${packageName.toLowerCase()}`)\n || r.fullName.toLowerCase().endsWith(`/${shortName.toLowerCase()}`),\n )\n if (match)\n return `https://github.com/${match.fullName}`\n for (const candidate of repos) {\n const gh = parseGitHubUrl(`https://github.com/${candidate.fullName}`)\n if (gh && await verifyNpmRepo(gh.owner, gh.repo, packageName))\n return `https://github.com/${candidate.fullName}`\n }\n }\n catch {\n // fall through to REST API\n }\n }\n\n // Fallback: GitHub REST search API (no auth needed, but rate-limited)\n const query = encodeURIComponent(`${searchTerm} in:name`)\n const data = await $fetch<{ items?: Array<{ full_name: string }> }>(\n `https://api.github.com/search/repositories?q=${query}&per_page=5`,\n ).catch(() => null)\n if (!data?.items?.length)\n return null\n\n const match = data.items.find(r =>\n r.full_name.toLowerCase().endsWith(`/${packageName.toLowerCase()}`)\n || r.full_name.toLowerCase().endsWith(`/${shortName.toLowerCase()}`),\n )\n if (match)\n return `https://github.com/${match.full_name}`\n\n for (const candidate of data.items) {\n const gh = parseGitHubUrl(`https://github.com/${candidate.full_name}`)\n if (gh && await verifyNpmRepo(gh.owner, gh.repo, packageName))\n return `https://github.com/${candidate.full_name}`\n }\n\n return null\n}\n\n/**\n * Fetch GitHub repo metadata to get website URL.\n * Pass packageName to check doc overrides first (avoids API call).\n */\nexport async function fetchGitHubRepoMeta(owner: string, repo: string, packageName?: string): Promise<{ homepage?: string } | null> {\n const override = packageName ? getDocOverride(packageName) : undefined\n if (override?.homepage)\n return { homepage: override.homepage }\n\n const data = await ghApi<{ homepage?: string }>(`repos/${owner}/${repo}`)\n ?? await $fetch<{ homepage?: string }>(`https://api.github.com/repos/${owner}/${repo}`).catch(() => null)\n return data?.homepage ? { homepage: data.homepage } : null\n}\n\n/** Resolve README URL for a GitHub repo, returns ungh:// pseudo-URL or raw URL */\nexport async function fetchReadme(owner: string, repo: string, subdir?: string, ref?: string): Promise<string | null> {\n const branch = ref || 'main'\n\n if (!isKnownPrivateRepo(owner, repo)) {\n const unghUrl = subdir\n ? `https://ungh.cc/repos/${owner}/${repo}/files/${branch}/${subdir}/README.md`\n : `https://ungh.cc/repos/${owner}/${repo}/readme${ref ? `?ref=${ref}` : ''}`\n\n const unghRes = await $fetch.raw(unghUrl).catch(() => null)\n\n if (unghRes?.ok) {\n return `ungh://${owner}/${repo}${subdir ? `/${subdir}` : ''}${ref ? `@${ref}` : ''}`\n }\n }\n\n // Fallback to raw.githubusercontent.com — use GET instead of HEAD\n // because raw.githubusercontent.com sometimes returns HTML on HEAD for valid URLs\n const basePath = subdir ? `${subdir}/` : ''\n const branches = ref ? [ref] : ['main', 'master']\n const token = isKnownPrivateRepo(owner, repo) ? getGitHubToken() : null\n const authHeaders: HeadersInit = token ? { Authorization: `token ${token}` } : {}\n for (const b of branches) {\n for (const filename of ['README.md', 'Readme.md', 'readme.md']) {\n const readmeUrl = `https://raw.githubusercontent.com/${owner}/${repo}/${b}/${basePath}${filename}`\n const res = await $fetch.raw(readmeUrl, { headers: authHeaders }).catch(() => null)\n if (res?.ok)\n return readmeUrl\n }\n }\n\n // Last resort: GitHub API (handles private repos via token auth)\n const refParam = ref ? `?ref=${ref}` : ''\n const endpoint = subdir\n ? `repos/${owner}/${repo}/contents/${subdir}/README.md${refParam}`\n : `repos/${owner}/${repo}/readme${refParam}`\n const apiData = await ghApi<{ download_url?: string }>(endpoint)\n if (apiData?.download_url) {\n markRepoPrivate(owner, repo)\n return apiData.download_url\n }\n\n return null\n}\n\n/** Fetch README content from ungh:// pseudo-URL, file:// URL, or regular URL */\nexport async function fetchReadmeContent(url: string): Promise<string | null> {\n if (url.startsWith('file://')) {\n const filePath = fileURLToPath(url)\n if (!fsExistsSync(filePath))\n return null\n return fsReadFileSync(filePath, 'utf-8')\n }\n\n if (url.startsWith('ungh://')) {\n let path = url.replace('ungh://', '')\n let ref = 'main'\n\n const atIdx = path.lastIndexOf('@')\n if (atIdx !== -1) {\n ref = path.slice(atIdx + 1)\n path = path.slice(0, atIdx)\n }\n\n const parts = path.split('/')\n const owner = parts[0]\n const repo = parts[1]\n const subdir = parts.slice(2).join('/')\n\n const unghUrl = subdir\n ? `https://ungh.cc/repos/${owner}/${repo}/files/${ref}/${subdir}/README.md`\n : `https://ungh.cc/repos/${owner}/${repo}/readme?ref=${ref}`\n\n const text = await $fetch(unghUrl, { responseType: 'text' }).catch(() => null)\n if (!text)\n return null\n\n try {\n const json = JSON.parse(text) as { markdown?: string, file?: { contents?: string } }\n return json.markdown || json.file?.contents || null\n }\n catch {\n return text\n }\n }\n\n if (url.includes('raw.githubusercontent.com'))\n return fetchGitHubRaw(url)\n\n return fetchText(url)\n}\n\n/**\n * Resolve a GitHub repo into a ResolvedPackage (no npm registry needed).\n * Fetches repo meta, latest release version, git docs, README, and llms.txt.\n */\nexport async function resolveGitHubRepo(\n owner: string,\n repo: string,\n onProgress?: (msg: string) => void,\n): Promise<ResolvedPackage | null> {\n onProgress?.('Fetching repo metadata')\n\n const repoUrl = `https://github.com/${owner}/${repo}`\n const meta = await ghApi<{ homepage?: string, description?: string }>(`repos/${owner}/${repo}`)\n ?? await $fetch<{ homepage?: string, description?: string }>(`https://api.github.com/repos/${owner}/${repo}`).catch(() => null)\n const homepage = meta?.homepage || undefined\n const description = meta?.description || undefined\n\n onProgress?.('Fetching latest release')\n const releases = await fetchUnghReleases(owner, repo)\n\n let version = 'main'\n let releasedAt: string | undefined\n const latestRelease = releases[0]\n if (latestRelease) {\n version = latestRelease.tag.replace(V_PREFIX_RE, '')\n releasedAt = latestRelease.publishedAt\n }\n\n onProgress?.('Resolving docs')\n const gitDocs = await fetchGitDocs(owner, repo, version)\n const gitDocsUrl = gitDocs ? `${repoUrl}/tree/${gitDocs.ref}/docs` : undefined\n const gitRef = gitDocs?.ref\n\n onProgress?.('Fetching README')\n const readmeUrl = await fetchReadme(owner, repo)\n\n let llmsUrl: string | undefined\n if (homepage) {\n onProgress?.('Checking llms.txt')\n llmsUrl = await fetchLlmsUrl(homepage).catch(() => null) ?? undefined\n }\n\n if (!gitDocsUrl && !readmeUrl && !llmsUrl)\n return null\n\n return {\n name: repo,\n version: latestRelease ? version : undefined,\n releasedAt,\n description,\n repoUrl,\n docsUrl: homepage,\n gitDocsUrl,\n gitRef,\n gitDocsFallback: gitDocs?.fallback,\n readmeUrl: readmeUrl ?? undefined,\n llmsUrl,\n }\n}\n","import type { ResolveAttempt, ResolvedPackage, ResolveResult } from './types.ts'\nimport { isLikelyCodeHostUrl, isUselessDocsUrl, normalizeRepoUrl, parseGitHubUrl } from '../core/url.ts'\nimport { resolveGitHubRepo } from './github.ts'\nimport { fetchLlmsUrl } from './llms.ts'\nimport { $fetch, createRateLimitedRunner } from './utils.ts'\n\nconst VALID_CRATE_NAME = /^[a-z0-9][\\w-]*$/\nconst runCratesApiRateLimited = createRateLimitedRunner(1000)\n\ninterface CratesApiResponse {\n crate?: {\n id?: string\n name?: string\n description?: string\n homepage?: string | null\n documentation?: string | null\n repository?: string | null\n max_version?: string\n newest_version?: string\n max_stable_version?: string\n default_version?: string\n updated_at?: string\n }\n versions?: Array<{\n num?: string\n yanked?: boolean\n created_at?: string\n description?: string | null\n homepage?: string | null\n documentation?: string | null\n repository?: string | null\n }>\n}\n\nfunction selectCrateVersion(\n data: CratesApiResponse,\n requestedVersion?: string,\n): { version: string, entry?: NonNullable<CratesApiResponse['versions']>[number] } | null {\n const versions = data.versions || []\n\n if (requestedVersion) {\n const exact = versions.find(v => v.num === requestedVersion && !v.yanked)\n if (exact?.num)\n return { version: exact.num, entry: exact }\n }\n\n const crate = data.crate\n const preferred = [\n crate?.max_stable_version,\n crate?.newest_version,\n crate?.max_version,\n crate?.default_version,\n ].find(Boolean)\n\n if (preferred) {\n const match = versions.find(v => v.num === preferred && !v.yanked)\n if (match?.num)\n return { version: preferred, entry: match }\n if (versions.length === 0)\n return { version: preferred }\n }\n\n const firstStable = versions.find(v => !v.yanked && v.num)\n if (firstStable?.num)\n return { version: firstStable.num, entry: firstStable }\n\n return null\n}\n\nfunction pickPreferredUrl(...urls: Array<string | null | undefined>): string | undefined {\n return urls.map(v => v?.trim()).find(v => !!v)\n}\n\nasync function fetchCratesApi<T>(url: string): Promise<T | null> {\n return runCratesApiRateLimited(() => $fetch<T>(url).catch(() => null))\n}\n\nexport async function resolveCrateDocsWithAttempts(\n crateName: string,\n options: { version?: string, onProgress?: (message: string) => void } = {},\n): Promise<ResolveResult> {\n const attempts: ResolveAttempt[] = []\n const onProgress = options.onProgress\n const normalizedName = crateName.trim().toLowerCase()\n\n if (!normalizedName || !VALID_CRATE_NAME.test(normalizedName)) {\n attempts.push({\n source: 'crates',\n status: 'error',\n message: `Invalid crate name: ${crateName}`,\n })\n return { package: null, attempts }\n }\n\n onProgress?.('crates.io metadata')\n const apiUrl = `https://crates.io/api/v1/crates/${encodeURIComponent(normalizedName)}`\n const data = await fetchCratesApi<CratesApiResponse>(apiUrl)\n\n if (!data?.crate) {\n attempts.push({\n source: 'crates',\n url: apiUrl,\n status: 'not-found',\n message: 'Crate not found on crates.io',\n })\n return { package: null, attempts }\n }\n\n attempts.push({\n source: 'crates',\n url: apiUrl,\n status: 'success',\n message: `Found crate: ${data.crate.name || normalizedName}`,\n })\n\n const selected = selectCrateVersion(data, options.version)\n if (!selected) {\n attempts.push({\n source: 'crates',\n url: apiUrl,\n status: 'error',\n message: 'No usable crate versions found',\n })\n return { package: null, attempts }\n }\n\n const version = selected.version\n const versionEntry = selected.entry\n const docsRsUrl = `https://docs.rs/${encodeURIComponent(normalizedName)}/${encodeURIComponent(version)}`\n\n const repositoryRaw = pickPreferredUrl(versionEntry?.repository, data.crate.repository)\n const homepage = pickPreferredUrl(versionEntry?.homepage, data.crate.homepage)\n const documentation = pickPreferredUrl(versionEntry?.documentation, data.crate.documentation)\n const normalizedRepo = repositoryRaw ? normalizeRepoUrl(repositoryRaw) : undefined\n const repoUrl = normalizedRepo && isLikelyCodeHostUrl(normalizedRepo)\n ? normalizedRepo\n : isLikelyCodeHostUrl(homepage)\n ? homepage\n : undefined\n\n let resolved: ResolvedPackage = {\n name: normalizedName,\n version,\n releasedAt: versionEntry?.created_at || data.crate.updated_at || undefined,\n description: versionEntry?.description || data.crate.description,\n docsUrl: (() => {\n if (documentation && !isUselessDocsUrl(documentation) && !isLikelyCodeHostUrl(documentation))\n return documentation\n if (homepage && !isUselessDocsUrl(homepage) && !isLikelyCodeHostUrl(homepage))\n return homepage\n return docsRsUrl\n })(),\n repoUrl,\n }\n\n const gh = repoUrl ? parseGitHubUrl(repoUrl) : null\n if (gh) {\n onProgress?.('GitHub enrichment')\n const ghResolved = await resolveGitHubRepo(gh.owner, gh.repo)\n if (ghResolved) {\n attempts.push({\n source: 'github-meta',\n url: repoUrl,\n status: 'success',\n message: 'Enriched via GitHub repo metadata',\n })\n resolved = {\n ...ghResolved,\n name: normalizedName,\n version,\n releasedAt: resolved.releasedAt || ghResolved.releasedAt,\n description: resolved.description || ghResolved.description,\n docsUrl: resolved.docsUrl || ghResolved.docsUrl,\n repoUrl,\n readmeUrl: ghResolved.readmeUrl || resolved.readmeUrl,\n }\n }\n else {\n attempts.push({\n source: 'github-meta',\n url: repoUrl,\n status: 'not-found',\n message: 'GitHub enrichment failed, using crates.io metadata',\n })\n }\n }\n\n if (!resolved.llmsUrl && resolved.docsUrl) {\n onProgress?.('llms.txt discovery')\n resolved.llmsUrl = await fetchLlmsUrl(resolved.docsUrl).catch(() => null) ?? undefined\n if (resolved.llmsUrl) {\n attempts.push({\n source: 'llms.txt',\n url: resolved.llmsUrl,\n status: 'success',\n })\n }\n }\n\n return { package: resolved, attempts }\n}\n","/**\n * Website crawl doc source — fetches docs by crawling a URL pattern\n */\n\nimport { rmSync } from 'node:fs'\nimport { tmpdir } from 'node:os'\nimport { crawlAndGenerate } from '@mdream/crawl'\nimport { join } from 'pathe'\nimport { TRAILING_SLASH_RE } from '../core/regex.ts'\n\nconst STATIC_REGEX_2 = /[_.:-]/\nconst STATIC_REGEX_3 = /\\/+$/\n\n/**\n * Crawl a URL pattern and return docs as cached doc format.\n * Uses HTTP crawler (no browser needed) with sitemap discovery + glob filtering.\n *\n * @param url - URL with optional glob pattern (e.g. 'https://example.com/docs/**')\n * @param onProgress - Optional progress callback\n * @param maxPages - Max pages to crawl (default 200)\n */\nexport async function fetchCrawledDocs(\n url: string,\n onProgress?: (message: string) => void,\n maxPages = 200,\n): Promise<Array<{ path: string, content: string }>> {\n const outputDir = join(tmpdir(), 'skilld-crawl', Date.now().toString())\n\n onProgress?.(`Crawling ${url}`)\n\n // Keep pages matching user's locale + English\n const userLang = getUserLang()\n const foreignUrls = new Set<string>()\n\n const doCrawl = () => crawlAndGenerate({\n urls: [url],\n outputDir,\n driver: 'http',\n generateLlmsTxt: false,\n generateIndividualMd: true,\n maxRequestsPerCrawl: maxPages,\n onPage: (page) => {\n const lang = extractHtmlLang(page.html)\n if (lang && !lang.startsWith('en') && !lang.startsWith(userLang))\n foreignUrls.add(page.url)\n },\n }, (progress) => {\n if (progress.crawling.status === 'processing' && progress.crawling.total > 0) {\n onProgress?.(`Crawling ${progress.crawling.processed}/${progress.crawling.total} pages`)\n }\n })\n\n let results = await doCrawl().catch((err) => {\n onProgress?.(`Crawl failed: ${err?.message || err}`)\n return []\n })\n // Retry once on transient failure (e.g. sitemap timeout)\n if (results.length === 0) {\n onProgress?.('Retrying crawl')\n results = await doCrawl().catch(() => [])\n }\n\n // Clean up temp dir\n rmSync(outputDir, { recursive: true, force: true })\n\n const docs: Array<{ path: string, content: string }> = []\n\n let localeFiltered = 0\n for (const result of results) {\n if (!result.success || !result.content)\n continue\n\n // Filter by <html lang> detected during crawl\n if (foreignUrls.has(result.url)) {\n localeFiltered++\n continue\n }\n\n const urlObj = new URL(result.url)\n const urlPath = urlObj.pathname.replace(TRAILING_SLASH_RE, '') || '/index'\n const segments = urlPath.split('/').filter(Boolean)\n\n // Fallback: filter by URL path locale prefix when no lang tag was present\n if (isForeignPathPrefix(segments[0], userLang)) {\n localeFiltered++\n continue\n }\n\n const path = `docs/${segments.join('/')}.md`\n docs.push({ path, content: result.content })\n }\n\n if (localeFiltered > 0)\n onProgress?.(`Filtered ${localeFiltered} foreign locale pages`)\n\n onProgress?.(`Crawled ${docs.length} pages`)\n\n return docs\n}\n\nconst HTML_LANG_RE = /<html[^>]*\\slang=[\"']([^\"']+)[\"']/i\n\n/** Extract lang attribute from <html> tag */\nfunction extractHtmlLang(html: string): string | undefined {\n return HTML_LANG_RE.exec(html)?.[1]?.toLowerCase()\n}\n\n/** Common ISO 639-1 locale codes for i18n'd doc sites */\nconst LOCALE_CODES = new Set([\n 'ar',\n 'de',\n 'es',\n 'fr',\n 'id',\n 'it',\n 'ja',\n 'ko',\n 'nl',\n 'pl',\n 'pt',\n 'pt-br',\n 'ru',\n 'th',\n 'tr',\n 'uk',\n 'vi',\n 'zh',\n 'zh-cn',\n 'zh-tw',\n])\n\n/** Check if a URL path segment is a known locale prefix foreign to both English and user's locale */\nfunction isForeignPathPrefix(segment: string | undefined, userLang: string): boolean {\n if (!segment)\n return false\n const lower = segment.toLowerCase()\n if (lower === 'en' || lower.startsWith(userLang))\n return false\n return LOCALE_CODES.has(lower)\n}\n\n/** Detect user's 2-letter language code from env (e.g. 'ja' from LANG=ja_JP.UTF-8) */\nfunction getUserLang(): string {\n const raw = process.env.LC_ALL || process.env.LANG || process.env.LANGUAGE || ''\n const code = raw.split(STATIC_REGEX_2)[0]?.toLowerCase() || ''\n return code.length >= 2 ? code.slice(0, 2) : 'en'\n}\n\n/** Append glob pattern to a docs URL for crawling */\nexport function toCrawlPattern(docsUrl: string): string {\n const cleaned = docsUrl.replace(STATIC_REGEX_3, '')\n return `${cleaned}/**`\n}\n","/**\n * GitHub discussions fetching via gh CLI GraphQL\n * Prioritizes Q&A and Help categories, includes accepted answers\n * Comment quality filtering, smart truncation, noise removal\n */\n\nimport { spawnSync } from 'node:child_process'\nimport { mapInsert } from '../core/map.ts'\nimport { BOT_USERS, buildFrontmatter, COMMENT_NOISE_RE, hasCodeBlock, isoDate, truncateBody } from './github-common.ts'\nimport { isGhAvailable } from './issues.ts'\n\n/** Categories most useful for skill generation (in priority order) */\nconst HIGH_VALUE_CATEGORIES = new Set([\n 'q&a',\n 'help',\n 'troubleshooting',\n 'support',\n])\n\nconst LOW_VALUE_CATEGORIES = new Set([\n 'show and tell',\n 'ideas',\n 'polls',\n])\n\nexport interface DiscussionComment {\n body: string\n author: string\n reactions: number\n isMaintainer?: boolean\n}\n\nexport interface GitHubDiscussion {\n number: number\n title: string\n body: string\n category: string\n createdAt: string\n url: string\n upvoteCount: number\n comments: number\n isMaintainer?: boolean\n answer?: string\n topComments: DiscussionComment[]\n}\n\n/** Off-topic or spam title patterns — instant reject */\nconst 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\n\n/** Minimum score for a discussion to be included */\nconst MIN_DISCUSSION_SCORE = 3\n\n/**\n * Score a comment for quality. Higher = more useful for skill generation.\n * Maintainers 3x, code blocks 2x, reactions linear.\n */\nfunction scoreComment(c: { body: string, reactions: number, isMaintainer?: boolean }): number {\n return (c.isMaintainer ? 3 : 1) * (hasCodeBlock(c.body) ? 2 : 1) * (1 + c.reactions)\n}\n\n/**\n * Score a discussion for overall quality. Used for filtering and sorting.\n * Returns -1 for instant-reject (spam/off-topic).\n */\nexport function scoreDiscussion(d: GitHubDiscussion): number {\n if (TITLE_NOISE_RE.test(d.title))\n return -1\n\n let score = 0\n\n // Discussion authored by a maintainer — high signal\n if (d.isMaintainer)\n score += 3\n\n // Code presence — strongest signal for technical discussions\n const allText = [d.body, d.answer || '', ...d.topComments.map(c => c.body)].join('\\n')\n if (hasCodeBlock(allText))\n score += 3\n\n // Engagement\n score += Math.min(d.upvoteCount, 5)\n\n // Answer quality\n if (d.answer) {\n score += 2\n if (d.answer.length > 100)\n score += 1\n }\n\n // Maintainer involvement\n if (d.topComments.some(c => c.isMaintainer))\n score += 2\n\n // Community validation via reactions\n if (d.topComments.some(c => c.reactions > 0))\n score += 1\n\n return score\n}\n\n/**\n * Fetch discussions from a GitHub repo using gh CLI GraphQL.\n * Prioritizes Q&A and Help categories. Includes accepted answer body for answered discussions.\n * Fetches extra comments and scores them for quality.\n */\nexport async function fetchGitHubDiscussions(\n owner: string,\n repo: string,\n limit = 20,\n releasedAt?: string,\n fromDate?: string,\n): Promise<GitHubDiscussion[]> {\n if (!isGhAvailable())\n return []\n\n // GraphQL discussions endpoint doesn't support date filtering,\n // so we fetch latest N and filter client-side. Skip entirely\n // if the cutoff is in the past — results would be empty anyway.\n // (Skip this check when fromDate is set — we'll filter client-side below)\n if (!fromDate && releasedAt) {\n const cutoff = new Date(releasedAt)\n cutoff.setMonth(cutoff.getMonth() + 6)\n if (cutoff < new Date())\n return []\n }\n\n try {\n // Fetch more to compensate for filtering\n const fetchCount = Math.min(limit * 3, 80)\n // Fetch 10 comments per discussion so we can filter noise and pick best\n const query = `query($owner: String!, $repo: String!) { repository(owner: $owner, name: $repo) { discussions(first: ${fetchCount}, orderBy: {field: CREATED_AT, direction: DESC}) { nodes { number title body category { name } createdAt url upvoteCount comments(first: 10) { totalCount nodes { body author { login } authorAssociation reactions { totalCount } } } answer { body author { login } authorAssociation } author { login } authorAssociation } } } }`\n\n const { stdout: result } = spawnSync('gh', ['api', 'graphql', '-f', `query=${query}`, '-f', `owner=${owner}`, '-f', `repo=${repo}`], {\n encoding: 'utf-8',\n maxBuffer: 10 * 1024 * 1024,\n })\n if (!result)\n return []\n\n const data = JSON.parse(result)\n const nodes = data?.data?.repository?.discussions?.nodes\n if (!Array.isArray(nodes))\n return []\n\n const fromTs = fromDate ? new Date(fromDate).getTime() : null\n const discussions = nodes\n .filter((d: any) => d.author && !BOT_USERS.has(d.author.login))\n .filter((d: any) => {\n const cat = (d.category?.name || '').toLowerCase()\n return !LOW_VALUE_CATEGORIES.has(cat)\n })\n .filter((d: any) => !fromTs || new Date(d.createdAt).getTime() >= fromTs)\n .map((d: any) => {\n // Process answer — tag maintainer status\n let answer: string | undefined\n if (d.answer?.body) {\n const isMaintainer = ['OWNER', 'MEMBER', 'COLLABORATOR'].includes(d.answer.authorAssociation)\n const author = d.answer.author?.login\n const tag = isMaintainer && author ? `**@${author}** [maintainer]:\\n\\n` : ''\n answer = `${tag}${d.answer.body}`\n }\n\n // Process comments — filter noise, score for quality, take best 3\n const comments: DiscussionComment[] = (d.comments?.nodes || [])\n .filter((c: any) => c.author && !BOT_USERS.has(c.author.login))\n .filter((c: any) => !COMMENT_NOISE_RE.test((c.body || '').trim()))\n .map((c: any) => {\n const isMaintainer = ['OWNER', 'MEMBER', 'COLLABORATOR'].includes(c.authorAssociation)\n return {\n body: c.body || '',\n author: c.author.login,\n reactions: c.reactions?.totalCount || 0,\n isMaintainer,\n }\n })\n .sort((a: DiscussionComment, b: DiscussionComment) => scoreComment(b) - scoreComment(a))\n .slice(0, 3)\n\n return {\n number: d.number,\n title: d.title,\n body: d.body || '',\n category: d.category?.name || '',\n createdAt: d.createdAt,\n url: d.url,\n upvoteCount: d.upvoteCount || 0,\n comments: d.comments?.totalCount || 0,\n isMaintainer: ['OWNER', 'MEMBER', 'COLLABORATOR'].includes(d.authorAssociation),\n answer,\n topComments: comments,\n }\n })\n // Score, filter low-quality, sort by category priority then score\n .map((d: GitHubDiscussion) => ({ d, score: scoreDiscussion(d) }))\n .filter(({ score }) => score >= MIN_DISCUSSION_SCORE)\n .sort((a, b) => {\n const aHigh = HIGH_VALUE_CATEGORIES.has(a.d.category.toLowerCase()) ? 1 : 0\n const bHigh = HIGH_VALUE_CATEGORIES.has(b.d.category.toLowerCase()) ? 1 : 0\n if (aHigh !== bHigh)\n return bHigh - aHigh\n return b.score - a.score\n })\n .slice(0, limit)\n .map(({ d }) => d)\n\n return discussions\n }\n catch {\n return []\n }\n}\n\n/**\n * Format a single discussion as markdown with YAML frontmatter\n */\nexport function formatDiscussionAsMarkdown(d: GitHubDiscussion): string {\n const fm = buildFrontmatter({\n number: d.number,\n title: d.title,\n category: d.category,\n created: isoDate(d.createdAt),\n url: d.url,\n upvotes: d.upvoteCount,\n comments: d.comments,\n answered: !!d.answer,\n })\n\n const bodyLimit = d.upvoteCount >= 5 ? 1500 : 800\n const lines = [fm, '', `# ${d.title}`]\n\n if (d.body) {\n lines.push('', truncateBody(d.body, bodyLimit))\n }\n\n if (d.answer) {\n lines.push('', '---', '', '## Accepted Answer', '', truncateBody(d.answer, 1000))\n }\n else if (d.topComments.length > 0) {\n // No accepted answer — include top comments as context\n lines.push('', '---', '', '## Top Comments')\n for (const c of d.topComments) {\n const reactions = c.reactions > 0 ? ` (+${c.reactions})` : ''\n const maintainer = c.isMaintainer ? ' [maintainer]' : ''\n lines.push('', `**@${c.author}**${maintainer}${reactions}:`, '', truncateBody(c.body, 600))\n }\n }\n\n return lines.join('\\n')\n}\n\n/**\n * Generate a summary index of all discussions for quick LLM scanning.\n * Groups by category so the LLM can quickly find Q&A vs general discussions.\n */\nexport function generateDiscussionIndex(discussions: GitHubDiscussion[]): string {\n const byCategory = new Map<string, GitHubDiscussion[]>()\n for (const d of discussions) {\n const cat = d.category || 'Uncategorized'\n mapInsert(byCategory, cat, () => []).push(d)\n }\n\n const answered = discussions.filter(d => d.answer).length\n\n const fm = [\n '---',\n `total: ${discussions.length}`,\n `answered: ${answered}`,\n '---',\n ]\n\n const sections: string[] = [fm.join('\\n'), '', '# Discussions Index', '']\n\n // Sort categories: high-value first\n const cats = [...byCategory.keys()].sort((a, b) => {\n const aHigh = HIGH_VALUE_CATEGORIES.has(a.toLowerCase()) ? 0 : 1\n const bHigh = HIGH_VALUE_CATEGORIES.has(b.toLowerCase()) ? 0 : 1\n return aHigh - bHigh || a.localeCompare(b)\n })\n\n for (const cat of cats) {\n const group = byCategory.get(cat)!\n sections.push(`## ${cat} (${group.length})`, '')\n for (const d of group) {\n const upvotes = d.upvoteCount > 0 ? ` (+${d.upvoteCount})` : ''\n const answered = d.answer ? ' [answered]' : ''\n const date = isoDate(d.createdAt)\n sections.push(`- [#${d.number}](./discussion-${d.number}.md): ${d.title}${upvotes}${answered} (${date})`)\n }\n sections.push('')\n }\n\n return sections.join('\\n')\n}\n","/**\n * Docs index generation — creates _INDEX.md for docs directory\n */\n\nimport { extractDescription, extractTitle } from '../core/markdown.ts'\n\nconst STATIC_REGEX_1 = /\\.md$/\n\n/**\n * Generate a _INDEX.md for a docs/ directory.\n * Input: array of cached docs with paths like `docs/api/reactivity.md`.\n * Output: markdown index grouped by directory with title + description per page.\n */\nexport function generateDocsIndex(docs: Array<{ path: string, content: string }>): string {\n const docFiles = docs\n .filter(d => d.path.startsWith('docs/') && d.path.endsWith('.md') && !d.path.endsWith('_INDEX.md'))\n .sort((a, b) => a.path.localeCompare(b.path))\n\n if (docFiles.length === 0)\n return ''\n\n // Group by directory, root-level files first\n const rootFiles: Array<{ path: string, content: string }> = []\n const byDir = new Map<string, Array<{ path: string, content: string }>>()\n for (const doc of docFiles) {\n const rel = doc.path.slice('docs/'.length)\n const dir = rel.includes('/') ? rel.slice(0, rel.lastIndexOf('/')) : ''\n if (!dir) {\n rootFiles.push(doc)\n }\n else {\n const list = byDir.get(dir)\n if (list)\n list.push(doc)\n else\n byDir.set(dir, [doc])\n }\n }\n\n const sections: string[] = [\n '---',\n `total: ${docFiles.length}`,\n '---',\n '',\n '# Docs Index',\n '',\n ]\n\n // Root-level files first (no directory header)\n for (const file of rootFiles) {\n const rel = file.path.slice('docs/'.length)\n const title = extractTitle(file.content) || rel.replace(STATIC_REGEX_1, '')\n const desc = extractDescription(file.content)\n const descPart = desc ? `: ${desc}` : ''\n sections.push(`- [${title}](./${rel})${descPart}`)\n }\n if (rootFiles.length > 0)\n sections.push('')\n\n // Then grouped directories\n for (const [dir, files] of byDir) {\n sections.push(`## ${dir} (${files.length})`, '')\n\n for (const file of files) {\n const rel = file.path.slice('docs/'.length)\n const title = extractTitle(file.content) || rel.replace(STATIC_REGEX_1, '').split('/').pop()!\n const desc = extractDescription(file.content)\n const descPart = desc ? `: ${desc}` : ''\n sections.push(`- [${title}](./${rel})${descPart}`)\n }\n sections.push('')\n }\n\n return sections.join('\\n')\n}\n","/**\n * Globs .d.ts type definition files from a package for search indexing.\n * Only types — source code is too verbose.\n */\nimport { existsSync, readFileSync } from 'node:fs'\nimport { glob } from 'node:fs/promises'\nimport { join } from 'pathe'\n\nexport interface EntryFile {\n path: string\n content: string\n type: 'types' | 'source'\n}\n\nconst SKIP_DIRS = [\n 'node_modules',\n '_vendor',\n '__tests__',\n '__mocks__',\n '__fixtures__',\n 'test',\n 'tests',\n 'fixture',\n 'fixtures',\n 'locales',\n 'locale',\n 'i18n',\n '.git',\n]\n\nconst SKIP_PATTERNS = [\n '*.min.*',\n '*.prod.*',\n '*.global.*',\n '*.browser.*',\n '*.map',\n '*.map.js',\n 'CHANGELOG*',\n 'LICENSE*',\n 'README*',\n]\n\nconst MAX_FILE_SIZE = 500 * 1024 // 500KB per file\n\n/**\n * Glob .d.ts type definition files from a package directory, skipping junk.\n */\nexport async function resolveEntryFiles(packageDir: string): Promise<EntryFile[]> {\n if (!existsSync(join(packageDir, 'package.json')))\n return []\n\n const skipDirSet = new Set(SKIP_DIRS)\n const isSkipPattern = (name: string): boolean =>\n SKIP_PATTERNS.some((p) => {\n const star = p.indexOf('*')\n if (star === -1)\n return name === p\n const prefix = p.slice(0, star)\n const suffix = p.slice(star + 1)\n return name.startsWith(prefix) && name.endsWith(suffix)\n })\n\n const files: string[] = []\n for await (const file of glob(['**/*.d.{ts,mts,cts}'], {\n cwd: packageDir,\n exclude: (p: string) => {\n const segs = p.split('/')\n const last = segs[segs.length - 1]!\n if (isSkipPattern(last))\n return true\n return segs.some(s => skipDirSet.has(s))\n },\n })) {\n files.push(file)\n }\n\n const entries: EntryFile[] = []\n\n for (const file of files) {\n const absPath = join(packageDir, file)\n let content: string\n try {\n content = readFileSync(absPath, 'utf-8')\n }\n catch {\n continue\n }\n\n if (content.length > MAX_FILE_SIZE)\n continue\n\n entries.push({ path: file, content, type: 'types' })\n }\n\n return entries\n}\n","/**\n * Git repo skill source — parse inputs + fetch pre-authored skills from repos\n *\n * Supports GitHub shorthand (owner/repo), full URLs, SSH, GitLab, and local paths.\n * Skills are pre-authored SKILL.md files — no doc resolution or LLM generation needed.\n */\n\nimport { existsSync, readdirSync, readFileSync, rmSync } from 'node:fs'\nimport { tmpdir } from 'node:os'\nimport { downloadTemplate } from 'giget'\nimport { join, resolve } from 'pathe'\nimport { parseFrontmatter } from '../core/markdown.ts'\nimport { GIT_SUFFIX_RE, LEADING_SLASH_RE } from '../core/regex.ts'\nimport { normalizeRepoUrl, parseGitHubUrl } from '../core/url.ts'\nimport { getGitHubToken } from './github-common.ts'\nimport { $fetch, fetchGitHubRaw } from './utils.ts'\n\nconst STATIC_REGEX_1 = /^[\\w.-]+\\/[\\w.-]+$/\n\nexport interface GitSkillSource {\n type: 'github' | 'gitlab' | 'git-ssh' | 'local'\n owner?: string\n repo?: string\n /** Direct path to a specific skill (from /tree/ref/path URLs) */\n skillPath?: string\n /** Branch/tag parsed from URL */\n ref?: string\n /** Absolute path for local sources */\n localPath?: string\n}\n\nexport interface RemoteSkill {\n /** From SKILL.md frontmatter `name` field, or directory name */\n name: string\n /** From SKILL.md frontmatter `description` field */\n description: string\n /** Path within repo (e.g., \"skills/web-design-guidelines\") */\n path: string\n /** Full SKILL.md content */\n content: string\n /** Supporting files (scripts/, references/, assets/) */\n files: Array<{ path: string, content: string }>\n}\n\n/**\n * Detect whether an input string is a git skill source.\n * Returns null for npm package names (including scoped @scope/pkg).\n */\nexport function parseGitSkillInput(input: string): GitSkillSource | null {\n const trimmed = input.trim()\n\n // Scoped npm packages → not git\n if (trimmed.startsWith('@'))\n return null\n\n // Local paths\n if (trimmed.startsWith('./') || trimmed.startsWith('../') || trimmed.startsWith('/') || trimmed.startsWith('~')) {\n const localPath = trimmed.startsWith('~')\n ? resolve(process.env.HOME || '', trimmed.slice(1))\n : resolve(trimmed)\n return { type: 'local', localPath }\n }\n\n // SSH format: git@github.com:owner/repo\n if (trimmed.startsWith('git@')) {\n const normalized = normalizeRepoUrl(trimmed)\n const gh = parseGitHubUrl(normalized)\n if (gh)\n return { type: 'github', owner: gh.owner, repo: gh.repo }\n return null\n }\n\n // Full URLs\n if (trimmed.startsWith('https://') || trimmed.startsWith('http://')) {\n return parseGitUrl(trimmed)\n }\n\n // GitHub shorthand: owner/repo (exactly one slash, no spaces, no commas)\n if (STATIC_REGEX_1.test(trimmed)) {\n return { type: 'github', owner: trimmed.split('/')[0], repo: trimmed.split('/')[1] }\n }\n\n // Everything else → npm\n return null\n}\n\nfunction parseGitUrl(url: string): GitSkillSource | null {\n try {\n const parsed = new URL(url)\n\n if (parsed.hostname === 'github.com' || parsed.hostname === 'www.github.com') {\n const parts = parsed.pathname.replace(LEADING_SLASH_RE, '').replace(GIT_SUFFIX_RE, '').split('/')\n const owner = parts[0]\n const repo = parts[1]\n if (!owner || !repo)\n return null\n\n // Handle /tree/ref/path URLs → extract specific skill path\n if (parts[2] === 'tree' && parts.length >= 4) {\n const ref = parts[3]\n const skillPath = parts.length > 4 ? parts.slice(4).join('/') : undefined\n return { type: 'github', owner, repo, ref, skillPath }\n }\n\n return { type: 'github', owner, repo }\n }\n\n if (parsed.hostname === 'gitlab.com') {\n const parts = parsed.pathname.replace(LEADING_SLASH_RE, '').replace(GIT_SUFFIX_RE, '').split('/')\n const owner = parts[0]\n const repo = parts[1]\n if (!owner || !repo)\n return null\n return { type: 'gitlab', owner, repo }\n }\n\n return null\n }\n catch {\n return null\n }\n}\n\n/**\n * Parse name and description from SKILL.md frontmatter.\n */\nexport function parseSkillFrontmatterName(content: string): { name?: string, description?: string } {\n const fm = parseFrontmatter(content)\n return { name: fm.name, description: fm.description }\n}\n\n/** Recursively find all directories containing a SKILL.md file. */\nfunction findSkillDirs(root: string, prefix = ''): Array<{ dir: string, repoPath: string }> {\n const out: Array<{ dir: string, repoPath: string }> = []\n if (!existsSync(root))\n return out\n for (const entry of readdirSync(root, { withFileTypes: true })) {\n if (!entry.isDirectory())\n continue\n const dir = resolve(root, entry.name)\n const repoPath = prefix ? `${prefix}/${entry.name}` : entry.name\n if (existsSync(resolve(dir, 'SKILL.md')))\n out.push({ dir, repoPath })\n else\n out.push(...findSkillDirs(dir, repoPath))\n }\n return out\n}\n\n/** Recursively collect all files in a directory, returning relative paths */\nfunction collectFiles(dir: string, prefix = ''): Array<{ path: string, content: string }> {\n const files: Array<{ path: string, content: string }> = []\n if (!existsSync(dir))\n return files\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n const relPath = prefix ? `${prefix}/${entry.name}` : entry.name\n const fullPath = resolve(dir, entry.name)\n if (entry.isDirectory()) {\n files.push(...collectFiles(fullPath, relPath))\n }\n else if (entry.isFile()) {\n files.push({ path: relPath, content: readFileSync(fullPath, 'utf-8') })\n }\n }\n return files\n}\n\n/**\n * Fetch skills from a git source. Returns list of discovered skills.\n */\nexport async function fetchGitSkills(\n source: GitSkillSource,\n onProgress?: (msg: string) => void,\n): Promise<{ skills: RemoteSkill[] }> {\n if (source.type === 'local')\n return fetchLocalSkills(source)\n if (source.type === 'github')\n return fetchGitHubSkills(source, onProgress)\n if (source.type === 'gitlab')\n return fetchGitLabSkills(source, onProgress)\n return { skills: [] }\n}\n\n// ── Local ──\n\nfunction fetchLocalSkills(source: GitSkillSource): { skills: RemoteSkill[] } {\n const base = source.localPath!\n if (!existsSync(base))\n return { skills: [] }\n\n const skills: RemoteSkill[] = []\n\n // Check for skills/ subdirectory (recursive — repos may nest by category)\n const skillsDir = resolve(base, 'skills')\n if (existsSync(skillsDir)) {\n for (const { dir, repoPath } of findSkillDirs(skillsDir, 'skills')) {\n const skill = readLocalSkill(dir, repoPath)\n if (skill)\n skills.push(skill)\n }\n }\n\n // Check for root SKILL.md\n if (skills.length === 0) {\n const skill = readLocalSkill(base, '')\n if (skill)\n skills.push(skill)\n }\n\n return { skills }\n}\n\nfunction readLocalSkill(dir: string, repoPath: string): RemoteSkill | null {\n const skillMdPath = resolve(dir, 'SKILL.md')\n if (!existsSync(skillMdPath))\n return null\n\n const content = readFileSync(skillMdPath, 'utf-8')\n const frontmatter = parseSkillFrontmatterName(content)\n const dirName = dir.split('/').pop()!\n const name = frontmatter.name || dirName\n\n // Collect all files except SKILL.md (handled separately)\n const files = collectFiles(dir).filter(f => f.path !== 'SKILL.md')\n\n return {\n name,\n description: frontmatter.description || '',\n path: repoPath,\n content,\n files,\n }\n}\n\n// ── GitHub ──\n\nasync function fetchGitHubSkills(\n source: GitSkillSource,\n onProgress?: (msg: string) => void,\n): Promise<{ skills: RemoteSkill[] }> {\n const { owner, repo } = source\n if (!owner || !repo)\n return { skills: [] }\n\n const ref = source.ref || 'main'\n const refs = ref === 'main' ? ['main', 'master'] : [ref]\n\n for (const tryRef of refs) {\n const skills = await downloadGitHubSkills(owner, repo, tryRef, source.skillPath, onProgress)\n if (skills.length > 0)\n return { skills }\n }\n\n return { skills: [] }\n}\n\nasync function downloadGitHubSkills(\n owner: string,\n repo: string,\n ref: string,\n skillPath?: string,\n onProgress?: (msg: string) => void,\n): Promise<RemoteSkill[]> {\n const tempDir = join(tmpdir(), `skilld-${Date.now()}`)\n\n try {\n if (skillPath) {\n onProgress?.(`Downloading ${owner}/${repo}/${skillPath}@${ref}`)\n const { dir } = await downloadTemplate(\n `github:${owner}/${repo}/${skillPath}#${ref}`,\n { dir: tempDir, force: true, auth: getGitHubToken() || undefined },\n )\n const skill = readLocalSkill(dir, skillPath)\n return skill ? [skill] : []\n }\n\n // Download skills/ subdirectory (single tarball request)\n onProgress?.(`Downloading ${owner}/${repo}/skills@${ref}`)\n try {\n const { dir } = await downloadTemplate(\n `github:${owner}/${repo}/skills#${ref}`,\n { dir: tempDir, force: true, auth: getGitHubToken() || undefined },\n )\n\n const skills: RemoteSkill[] = []\n for (const { dir: skillDir, repoPath } of findSkillDirs(dir, 'skills')) {\n const skill = readLocalSkill(skillDir, repoPath)\n if (skill)\n skills.push(skill)\n }\n\n if (skills.length > 0) {\n onProgress?.(`Found ${skills.length} skill(s)`)\n return skills\n }\n }\n catch {}\n\n // Fallback: check root SKILL.md via single HTTP request (auth-aware for private repos)\n const content = await fetchGitHubRaw(\n `https://raw.githubusercontent.com/${owner}/${repo}/${ref}/SKILL.md`,\n )\n if (content) {\n const fm = parseSkillFrontmatterName(content)\n onProgress?.('Found 1 skill')\n return [{\n name: fm.name || repo,\n description: fm.description || '',\n path: '',\n content,\n files: [],\n }]\n }\n\n return []\n }\n catch {\n return []\n }\n finally {\n rmSync(tempDir, { recursive: true, force: true })\n }\n}\n\n// ── GitLab ──\n\nasync function fetchGitLabSkills(\n source: GitSkillSource,\n onProgress?: (msg: string) => void,\n): Promise<{ skills: RemoteSkill[] }> {\n const { owner, repo } = source\n if (!owner || !repo)\n return { skills: [] }\n\n const ref = source.ref || 'main'\n const tempDir = join(tmpdir(), `skilld-gitlab-${Date.now()}`)\n\n try {\n const subdir = source.skillPath || 'skills'\n onProgress?.(`Downloading ${owner}/${repo}/${subdir}@${ref}`)\n\n const { dir } = await downloadTemplate(\n `gitlab:${owner}/${repo}/${subdir}#${ref}`,\n { dir: tempDir, force: true },\n )\n\n if (source.skillPath) {\n const skill = readLocalSkill(dir, source.skillPath)\n return { skills: skill ? [skill] : [] }\n }\n\n const skills: RemoteSkill[] = []\n for (const { dir: skillDir, repoPath } of findSkillDirs(dir, 'skills')) {\n const skill = readLocalSkill(skillDir, repoPath)\n if (skill)\n skills.push(skill)\n }\n\n if (skills.length > 0) {\n onProgress?.(`Found ${skills.length} skill(s)`)\n return { skills }\n }\n\n // Fallback: check root SKILL.md\n const content = await $fetch(\n `https://gitlab.com/${owner}/${repo}/-/raw/${ref}/SKILL.md`,\n { responseType: 'text' },\n ).catch(() => null)\n if (content) {\n const fm = parseSkillFrontmatterName(content)\n return {\n skills: [{\n name: fm.name || repo,\n description: fm.description || '',\n path: '',\n content,\n files: [],\n }],\n }\n }\n\n return { skills: [] }\n }\n catch {\n return { skills: [] }\n }\n finally {\n rmSync(tempDir, { recursive: true, force: true })\n }\n}\n","/**\n * Local package reading: parsing dependency specifiers (link:/npm:/workspace:/etc.),\n * resolving installed versions from node_modules, and reading package.json.\n */\n\nimport type { LocalDependency, ResolvedPackage } from './types.ts'\nimport { existsSync, readdirSync, readFileSync } from 'node:fs'\nimport { createRequire } from 'node:module'\nimport { pathToFileURL } from 'node:url'\nimport { basename, dirname, join, resolve } from 'pathe'\nimport { readPackageJsonSafe } from '../core/package-json.ts'\nimport { README_FILENAME_RE, VERSION_RANGE_PREFIX_RE } from '../core/regex.ts'\nimport { normalizeRepoUrl, parseGitHubUrl } from '../core/url.ts'\nimport { fetchGitDocs, fetchReadme } from './github.ts'\n\nconst STATIC_REGEX_1 = /^[\\^~>=<\\d]/\nconst STATIC_REGEX_4 = /^version:\\s*\"?([^\"\\n]+)\"?/m\n\nexport function parseVersionSpecifier(\n name: string,\n version: string,\n cwd: string,\n): LocalDependency | null {\n if (version.startsWith('link:')) {\n const linkPath = resolve(cwd, version.slice(5))\n const linkedPkg = readPackageJsonSafe(join(linkPath, 'package.json'))\n if (linkedPkg) {\n return {\n name: (linkedPkg.parsed.name as string) || name,\n version: (linkedPkg.parsed.version as string) || '0.0.0',\n }\n }\n return null\n }\n\n if (version.startsWith('npm:')) {\n const specifier = version.slice(4)\n const atIndex = specifier.startsWith('@')\n ? specifier.indexOf('@', 1)\n : specifier.indexOf('@')\n const realName = atIndex > 0 ? specifier.slice(0, atIndex) : specifier\n return { name: realName, version: resolveInstalledVersion(realName, cwd) || '*' }\n }\n\n if (version.startsWith('file:') || version.startsWith('git:') || version.startsWith('git+')) {\n return null\n }\n\n const installed = resolveInstalledVersion(name, cwd)\n if (installed)\n return { name, version: installed }\n\n if (STATIC_REGEX_1.test(version))\n return { name, version: version.replace(VERSION_RANGE_PREFIX_RE, '') }\n\n if (version.startsWith('catalog:') || version.startsWith('workspace:'))\n return { name, version: '*' }\n\n return null\n}\n\nexport function resolveInstalledVersion(name: string, cwd: string): string | null {\n const directPackageJson = join(cwd, 'node_modules', ...name.split('/'), 'package.json')\n const direct = readPackageJsonSafe(directPackageJson)\n if (direct)\n return (direct.parsed.version as string) || null\n\n const req = createRequire(join(cwd, 'package.json'))\n try {\n const resolved = req.resolve(`${name}/package.json`)\n return (readPackageJsonSafe(resolved)?.parsed.version as string) || null\n }\n catch {\n try {\n const entry = req.resolve(name)\n let dir = dirname(entry)\n while (dir && basename(dir) !== 'node_modules') {\n const pkg = readPackageJsonSafe(join(dir, 'package.json'))\n if (pkg)\n return (pkg.parsed.version as string) || null\n const parent = dirname(dir)\n if (parent === dir)\n break\n dir = parent\n }\n }\n catch {}\n return null\n }\n}\n\nexport async function readLocalDependencies(cwd: string): Promise<LocalDependency[]> {\n const pkgPath = join(cwd, 'package.json')\n const result = readPackageJsonSafe(pkgPath)\n if (!result) {\n throw new Error('No package.json found in current directory')\n }\n\n const pkg = result.parsed\n const deps: Record<string, string> = {\n ...pkg.dependencies as Record<string, string>,\n ...pkg.devDependencies as Record<string, string>,\n }\n\n const results: LocalDependency[] = []\n\n for (const [name, version] of Object.entries(deps)) {\n const parsed = parseVersionSpecifier(name, version, cwd)\n if (parsed) {\n results.push(parsed)\n }\n }\n\n return results\n}\n\nexport interface LocalPackageInfo {\n name: string\n version: string\n description?: string\n repoUrl?: string\n localPath: string\n}\n\nexport function readLocalPackageInfo(localPath: string): LocalPackageInfo | null {\n const result = readPackageJsonSafe(join(localPath, 'package.json'))\n if (!result)\n return null\n\n const pkg = result.parsed as Record<string, any>\n\n let repoUrl: string | undefined\n if (pkg.repository?.url) {\n repoUrl = normalizeRepoUrl(pkg.repository.url)\n }\n else if (typeof pkg.repository === 'string') {\n repoUrl = normalizeRepoUrl(pkg.repository)\n }\n\n return {\n name: pkg.name,\n version: pkg.version || '0.0.0',\n description: pkg.description,\n repoUrl,\n localPath,\n }\n}\n\nexport async function resolveLocalPackageDocs(localPath: string): Promise<ResolvedPackage | null> {\n const info = readLocalPackageInfo(localPath)\n if (!info)\n return null\n\n const result: ResolvedPackage = {\n name: info.name,\n version: info.version,\n description: info.description,\n repoUrl: info.repoUrl,\n }\n\n if (info.repoUrl?.includes('github.com')) {\n const gh = parseGitHubUrl(info.repoUrl)\n if (gh) {\n const gitDocs = await fetchGitDocs(gh.owner, gh.repo, info.version, info.name)\n if (gitDocs) {\n result.gitDocsUrl = gitDocs.baseUrl\n result.gitRef = gitDocs.ref\n result.gitDocsFallback = gitDocs.fallback\n }\n\n const readmeUrl = await fetchReadme(gh.owner, gh.repo, undefined, result.gitRef)\n if (readmeUrl) {\n result.readmeUrl = readmeUrl\n }\n }\n }\n\n if (!result.readmeUrl && !result.gitDocsUrl) {\n const readmeFile = readdirSync(localPath).find(f => README_FILENAME_RE.test(f))\n if (readmeFile) {\n result.readmeUrl = pathToFileURL(join(localPath, readmeFile)).href\n }\n }\n\n if (!result.readmeUrl && !result.gitDocsUrl) {\n return null\n }\n\n return result\n}\n\nexport function getInstalledSkillVersion(skillDir: string): string | null {\n const skillPath = join(skillDir, 'SKILL.md')\n if (!existsSync(skillPath))\n return null\n\n const content = readFileSync(skillPath, 'utf-8')\n const match = content.match(STATIC_REGEX_4)\n return match?.[1] || null\n}\n\n/** Try resolving a `link:` dependency to local package docs. Returns null if not a link dep or resolution fails. */\nexport async function resolveLocalDep(packageName: string, cwd: string): Promise<ResolvedPackage | null> {\n const result = readPackageJsonSafe(join(cwd, 'package.json'))\n if (!result)\n return null\n\n const pkg = result.parsed\n const deps = { ...pkg.dependencies as Record<string, string>, ...pkg.devDependencies as Record<string, string> }\n const depVersion = deps[packageName]\n\n if (!depVersion?.startsWith('link:'))\n return null\n\n const localPath = resolve(cwd, depVersion.slice(5))\n return resolveLocalPackageDocs(localPath)\n}\n","/**\n * NPM registry I/O — search, package metadata, dist-tags, tarball download.\n */\n\nimport type { NpmPackageInfo } from './types.ts'\nimport { spawnSync } from 'node:child_process'\nimport { createWriteStream, existsSync, mkdirSync, rmSync } from 'node:fs'\nimport { Writable } from 'node:stream'\nimport { join } from 'pathe'\nimport { getCacheDir } from '../cache/index.ts'\nimport { parsePackageSpec } from '../core/url.ts'\nimport { $fetch, SKILLD_USER_AGENT } from './utils.ts'\n\nexport async function searchNpmPackages(query: string, size = 5): Promise<Array<{ name: string, description?: string, version: string }>> {\n const data = await $fetch<{\n objects: Array<{ package: { name: string, description?: string, version: string } }>\n }>(`https://registry.npmjs.org/-/v1/search?text=${encodeURIComponent(query)}&size=${size}`).catch(() => null)\n\n if (!data?.objects?.length)\n return []\n\n return data.objects.map(o => ({\n name: o.package.name,\n description: o.package.description,\n version: o.package.version,\n }))\n}\n\nexport async function fetchNpmPackage(packageName: string): Promise<NpmPackageInfo | null> {\n const data = await $fetch<NpmPackageInfo>(`https://unpkg.com/${packageName}/package.json`).catch(() => null)\n if (data)\n return data\n return $fetch<NpmPackageInfo>(`https://registry.npmjs.org/${packageName}/latest`).catch(() => null)\n}\n\nexport interface DistTagInfo {\n version: string\n releasedAt?: string\n}\n\nexport interface NpmRegistryMeta {\n releasedAt?: string\n distTags?: Record<string, DistTagInfo>\n}\n\nexport async function fetchNpmRegistryMeta(packageName: string, version: string): Promise<NpmRegistryMeta> {\n const { name: barePackageName } = parsePackageSpec(packageName)\n const data = await $fetch<{\n 'time'?: Record<string, string>\n 'dist-tags'?: Record<string, string>\n }>(`https://registry.npmjs.org/${barePackageName}`, {\n headers: { Accept: 'application/vnd.npm.install-v1+json' },\n }).catch(() => null)\n\n if (!data)\n return {}\n\n const distTags: Record<string, DistTagInfo> | undefined = data['dist-tags']\n ? Object.fromEntries(\n Object.entries(data['dist-tags']).map(([tag, ver]) => [\n tag,\n { version: ver, releasedAt: data.time?.[ver] },\n ]),\n )\n : undefined\n\n return {\n releasedAt: data.time?.[version] || undefined,\n distTags,\n }\n}\n\n/**\n * Download and extract npm package tarball to cache directory.\n * Extracts to: ~/.skilld/references/<pkg>@<version>/pkg/\n */\nexport async function fetchPkgDist(name: string, version: string): Promise<string | null> {\n const cacheDir = getCacheDir(name, version)\n const pkgDir = join(cacheDir, 'pkg')\n\n if (existsSync(join(pkgDir, 'package.json')))\n return pkgDir\n\n const data = await $fetch<{ dist?: { tarball?: string } }>(\n `https://registry.npmjs.org/${name}/${version}`,\n ).catch(() => null)\n if (!data)\n return null\n const tarballUrl = data.dist?.tarball\n if (!tarballUrl)\n return null\n\n const tarballRes = await fetch(tarballUrl, {\n headers: { 'User-Agent': SKILLD_USER_AGENT },\n }).catch(() => null)\n\n if (!tarballRes?.ok || !tarballRes.body)\n return null\n\n mkdirSync(pkgDir, { recursive: true })\n\n const tmpTarball = join(cacheDir, '_pkg.tgz')\n const fileStream = createWriteStream(tmpTarball)\n const fileClosed = new Promise<void>(resolve => fileStream.once('close', resolve))\n\n const reader = tarballRes.body.getReader()\n\n try {\n await new Promise<void>((res, reject) => {\n const writable = new Writable({\n write(chunk, _encoding, callback) {\n fileStream.write(chunk, callback)\n },\n })\n writable.on('finish', () => {\n fileStream.end()\n })\n fileStream.on('close', () => res())\n writable.on('error', reject)\n fileStream.on('error', reject)\n\n function pump() {\n reader.read().then(({ done, value }) => {\n if (done) {\n writable.end()\n return\n }\n writable.write(value, () => pump())\n }).catch(reject)\n }\n pump()\n })\n\n const { status } = spawnSync('tar', ['xzf', tmpTarball, '--strip-components=1', '-C', pkgDir], { stdio: 'ignore' })\n if (status !== 0) {\n rmSync(pkgDir, { recursive: true, force: true })\n return null\n }\n\n return pkgDir\n }\n catch {\n rmSync(pkgDir, { recursive: true, force: true })\n return null\n }\n finally {\n reader.cancel().catch(() => {})\n fileStream.destroy()\n await fileClosed\n try {\n rmSync(tmpTarball, { force: true })\n }\n catch {}\n }\n}\n\nexport async function fetchLatestVersion(packageName: string): Promise<string | null> {\n const data = await $fetch<{ version?: string }>(\n `https://unpkg.com/${packageName}/package.json`,\n ).catch(() => null)\n if (data?.version)\n return data.version\n\n const registry = await $fetch<{ 'dist-tags'?: Record<string, string> }>(\n `https://registry.npmjs.org/${packageName}`,\n { headers: { Accept: 'application/vnd.npm.install-v1+json' } },\n ).catch(() => null)\n return registry?.['dist-tags']?.latest || null\n}\n","/**\n * Prefix-based input parser for `skilld add`\n *\n * All sources require an explicit prefix:\n * npm:vue → package skill from registry\n * crate:serde → Rust crate from crates.io\n * gh:owner/repo → git skill\n * github:o/r → git skill (alias)\n * @handle → curator's skills\n * @handle/coll → specific collection\n *\n * Bare names (no prefix) are deprecated but still resolve as npm: with a warning.\n */\n\nimport type { GitSkillSource } from '../sources/git-skills.ts'\nimport { parseGitSkillInput } from '../sources/git-skills.ts'\n\nconst STATIC_REGEX_1 = /^[\\w.-]+\\/[\\w.-]+/\n\nexport type SkillSource\n = | { type: 'npm', package: string, tag?: string }\n | { type: 'crate', package: string, version?: string }\n | { type: 'git', source: GitSkillSource, skillFilter?: string }\n | { type: 'curator', handle: string }\n | { type: 'collection', handle: string, name: string }\n | { type: 'bare', package: string, tag?: string }\n\n/**\n * Parse a single CLI input token into a typed SkillSource.\n *\n * Does NOT emit deprecation warnings; callers handle that for `bare` type.\n */\nexport function parseSkillInput(input: string): SkillSource {\n const trimmed = input.trim()\n\n // npm: prefix → package skill\n if (trimmed.startsWith('npm:')) {\n const rest = trimmed.slice(4)\n const { name, tag } = splitPackageTag(rest)\n return { type: 'npm', package: name, tag }\n }\n\n // crate: prefix → Rust crate from crates.io\n if (trimmed.startsWith('crate:')) {\n const rest = trimmed.slice(6).trim()\n const atIdx = rest.indexOf('@')\n const name = (atIdx === -1 ? rest : rest.slice(0, atIdx)).toLowerCase()\n const version = atIdx === -1 ? undefined : rest.slice(atIdx + 1) || undefined\n return { type: 'crate', package: name, version }\n }\n\n // gh: or github: prefix → git skill\n if (trimmed.startsWith('gh:') || trimmed.startsWith('github:')) {\n const rest = trimmed.startsWith('gh:') ? trimmed.slice(3) : trimmed.slice(7)\n const gitSource = parseGitSkillInput(rest)\n if (gitSource)\n return { type: 'git', source: gitSource }\n // If gh: prefix used but can't parse as git, treat as github shorthand\n if (STATIC_REGEX_1.test(rest)) {\n const [owner, repo] = rest.split('/')\n return { type: 'git', source: { type: 'github', owner, repo } }\n }\n // Invalid gh: input, fall through to bare\n return { type: 'bare', package: rest }\n }\n\n // @handle (curator) or @scope/pkg (npm scoped package)\n if (trimmed.startsWith('@')) {\n const rest = trimmed.slice(1)\n const slashIdx = rest.indexOf('/')\n if (slashIdx === -1) {\n return { type: 'curator', handle: rest }\n }\n // @scope/pkg → treat as npm scoped package (bare, deprecated form)\n // Collections must be installed via npm:@handle/coll or a future prefix.\n const { name, tag } = splitPackageTag(trimmed)\n return { type: 'bare', package: name, tag }\n }\n\n // Try existing git detection (SSH, URLs, local paths, owner/repo shorthand)\n const gitSource = parseGitSkillInput(trimmed)\n if (gitSource)\n return { type: 'git', source: gitSource }\n\n // Bare name (deprecated) → resolves as npm\n const { name, tag } = splitPackageTag(trimmed)\n return { type: 'bare', package: name, tag }\n}\n\n/**\n * Resolve a CLI input to the bare package/skill name used for lookup in the lockfile.\n * Strips `npm:` / `gh:` prefixes. Returns null for curator/collection (not addressable\n * as a single skill name).\n */\nexport function resolveSkillName(input: string): string | null {\n const source = parseSkillInput(input)\n switch (source.type) {\n case 'npm':\n case 'bare':\n return source.package\n case 'crate':\n return `crate:${source.package}`\n case 'git':\n if (source.source.type === 'github' && source.source.repo)\n return source.source.repo\n return null\n case 'curator':\n case 'collection':\n return null\n default: {\n const _exhaustive: never = source\n throw new Error(`Unhandled SkillSource type: ${JSON.stringify(_exhaustive)}`)\n }\n }\n}\n\n/**\n * Map a lockfile/identity package name to the storage-safe name used for\n * cache directories and symlinks. `crate:serde` → `@skilld-crate/serde`;\n * other names pass through unchanged.\n */\nexport function toStoragePackageName(identityName: string): string {\n if (identityName.startsWith('crate:'))\n return `@skilld-crate/${identityName.slice('crate:'.length)}`\n return identityName\n}\n\n/** True if `spec` targets crates.io (`crate:<name>` form). */\nexport function isCrateSpec(spec: string): boolean {\n return spec.startsWith('crate:')\n}\n\n/** Wrap a bare crate name as the lockfile identity name. */\nexport function toCrateIdentity(crateName: string): string {\n return `crate:${crateName}`\n}\n\n/**\n * Split \"package@tag\" into name and optional tag.\n * Handles scoped packages: \"@scope/pkg@tag\"\n */\nfunction splitPackageTag(spec: string): { name: string, tag?: string } {\n // Scoped: @scope/pkg@tag → find the @ after the scope\n if (spec.startsWith('@')) {\n const slashIdx = spec.indexOf('/')\n if (slashIdx !== -1) {\n const afterSlash = spec.indexOf('@', slashIdx)\n if (afterSlash !== -1)\n return { name: spec.slice(0, afterSlash), tag: spec.slice(afterSlash + 1) || undefined }\n }\n return { name: spec }\n }\n // Unscoped: pkg@tag\n const atIdx = spec.indexOf('@')\n if (atIdx !== -1)\n return { name: spec.slice(0, atIdx), tag: spec.slice(atIdx + 1) || undefined }\n return { name: spec }\n}\n","/**\n * Crawl URL resolver — populates `result.crawlUrl` from package-registry.\n *\n * Pure registry lookup; no network. Some packages (e.g. motion-v) have a\n * curated crawl pattern used later by the content stage.\n */\n\nimport { getCrawlUrl } from '../package-registry.ts'\nimport { defineResolver } from '../resolver-registry.ts'\n\nexport const crawlUrlResolver = defineResolver({\n id: 'crawl',\n canResolve: ctx => !!ctx.result,\n async run(ctx) {\n const crawlUrl = getCrawlUrl(ctx.packageName)\n if (crawlUrl)\n ctx.result!.crawlUrl = crawlUrl\n return { kind: 'ok' }\n },\n})\n","/**\n * Versioned git docs resolver — fetches `docs/` at the package's git tag.\n *\n * Only runs when we have a GitHub repo URL. Honors a caller-supplied\n * version override (`options.version`) but falls back to the npm version.\n * Records `gitDocsUrl`, `gitRef`, `gitDocsFallback`, and stashes\n * `gitDocsAllFiles` on the context for later llms.txt cross-validation.\n */\n\nimport { parseGitHubUrl } from '../../core/url.ts'\nimport { fetchGitDocs } from '../github.ts'\nimport { defineResolver } from '../resolver-registry.ts'\n\nexport const gitTagResolver = defineResolver({\n id: 'github-docs',\n canResolve: ctx => !!ctx.result?.repoUrl?.includes('github.com'),\n async run(ctx) {\n const result = ctx.result!\n const gh = parseGitHubUrl(result.repoUrl!)\n if (!gh)\n return { kind: 'skip' }\n\n const targetVersion = ctx.options.version || ctx.npm?.version\n if (!targetVersion)\n return { kind: 'skip' }\n\n ctx.options.onProgress?.('github-docs')\n const gitDocs = await fetchGitDocs(gh.owner, gh.repo, targetVersion, ctx.packageName, ctx.rawRepoUrl)\n if (gitDocs) {\n result.gitDocsUrl = gitDocs.baseUrl\n result.gitRef = gitDocs.ref\n result.gitDocsFallback = gitDocs.fallback\n ctx.gitDocsAllFiles = gitDocs.allFiles\n ctx.attempts.push({\n source: 'github-docs',\n url: gitDocs.baseUrl,\n status: 'success',\n message: gitDocs.fallback\n ? `Found ${gitDocs.files.length} docs at ${gitDocs.ref} (no tag for v${targetVersion})`\n : `Found ${gitDocs.files.length} docs at ${gitDocs.ref}`,\n })\n return { kind: 'ok' }\n }\n ctx.attempts.push({\n source: 'github-docs',\n url: `${result.repoUrl}/tree/v${targetVersion}/docs`,\n status: 'not-found',\n message: 'No docs/ folder found at version tag',\n })\n return { kind: 'skip' }\n },\n})\n","/**\n * GitHub repo metadata resolver — fills `docsUrl` from the repo's homepage.\n *\n * Only runs when we have a GitHub repo URL and don't yet have a docsUrl\n * (set earlier from npm `homepage`).\n */\n\nimport { isUselessDocsUrl, parseGitHubUrl } from '../../core/url.ts'\nimport { fetchGitHubRepoMeta } from '../github.ts'\nimport { defineResolver } from '../resolver-registry.ts'\n\nexport const githubMetaResolver = defineResolver({\n id: 'github-meta',\n canResolve: ctx => !!ctx.result?.repoUrl?.includes('github.com') && !ctx.result.docsUrl,\n async run(ctx) {\n const result = ctx.result!\n const gh = parseGitHubUrl(result.repoUrl!)\n if (!gh)\n return { kind: 'skip' }\n\n ctx.options.onProgress?.('github-meta')\n const repoMeta = await fetchGitHubRepoMeta(gh.owner, gh.repo, ctx.packageName)\n if (repoMeta?.homepage && !isUselessDocsUrl(repoMeta.homepage)) {\n result.docsUrl = repoMeta.homepage\n ctx.attempts.push({\n source: 'github-meta',\n url: result.repoUrl!,\n status: 'success',\n message: `Found homepage: ${repoMeta.homepage}`,\n })\n return { kind: 'ok' }\n }\n ctx.attempts.push({\n source: 'github-meta',\n url: result.repoUrl!,\n status: 'not-found',\n message: 'No homepage in repo metadata',\n })\n return { kind: 'skip' }\n },\n})\n","/**\n * GitHub README resolver — fetches the readme URL at the resolved git ref.\n *\n * Runs whenever we have a GitHub repo URL. Uses any prior `gitRef` from\n * the git-tag step and the npm `repository.directory` subdir if present.\n */\n\nimport { parseGitHubUrl } from '../../core/url.ts'\nimport { fetchReadme } from '../github.ts'\nimport { defineResolver } from '../resolver-registry.ts'\n\nexport const githubReadmeResolver = defineResolver({\n id: 'readme',\n canResolve: ctx => !!ctx.result?.repoUrl?.includes('github.com'),\n async run(ctx) {\n const result = ctx.result!\n const gh = parseGitHubUrl(result.repoUrl!)\n if (!gh)\n return { kind: 'skip' }\n\n ctx.options.onProgress?.('readme')\n const readmeUrl = await fetchReadme(gh.owner, gh.repo, ctx.subdir, result.gitRef)\n if (readmeUrl) {\n result.readmeUrl = readmeUrl\n ctx.attempts.push({ source: 'readme', url: readmeUrl, status: 'success' })\n return { kind: 'ok' }\n }\n ctx.attempts.push({\n source: 'readme',\n url: `${result.repoUrl}/README.md`,\n status: 'not-found',\n message: 'No README found',\n })\n return { kind: 'skip' }\n },\n})\n","/**\n * GitHub search fallback — runs only when npm metadata had no repository URL.\n *\n * Searches GitHub by package name and records the discovered repo URL on\n * `ctx.result.repoUrl` so downstream github-* resolvers can pick it up.\n */\n\nimport { searchGitHubRepo } from '../github.ts'\nimport { defineResolver } from '../resolver-registry.ts'\n\nexport const githubSearchResolver = defineResolver({\n id: 'github-search',\n canResolve: ctx => !!ctx.result && !ctx.result.repoUrl,\n async run(ctx) {\n const result = ctx.result!\n ctx.options.onProgress?.('github-search')\n const searchedUrl = await searchGitHubRepo(ctx.packageName)\n if (searchedUrl) {\n result.repoUrl = searchedUrl\n ctx.attempts.push({\n source: 'github-search',\n url: searchedUrl,\n status: 'success',\n message: `Found via GitHub search: ${searchedUrl}`,\n })\n return { kind: 'ok' }\n }\n ctx.attempts.push({\n source: 'github-search',\n status: 'not-found',\n message: 'No repository URL in package.json and GitHub search found no match',\n })\n return { kind: 'skip' }\n },\n})\n","/**\n * llms.txt resolver — discovers `llms.txt` under the package's docs site.\n *\n * Two phases:\n * 1. Look for an llms.txt URL anchored at `result.docsUrl` and record it.\n * 2. If we also have git-docs, cross-validate the heuristic git-docs\n * against the llms.txt link set; discard git-docs if match ratio is low.\n */\n\nimport { validateGitDocsWithLlms } from '../github.ts'\nimport { fetchLlmsTxt, fetchLlmsUrl } from '../llms.ts'\nimport { defineResolver } from '../resolver-registry.ts'\n\nexport const llmsTxtResolver = defineResolver({\n id: 'llms.txt',\n canResolve: ctx => !!ctx.result?.docsUrl,\n async run(ctx) {\n const result = ctx.result!\n ctx.options.onProgress?.('llms.txt')\n const llmsUrl = await fetchLlmsUrl(result.docsUrl!)\n if (llmsUrl) {\n result.llmsUrl = llmsUrl\n ctx.attempts.push({ source: 'llms.txt', url: llmsUrl, status: 'success' })\n }\n else {\n ctx.attempts.push({\n source: 'llms.txt',\n url: `${new URL(result.docsUrl!).origin}/llms.txt`,\n status: 'not-found',\n message: 'No llms.txt at docs URL',\n })\n }\n\n // Cross-validate heuristic git-docs against llms.txt link set.\n if (result.gitDocsUrl && result.llmsUrl && ctx.gitDocsAllFiles) {\n const llmsContent = await fetchLlmsTxt(result.llmsUrl)\n if (llmsContent && llmsContent.links.length > 0) {\n const validation = validateGitDocsWithLlms(llmsContent.links, ctx.gitDocsAllFiles)\n if (!validation.isValid) {\n ctx.attempts.push({\n source: 'github-docs',\n url: result.gitDocsUrl,\n status: 'not-found',\n message: `Heuristic git docs don't match llms.txt links (${Math.round(validation.matchRatio * 100)}% match), preferring llms.txt`,\n })\n result.gitDocsUrl = undefined\n result.gitRef = undefined\n }\n }\n }\n\n return { kind: 'ok' }\n },\n})\n","/**\n * Local readme fallback resolver — last-ditch lookup in `node_modules`.\n *\n * Runs only when nothing else has populated docs/llms/readme/gitDocs and a\n * `cwd` is available. Finds `README.md` under `node_modules/<pkg>/` and\n * records it as a `file://` URL.\n */\n\nimport { existsSync, readdirSync } from 'node:fs'\nimport { pathToFileURL } from 'node:url'\nimport { join } from 'pathe'\nimport { README_FILENAME_RE } from '../../core/regex.ts'\nimport { defineResolver } from '../resolver-registry.ts'\n\nexport const localReadmeResolver = defineResolver({\n id: 'local',\n canResolve: (ctx) => {\n const r = ctx.result\n return !!r && !!ctx.options.cwd && !r.docsUrl && !r.llmsUrl && !r.readmeUrl && !r.gitDocsUrl\n },\n async run(ctx) {\n const result = ctx.result!\n ctx.options.onProgress?.('local')\n const pkgDir = join(ctx.options.cwd!, 'node_modules', ctx.packageName)\n const readmeFile = existsSync(pkgDir) && readdirSync(pkgDir).find(f => README_FILENAME_RE.test(f))\n if (readmeFile) {\n const readmePath = join(pkgDir, readmeFile)\n result.readmeUrl = pathToFileURL(readmePath).href\n ctx.attempts.push({\n source: 'readme',\n url: readmePath,\n status: 'success',\n message: 'Found local readme in node_modules',\n })\n return { kind: 'ok' }\n }\n return { kind: 'skip' }\n },\n})\n","/**\n * npm metadata resolver — bootstrap step of the cascade.\n *\n * Fetches the package's npm registry record. On success, seeds\n * `ctx.result` with name/version/description/deps and records repo +\n * homepage hints for downstream resolvers. On miss, fatal-exits the cascade.\n */\n\nimport type { ResolvedPackage } from '../types.ts'\nimport { isGitHubRepoUrl, isUselessDocsUrl, normalizeRepoUrl, parseGitHubUrl } from '../../core/url.ts'\nimport { fetchNpmPackage, fetchNpmRegistryMeta } from '../npm-registry.ts'\nimport { defineResolver } from '../resolver-registry.ts'\n\nconst STATIC_REGEX_1 = /^github:/\n\nexport const npmResolver = defineResolver({\n id: 'npm',\n async run(ctx) {\n ctx.options.onProgress?.('npm')\n const pkg = await fetchNpmPackage(ctx.packageName)\n if (!pkg) {\n ctx.attempts.push({\n source: 'npm',\n url: `https://registry.npmjs.org/${ctx.packageName}/latest`,\n status: 'not-found',\n message: 'Package not found on npm registry',\n })\n return { kind: 'fatal' }\n }\n\n ctx.attempts.push({\n source: 'npm',\n url: `https://registry.npmjs.org/${ctx.packageName}/latest`,\n status: 'success',\n message: `Found ${pkg.name}@${pkg.version}`,\n })\n\n const registryMeta = pkg.version\n ? await fetchNpmRegistryMeta(ctx.packageName, pkg.version)\n : {}\n\n const result: ResolvedPackage = {\n name: pkg.name,\n version: pkg.version,\n releasedAt: registryMeta.releasedAt,\n description: pkg.description,\n dependencies: pkg.dependencies,\n distTags: registryMeta.distTags,\n }\n\n if (typeof pkg.repository === 'object' && pkg.repository?.url) {\n ctx.rawRepoUrl = pkg.repository.url\n const normalized = normalizeRepoUrl(pkg.repository.url)\n if (!normalized.includes('://') && normalized.includes('/') && !normalized.includes(':'))\n result.repoUrl = `https://github.com/${normalized}`\n else\n result.repoUrl = normalized\n ctx.subdir = pkg.repository.directory\n }\n else if (typeof pkg.repository === 'string') {\n if (pkg.repository.includes('://')) {\n const gh = parseGitHubUrl(pkg.repository)\n if (gh)\n result.repoUrl = `https://github.com/${gh.owner}/${gh.repo}`\n }\n else {\n const repo = pkg.repository.replace(STATIC_REGEX_1, '')\n if (repo.includes('/') && !repo.includes(':'))\n result.repoUrl = `https://github.com/${repo}`\n }\n }\n\n if (pkg.homepage && !isGitHubRepoUrl(pkg.homepage) && !isUselessDocsUrl(pkg.homepage))\n result.docsUrl = pkg.homepage\n\n ctx.npm = pkg\n ctx.result = result\n return { kind: 'ok' }\n },\n})\n","/**\n * Default URL-resolution cascade order.\n *\n * Order is load-bearing — it preserves the success-rate characteristics\n * of the pre-registry inlined cascade. Steps:\n *\n * 1. npm — bootstrap; mandatory.\n * 2. github-search — fallback when npm had no repository URL.\n * 3. github-docs — versioned `docs/` at git tag.\n * 4. github-meta — homepage from repo metadata (if no docsUrl yet).\n * 5. readme — README at resolved git ref.\n * 6. crawl — record curated crawl pattern.\n * 7. llms.txt — discover + cross-validate against git-docs.\n * 8. local — node_modules readme fallback.\n */\n\nimport type { Resolver } from '../resolver-registry.ts'\nimport { crawlUrlResolver } from './crawl-url.ts'\nimport { gitTagResolver } from './git-tag.ts'\nimport { githubMetaResolver } from './github-meta.ts'\nimport { githubReadmeResolver } from './github-readme.ts'\nimport { githubSearchResolver } from './github-search.ts'\nimport { llmsTxtResolver } from './llms-txt.ts'\nimport { localReadmeResolver } from './local-readme.ts'\nimport { npmResolver } from './npm.ts'\n\nexport const defaultResolvers: Resolver[] = [\n npmResolver,\n githubSearchResolver,\n gitTagResolver,\n githubMetaResolver,\n githubReadmeResolver,\n crawlUrlResolver,\n llmsTxtResolver,\n localReadmeResolver,\n]\n","/**\n * Typed registry for the URL resolution cascade.\n *\n * Each `Resolver` is one step in the cascade (npm metadata, github tags,\n * llms.txt, etc). The cascade is stateful: each step reads and mutates a\n * shared `ResolvedPackage` accumulator and pushes diagnostic attempts.\n *\n * `createContentResolver` walks an ordered list of resolvers and aggregates\n * results into a `ResolveResult`. Built-in resolvers live in\n * `./resolvers/` and the default order is `defaultResolvers`.\n */\n\nimport type { NpmPackageInfo, ResolveAttempt, ResolvedPackage, ResolveResult } from './types.ts'\nimport { defaultResolvers } from './resolvers/default.ts'\n\nexport type ResolveStep\n = | 'npm'\n | 'github-docs'\n | 'github-meta'\n | 'github-search'\n | 'readme'\n | 'llms.txt'\n | 'crawl'\n | 'local'\n\n/**\n * Mutable cascade state shared across resolvers.\n *\n * Resolvers read prior fields (e.g. `repoUrl`, `docsUrl`) to decide what to\n * do, and write their findings back so later resolvers can see them.\n */\nexport interface ResolveCtx {\n /** Package name being resolved. */\n packageName: string\n /** Caller-provided options. */\n options: ResolveOptions\n /** Accumulator — built up step by step. `null` until npm step succeeds. */\n result: ResolvedPackage | null\n /** Diagnostic trail. */\n attempts: ResolveAttempt[]\n /** npm metadata fetched by the npm resolver — read by later steps. */\n npm?: NpmPackageInfo\n /** Repository URL string from npm package.json (pre-normalization). */\n rawRepoUrl?: string\n /** Subdirectory inside the repo (npm `repository.directory`). */\n subdir?: string\n /** All files discovered under git docs/, used by llms.txt validation. */\n gitDocsAllFiles?: string[]\n}\n\nexport interface ResolveOptions {\n /** Specific version to target for versioned git docs. */\n version?: string\n /** Working directory for local node_modules readme fallback. */\n cwd?: string\n /** Called before each cascade step runs. */\n onProgress?: (step: ResolveStep) => void\n}\n\n/** Outcome of a single resolver run. */\nexport type ResolverOutcome\n = | { kind: 'ok' }\n | { kind: 'skip', reason?: string }\n | { kind: 'fatal', registryVersion?: string }\n\nexport interface Resolver {\n id: ResolveStep | string\n /** Cheap pre-check; if false the resolver is skipped without invoking `run`. */\n canResolve?: (ctx: ResolveCtx) => boolean\n /** Mutates `ctx.result` / `ctx.attempts`; may short-circuit via `fatal`. */\n run: (ctx: ResolveCtx) => Promise<ResolverOutcome>\n}\n\nexport function defineResolver(r: Resolver): Resolver {\n return r\n}\n\nexport interface ContentResolver {\n /** Walks the configured resolvers, returning the aggregate cascade result. */\n resolve: (packageName: string, options?: ResolveOptions) => Promise<ResolveResult>\n}\n\nexport function createContentResolver(opts: { resolvers: Resolver[] }): ContentResolver {\n return {\n async resolve(packageName, options = {}) {\n const ctx: ResolveCtx = {\n packageName,\n options,\n result: null,\n attempts: [],\n }\n\n let registryVersion: string | undefined\n\n for (const resolver of opts.resolvers) {\n if (resolver.canResolve && !resolver.canResolve(ctx))\n continue\n const outcome = await resolver.run(ctx)\n if (outcome.kind === 'fatal') {\n return {\n package: null,\n attempts: ctx.attempts,\n registryVersion: outcome.registryVersion ?? registryVersion,\n }\n }\n }\n\n // Capture npm version even when downstream resolvers find nothing.\n registryVersion = ctx.npm?.version\n\n // If no useful URLs found, return null package but keep attempts.\n const r = ctx.result\n if (!r || (!r.docsUrl && !r.llmsUrl && !r.readmeUrl && !r.gitDocsUrl))\n return { package: null, attempts: ctx.attempts, registryVersion }\n\n return { package: r, attempts: ctx.attempts, registryVersion }\n },\n }\n}\n\nconst defaultContentResolver = createContentResolver({ resolvers: defaultResolvers })\n\nexport async function resolvePackageDocs(packageName: string, options: ResolveOptions = {}): Promise<ResolvedPackage | null> {\n const result = await defaultContentResolver.resolve(packageName, options)\n return result.package\n}\n\nexport async function resolvePackageDocsWithAttempts(packageName: string, options: ResolveOptions = {}): Promise<ResolveResult> {\n return defaultContentResolver.resolve(packageName, options)\n}\n","/**\n * Unified entry for npm/crate package resolution.\n *\n * Owns the dispatch between `resolvePackageDocsWithAttempts` (npm),\n * `resolveCrateDocsWithAttempts` (crates.io), and the `link:` local\n * dependency fallback. Returns a single `PackageResolution` with all\n * the derived names callers need (identity, storage, lockfile, display).\n *\n * Used by every sync flow that takes a `package` or `crate:name` spec.\n */\nimport type { ResolveAttempt, ResolvedPackage } from './index.ts'\nimport { isCrateSpec, toCrateIdentity, toStoragePackageName } from '../core/prefix.ts'\nimport { parsePackageSpec } from '../core/url.ts'\nimport { resolveCrateDocsWithAttempts } from './crates.ts'\nimport { readLocalDependencies, resolveLocalDep } from './local-package.ts'\nimport { resolvePackageDocsWithAttempts } from './resolver.ts'\n\nconst RESOLVE_STEP_LABELS: Record<string, string> = {\n 'npm': 'npm registry',\n 'github-docs': 'GitHub docs',\n 'github-meta': 'GitHub meta',\n 'github-search': 'GitHub search',\n 'readme': 'README',\n 'llms.txt': 'llms.txt',\n 'crawl': 'website crawl',\n 'local': 'node_modules',\n}\n\nexport interface PackageResolution {\n /** Bare package name (lower-cased for crates). */\n packageName: string\n /** Public/lockfile name (`crate:` prefix retained). */\n identityPackageName: string\n /** Cache-safe name used for `~/.skilld/references/<name>@<version>/`. */\n storagePackageName: string\n isCrate: boolean\n /** Tag/version requested in the spec (e.g. \"beta\" from \"vue@beta\"). */\n requestedTag?: string\n /** Version pinned in the project's package.json, if any. */\n localVersion?: string\n /** Resolved package metadata, or null when no docs source matched. */\n resolved: ResolvedPackage | null\n /** Per-source resolution log (npm, github-docs, llms.txt, ...). */\n attempts: ResolveAttempt[]\n /** npm registry version even when docs resolution failed. */\n registryVersion?: string\n}\n\nexport interface ResolvePackageOptions {\n cwd: string\n /**\n * Progress label callback. For npm, raw `ResolveStep` values are translated\n * to friendly names (`npm registry`, `GitHub docs`, ...) — the same set of\n * strings the legacy `RESOLVE_STEP_LABELS` map produced.\n */\n onProgress?: (message: string) => void\n}\n\n/**\n * Resolve `packageSpec` to a `PackageResolution`. `packageSpec` may be:\n * - bare npm name: `vue`, `@scope/pkg`\n * - npm with tag/version: `vue@beta`, `vue@3.4.0`\n * - crate spec: `crate:tokio`, `crate:serde@1`\n *\n * Always returns a result; `resolved` is null when no docs source matched.\n * The caller decides whether to fall through to shipped-skills, npm\n * search-and-suggest, or hard error.\n */\nexport async function resolvePackageOrCrate(\n packageSpec: string,\n opts: ResolvePackageOptions,\n): Promise<PackageResolution> {\n const { cwd, onProgress } = opts\n const isCrate = isCrateSpec(packageSpec)\n const normalizedSpec = isCrate ? packageSpec.slice('crate:'.length).trim() : packageSpec\n\n const { name: parsedName, tag: requestedTag } = parsePackageSpec(normalizedSpec)\n const packageName = isCrate ? parsedName.toLowerCase() : parsedName\n const identityPackageName = isCrate ? toCrateIdentity(packageName) : packageName\n const storagePackageName = toStoragePackageName(identityPackageName)\n\n const localDeps = isCrate ? [] : await readLocalDependencies(cwd).catch(() => [])\n const localVersion = isCrate ? undefined : localDeps.find(d => d.name === packageName)?.version\n\n const resolveResult = isCrate\n ? await resolveCrateDocsWithAttempts(packageName, {\n version: requestedTag,\n onProgress,\n })\n : await resolvePackageDocsWithAttempts(requestedTag ? normalizedSpec : packageName, {\n version: localVersion,\n cwd,\n onProgress: step => onProgress?.(RESOLVE_STEP_LABELS[step] ?? step),\n })\n\n let resolved = resolveResult.package\n if (!resolved && !isCrate) {\n onProgress?.(RESOLVE_STEP_LABELS.local!)\n resolved = await resolveLocalDep(packageName, cwd)\n }\n\n return {\n packageName,\n identityPackageName,\n storagePackageName,\n isCrate,\n requestedTag,\n localVersion,\n resolved,\n attempts: resolveResult.attempts,\n registryVersion: resolveResult.registryVersion,\n }\n}\n","/**\n * Thin semver wrappers that pin `loose: true` at every callsite.\n * Centralized so the loose flag stays consistent across the project.\n */\n\nimport { diff as _diff, gt as _gt, valid as _valid } from 'semver'\n\n/** Returns the cleaned version if valid semver, null otherwise. */\nexport function semverValid(v: string): string | null {\n return _valid(v, true)\n}\n\n/** Compare two semver strings: returns true if a > b. Handles prereleases. */\nexport function semverGt(a: string, b: string): boolean {\n return _gt(a, b, true)\n}\n\n/** Returns the semver diff type between two versions, or null if equal/invalid. */\nexport function semverDiff(a: string, b: string): string | null {\n return _diff(a, b)\n}\n"],"mappings":"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"}
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import { c as SHARED_SKILLS_DIR, d as getSharedSkillsDir } from "./paths.mjs";
|
|
2
|
-
import { F as registerProject } from "./cache.mjs";
|
|
3
|
-
import { n as linkShippedSkill, t as getShippedSkills } from "./prepare.mjs";
|
|
4
1
|
import { a as targets } from "./detect.mjs";
|
|
5
2
|
import "./agent.mjs";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
3
|
+
import { g as todayIsoDate, i as linkSkillToAgents } from "./prompts.mjs";
|
|
4
|
+
import { f as getSharedSkillsDir, l as SHARED_SKILLS_DIR } from "./paths.mjs";
|
|
5
|
+
import { i as linkShippedSkill, n as getShippedSkills } from "./prepare.mjs";
|
|
6
|
+
import { f as registerProject } from "./cache.mjs";
|
|
7
|
+
import { t as isInteractive } from "./env.mjs";
|
|
8
8
|
import { c as readLock, d as writeLock, l as removeLockEntry, r as findSkillDirsByPackage } from "./lockfile.mjs";
|
|
9
9
|
import { appendFileSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
10
|
-
import {
|
|
10
|
+
import { styleText } from "node:util";
|
|
11
11
|
import * as p from "@clack/prompts";
|
|
12
|
+
import { join, relative } from "pathe";
|
|
12
13
|
function handleShippedSkills(packageName, version, cwd, agent, global) {
|
|
13
14
|
const shippedSkills = getShippedSkills(packageName, cwd, version);
|
|
14
15
|
if (shippedSkills.length === 0) return null;
|
|
@@ -79,7 +80,7 @@ async function ensureGitignore(skillsDir, cwd, isGlobal) {
|
|
|
79
80
|
return;
|
|
80
81
|
}
|
|
81
82
|
const relSkillsDir = relative(cwd, skillsDir) || ".";
|
|
82
|
-
p.log.info(
|
|
83
|
+
p.log.info(`${styleText("bold", "Git guidance:")}\n ${styleText("green", "✓")} Commit: ${styleText("cyan", `${relSkillsDir}/*/SKILL.md`)}\n ${styleText("green", "✓")} Commit: ${styleText("cyan", `${relSkillsDir}/skilld-lock.yaml`)}\n ${styleText("red", "✗")} Ignore: ${styleText("cyan", pattern)} ${styleText("gray", "(recreated by `skilld install`)")}`);
|
|
83
84
|
const add = await p.confirm({
|
|
84
85
|
message: `Add \`${pattern}\` to .gitignore?`,
|
|
85
86
|
initialValue: true
|
|
@@ -115,8 +116,7 @@ async function ensureAgentInstructions(agent, cwd, isGlobal) {
|
|
|
115
116
|
p.note(`This tells your agent to check installed skills before making
|
|
116
117
|
code changes. Without it, skills are available but may not
|
|
117
118
|
activate automatically.
|
|
118
|
-
|
|
119
|
-
\x1B[90m${getMdcSkillInstructions(agent)}\x1B[0m`, `Create ${agentConfig.instructionFile}`);
|
|
119
|
+
\n${styleText("gray", getMdcSkillInstructions(agent))}`, `Create ${agentConfig.instructionFile}`);
|
|
120
120
|
const add = await p.confirm({
|
|
121
121
|
message: `Create ${agentConfig.instructionFile} with skill activation instructions?`,
|
|
122
122
|
initialValue: true
|
|
@@ -139,8 +139,7 @@ activate automatically.
|
|
|
139
139
|
p.note(`This tells your agent to check installed skills before making
|
|
140
140
|
code changes. Without it, skills are available but may not
|
|
141
141
|
activate automatically.
|
|
142
|
-
|
|
143
|
-
\x1B[90m${getSkillInstructions(agent).replace(/\n/g, "\n")}\x1B[0m`, `${action} ${agentConfig.instructionFile}`);
|
|
142
|
+
\n${styleText("gray", getSkillInstructions(agent).replace(/\n/g, "\n"))}`, `${action} ${agentConfig.instructionFile}`);
|
|
144
143
|
const add = await p.confirm({
|
|
145
144
|
message: `${action} ${agentConfig.instructionFile} with skill activation instructions?`,
|
|
146
145
|
initialValue: true
|