skilld 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -23
- package/dist/_chunks/config.mjs +8 -2
- package/dist/_chunks/config.mjs.map +1 -1
- package/dist/_chunks/llm.mjs +710 -204
- package/dist/_chunks/llm.mjs.map +1 -1
- package/dist/_chunks/pool.mjs +115 -0
- package/dist/_chunks/pool.mjs.map +1 -0
- package/dist/_chunks/releases.mjs +689 -179
- package/dist/_chunks/releases.mjs.map +1 -1
- package/dist/_chunks/storage.mjs +311 -19
- package/dist/_chunks/storage.mjs.map +1 -1
- package/dist/_chunks/sync-parallel.mjs +134 -378
- package/dist/_chunks/sync-parallel.mjs.map +1 -1
- package/dist/_chunks/types.d.mts +9 -6
- package/dist/_chunks/types.d.mts.map +1 -1
- package/dist/_chunks/utils.d.mts +137 -68
- package/dist/_chunks/utils.d.mts.map +1 -1
- package/dist/_chunks/version.d.mts +43 -6
- package/dist/_chunks/version.d.mts.map +1 -1
- package/dist/agent/index.d.mts +58 -15
- package/dist/agent/index.d.mts.map +1 -1
- package/dist/agent/index.mjs +4 -2
- package/dist/cache/index.d.mts +2 -2
- package/dist/cache/index.mjs +2 -2
- package/dist/cli.mjs +2170 -1436
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +4 -3
- package/dist/index.mjs +2 -2
- package/dist/retriv/index.d.mts +16 -2
- package/dist/retriv/index.d.mts.map +1 -1
- package/dist/retriv/index.mjs +44 -15
- package/dist/retriv/index.mjs.map +1 -1
- package/dist/retriv/worker.d.mts +33 -0
- package/dist/retriv/worker.d.mts.map +1 -0
- package/dist/retriv/worker.mjs +47 -0
- package/dist/retriv/worker.mjs.map +1 -0
- package/dist/sources/index.d.mts +2 -2
- package/dist/sources/index.mjs +2 -2
- package/dist/types.d.mts +5 -3
- package/package.json +11 -7
|
@@ -1,62 +1,267 @@
|
|
|
1
1
|
import { a as getCacheDir } from "./config.mjs";
|
|
2
|
-
import { join, resolve } from "
|
|
3
|
-
import { createWriteStream, existsSync, mkdirSync, readFileSync, rmSync, unlinkSync } from "node:fs";
|
|
4
|
-
import {
|
|
2
|
+
import { basename, dirname, join, resolve } from "pathe";
|
|
3
|
+
import { createWriteStream, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, unlinkSync } from "node:fs";
|
|
4
|
+
import { spawnSync } from "node:child_process";
|
|
5
5
|
import { globby } from "globby";
|
|
6
|
+
import { ofetch } from "ofetch";
|
|
7
|
+
import pLimit from "p-limit";
|
|
6
8
|
import { Writable } from "node:stream";
|
|
7
9
|
import { pathToFileURL } from "node:url";
|
|
10
|
+
import { resolvePathSync } from "mlly";
|
|
8
11
|
let _ghAvailable;
|
|
9
12
|
function isGhAvailable() {
|
|
10
13
|
if (_ghAvailable !== void 0) return _ghAvailable;
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
return _ghAvailable = true;
|
|
14
|
-
} catch {
|
|
15
|
-
return _ghAvailable = false;
|
|
16
|
-
}
|
|
14
|
+
const { status } = spawnSync("gh", ["auth", "status"], { stdio: "ignore" });
|
|
15
|
+
return _ghAvailable = status === 0;
|
|
17
16
|
}
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
const BOT_USERS = new Set([
|
|
18
|
+
"renovate[bot]",
|
|
19
|
+
"dependabot[bot]",
|
|
20
|
+
"renovate-bot",
|
|
21
|
+
"dependabot",
|
|
22
|
+
"github-actions[bot]"
|
|
23
|
+
]);
|
|
24
|
+
const NOISE_LABELS = new Set([
|
|
25
|
+
"duplicate",
|
|
26
|
+
"stale",
|
|
27
|
+
"invalid",
|
|
28
|
+
"wontfix",
|
|
29
|
+
"won't fix",
|
|
30
|
+
"spam",
|
|
31
|
+
"off-topic",
|
|
32
|
+
"needs triage",
|
|
33
|
+
"triage"
|
|
34
|
+
]);
|
|
35
|
+
const FEATURE_LABELS = new Set([
|
|
36
|
+
"enhancement",
|
|
37
|
+
"feature",
|
|
38
|
+
"feature request",
|
|
39
|
+
"feature-request",
|
|
40
|
+
"proposal",
|
|
41
|
+
"rfc",
|
|
42
|
+
"idea",
|
|
43
|
+
"suggestion"
|
|
44
|
+
]);
|
|
45
|
+
const BUG_LABELS = new Set([
|
|
46
|
+
"bug",
|
|
47
|
+
"defect",
|
|
48
|
+
"regression",
|
|
49
|
+
"error",
|
|
50
|
+
"crash",
|
|
51
|
+
"fix",
|
|
52
|
+
"confirmed",
|
|
53
|
+
"verified"
|
|
54
|
+
]);
|
|
55
|
+
const QUESTION_LABELS = new Set([
|
|
56
|
+
"question",
|
|
57
|
+
"help wanted",
|
|
58
|
+
"support",
|
|
59
|
+
"usage",
|
|
60
|
+
"how-to",
|
|
61
|
+
"help",
|
|
62
|
+
"assistance"
|
|
63
|
+
]);
|
|
64
|
+
const DOCS_LABELS = new Set([
|
|
65
|
+
"documentation",
|
|
66
|
+
"docs",
|
|
67
|
+
"doc",
|
|
68
|
+
"typo"
|
|
69
|
+
]);
|
|
70
|
+
function classifyIssue(labels) {
|
|
71
|
+
const lower = labels.map((l) => l.toLowerCase());
|
|
72
|
+
if (lower.some((l) => BUG_LABELS.has(l))) return "bug";
|
|
73
|
+
if (lower.some((l) => QUESTION_LABELS.has(l))) return "question";
|
|
74
|
+
if (lower.some((l) => DOCS_LABELS.has(l))) return "docs";
|
|
75
|
+
if (lower.some((l) => FEATURE_LABELS.has(l))) return "feature";
|
|
76
|
+
return "other";
|
|
77
|
+
}
|
|
78
|
+
function isNoiseIssue(issue) {
|
|
79
|
+
if (issue.labels.map((l) => l.toLowerCase()).some((l) => NOISE_LABELS.has(l))) return true;
|
|
80
|
+
if (issue.title.startsWith("☂️") || issue.title.startsWith("[META]") || issue.title.startsWith("[Tracking]")) return true;
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
function bodyLimit(reactions) {
|
|
84
|
+
if (reactions >= 10) return 2e3;
|
|
85
|
+
if (reactions >= 5) return 1500;
|
|
86
|
+
return 800;
|
|
87
|
+
}
|
|
88
|
+
function fetchIssuesByState(owner, repo, state, count) {
|
|
89
|
+
const fetchCount = Math.min(count * 3, 100);
|
|
90
|
+
const { stdout: result } = spawnSync("gh", [
|
|
91
|
+
"api",
|
|
92
|
+
`search/issues?q=${`repo:${owner}/${repo}+is:issue+is:${state}${state === "closed" ? `+closed:>${oneYearAgo()}` : ""}`}&sort=reactions&order=desc&per_page=${fetchCount}`,
|
|
93
|
+
"-q",
|
|
94
|
+
".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}"
|
|
95
|
+
], {
|
|
96
|
+
encoding: "utf-8",
|
|
97
|
+
maxBuffer: 10 * 1024 * 1024
|
|
98
|
+
});
|
|
99
|
+
if (!result) return [];
|
|
100
|
+
return result.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line)).filter((issue) => !BOT_USERS.has(issue.user) && issue.userType !== "Bot").filter((issue) => !isNoiseIssue(issue)).map(({ user: _, userType: __, ...issue }) => ({
|
|
101
|
+
...issue,
|
|
102
|
+
type: classifyIssue(issue.labels),
|
|
103
|
+
topComments: []
|
|
104
|
+
})).sort((a, b) => (a.type === "feature" ? 1 : 0) - (b.type === "feature" ? 1 : 0)).slice(0, count);
|
|
105
|
+
}
|
|
106
|
+
function oneYearAgo() {
|
|
107
|
+
const d = /* @__PURE__ */ new Date();
|
|
108
|
+
d.setFullYear(d.getFullYear() - 1);
|
|
109
|
+
return d.toISOString().split("T")[0];
|
|
110
|
+
}
|
|
111
|
+
function enrichWithComments(owner, repo, issues, topN = 10) {
|
|
112
|
+
const worth = issues.filter((i) => i.comments > 0 && (i.type === "bug" || i.type === "question" || i.reactions >= 3)).sort((a, b) => b.reactions - a.reactions).slice(0, topN);
|
|
113
|
+
if (worth.length === 0) return;
|
|
114
|
+
const query = `query($owner: String!, $repo: String!) { repository(owner: $owner, name: $repo) { ${worth.map((issue, i) => `i${i}: issue(number: ${issue.number}) { comments(first: 3) { nodes { body author { login } reactions { totalCount } } } }`).join(" ")} } }`;
|
|
20
115
|
try {
|
|
21
|
-
const result =
|
|
116
|
+
const { stdout: result } = spawnSync("gh", [
|
|
117
|
+
"api",
|
|
118
|
+
"graphql",
|
|
119
|
+
"-f",
|
|
120
|
+
`query=${query}`,
|
|
121
|
+
"-f",
|
|
122
|
+
`owner=${owner}`,
|
|
123
|
+
"-f",
|
|
124
|
+
`repo=${repo}`
|
|
125
|
+
], {
|
|
22
126
|
encoding: "utf-8",
|
|
23
127
|
maxBuffer: 10 * 1024 * 1024
|
|
24
128
|
});
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
129
|
+
if (!result) return;
|
|
130
|
+
const repo_ = JSON.parse(result)?.data?.repository;
|
|
131
|
+
if (!repo_) return;
|
|
132
|
+
for (let i = 0; i < worth.length; i++) {
|
|
133
|
+
const nodes = repo_[`i${i}`]?.comments?.nodes;
|
|
134
|
+
if (!Array.isArray(nodes)) continue;
|
|
135
|
+
worth[i].topComments = nodes.filter((c) => c.author && !BOT_USERS.has(c.author.login)).map((c) => ({
|
|
136
|
+
body: c.body || "",
|
|
137
|
+
author: c.author.login,
|
|
138
|
+
reactions: c.reactions?.totalCount || 0
|
|
139
|
+
}));
|
|
140
|
+
}
|
|
141
|
+
} catch {}
|
|
142
|
+
}
|
|
143
|
+
async function fetchGitHubIssues(owner, repo, limit = 30) {
|
|
144
|
+
if (!isGhAvailable()) return [];
|
|
145
|
+
const openCount = Math.ceil(limit * .75);
|
|
146
|
+
const closedCount = limit - openCount;
|
|
147
|
+
try {
|
|
148
|
+
const open = fetchIssuesByState(owner, repo, "open", openCount);
|
|
149
|
+
const closed = fetchIssuesByState(owner, repo, "closed", closedCount);
|
|
150
|
+
const all = [...open, ...closed];
|
|
151
|
+
enrichWithComments(owner, repo, all);
|
|
152
|
+
return all;
|
|
33
153
|
} catch {
|
|
34
154
|
return [];
|
|
35
155
|
}
|
|
36
156
|
}
|
|
37
|
-
function
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
157
|
+
function formatIssueAsMarkdown(issue) {
|
|
158
|
+
const limit = bodyLimit(issue.reactions);
|
|
159
|
+
const fm = [
|
|
160
|
+
"---",
|
|
161
|
+
`number: ${issue.number}`,
|
|
162
|
+
`title: "${issue.title.replace(/"/g, "\\\"")}"`,
|
|
163
|
+
`type: ${issue.type}`,
|
|
164
|
+
`state: ${issue.state}`,
|
|
165
|
+
`created: ${issue.createdAt.split("T")[0]}`,
|
|
166
|
+
`url: ${issue.url}`,
|
|
167
|
+
`reactions: ${issue.reactions}`,
|
|
168
|
+
`comments: ${issue.comments}`
|
|
169
|
+
];
|
|
170
|
+
if (issue.labels.length > 0) fm.push(`labels: [${issue.labels.join(", ")}]`);
|
|
171
|
+
fm.push("---");
|
|
172
|
+
const lines = [
|
|
173
|
+
fm.join("\n"),
|
|
174
|
+
"",
|
|
175
|
+
`# ${issue.title}`
|
|
176
|
+
];
|
|
177
|
+
if (issue.body) {
|
|
178
|
+
const body = issue.body.length > limit ? `${issue.body.slice(0, limit)}...` : issue.body;
|
|
179
|
+
lines.push("", body);
|
|
180
|
+
}
|
|
181
|
+
if (issue.topComments.length > 0) {
|
|
182
|
+
lines.push("", "---", "", "## Top Comments");
|
|
183
|
+
for (const c of issue.topComments) {
|
|
184
|
+
const reactions = c.reactions > 0 ? ` (+${c.reactions})` : "";
|
|
185
|
+
const commentBody = c.body.length > 600 ? `${c.body.slice(0, 600)}...` : c.body;
|
|
186
|
+
lines.push("", `**@${c.author}**${reactions}:`, "", commentBody);
|
|
48
187
|
}
|
|
49
|
-
lines.push("\n---\n");
|
|
50
188
|
}
|
|
51
189
|
return lines.join("\n");
|
|
52
190
|
}
|
|
191
|
+
function generateIssueIndex(issues) {
|
|
192
|
+
const byType = /* @__PURE__ */ new Map();
|
|
193
|
+
for (const issue of issues) {
|
|
194
|
+
const list = byType.get(issue.type) || [];
|
|
195
|
+
list.push(issue);
|
|
196
|
+
byType.set(issue.type, list);
|
|
197
|
+
}
|
|
198
|
+
const typeLabels = {
|
|
199
|
+
bug: "Bugs & Regressions",
|
|
200
|
+
question: "Questions & Usage Help",
|
|
201
|
+
docs: "Documentation",
|
|
202
|
+
feature: "Feature Requests",
|
|
203
|
+
other: "Other"
|
|
204
|
+
};
|
|
205
|
+
const typeOrder = [
|
|
206
|
+
"bug",
|
|
207
|
+
"question",
|
|
208
|
+
"docs",
|
|
209
|
+
"other",
|
|
210
|
+
"feature"
|
|
211
|
+
];
|
|
212
|
+
const sections = [
|
|
213
|
+
[
|
|
214
|
+
"---",
|
|
215
|
+
`total: ${issues.length}`,
|
|
216
|
+
`open: ${issues.filter((i) => i.state === "open").length}`,
|
|
217
|
+
`closed: ${issues.filter((i) => i.state !== "open").length}`,
|
|
218
|
+
"---"
|
|
219
|
+
].join("\n"),
|
|
220
|
+
"",
|
|
221
|
+
"# Issues Index",
|
|
222
|
+
""
|
|
223
|
+
];
|
|
224
|
+
for (const type of typeOrder) {
|
|
225
|
+
const group = byType.get(type);
|
|
226
|
+
if (!group?.length) continue;
|
|
227
|
+
sections.push(`## ${typeLabels[type]} (${group.length})`, "");
|
|
228
|
+
for (const issue of group) {
|
|
229
|
+
const reactions = issue.reactions > 0 ? ` (+${issue.reactions})` : "";
|
|
230
|
+
const state = issue.state === "open" ? "" : " [closed]";
|
|
231
|
+
sections.push(`- [#${issue.number}](./issue-${issue.number}.md): ${issue.title}${reactions}${state}`);
|
|
232
|
+
}
|
|
233
|
+
sections.push("");
|
|
234
|
+
}
|
|
235
|
+
return sections.join("\n");
|
|
236
|
+
}
|
|
237
|
+
const HIGH_VALUE_CATEGORIES = new Set([
|
|
238
|
+
"q&a",
|
|
239
|
+
"help",
|
|
240
|
+
"troubleshooting",
|
|
241
|
+
"support"
|
|
242
|
+
]);
|
|
243
|
+
const LOW_VALUE_CATEGORIES = new Set([
|
|
244
|
+
"show and tell",
|
|
245
|
+
"ideas",
|
|
246
|
+
"polls"
|
|
247
|
+
]);
|
|
53
248
|
async function fetchGitHubDiscussions(owner, repo, limit = 20) {
|
|
54
249
|
if (!isGhAvailable()) return [];
|
|
55
250
|
try {
|
|
56
|
-
const
|
|
251
|
+
const { stdout: result } = spawnSync("gh", [
|
|
252
|
+
"api",
|
|
253
|
+
"graphql",
|
|
254
|
+
"-f",
|
|
255
|
+
`query=${`query($owner: String!, $repo: String!) { repository(owner: $owner, name: $repo) { discussions(first: ${Math.min(limit * 3, 80)}, orderBy: {field: CREATED_AT, direction: DESC}) { nodes { number title body category { name } createdAt url upvoteCount comments(first: 3) { totalCount nodes { body author { login } } } answer { body } author { login } } } } }`}`,
|
|
256
|
+
"-f",
|
|
257
|
+
`owner=${owner}`,
|
|
258
|
+
"-f",
|
|
259
|
+
`repo=${repo}`
|
|
260
|
+
], {
|
|
57
261
|
encoding: "utf-8",
|
|
58
262
|
maxBuffer: 10 * 1024 * 1024
|
|
59
263
|
});
|
|
264
|
+
if (!result) return [];
|
|
60
265
|
const nodes = JSON.parse(result)?.data?.repository?.discussions?.nodes;
|
|
61
266
|
if (!Array.isArray(nodes)) return [];
|
|
62
267
|
const BOT_USERS = new Set([
|
|
@@ -66,7 +271,10 @@ async function fetchGitHubDiscussions(owner, repo, limit = 20) {
|
|
|
66
271
|
"dependabot",
|
|
67
272
|
"github-actions[bot]"
|
|
68
273
|
]);
|
|
69
|
-
return nodes.filter((d) => d.author && !BOT_USERS.has(d.author.login)).
|
|
274
|
+
return nodes.filter((d) => d.author && !BOT_USERS.has(d.author.login)).filter((d) => {
|
|
275
|
+
const cat = (d.category?.name || "").toLowerCase();
|
|
276
|
+
return !LOW_VALUE_CATEGORIES.has(cat);
|
|
277
|
+
}).map((d) => ({
|
|
70
278
|
number: d.number,
|
|
71
279
|
title: d.title,
|
|
72
280
|
body: d.body || "",
|
|
@@ -74,33 +282,93 @@ async function fetchGitHubDiscussions(owner, repo, limit = 20) {
|
|
|
74
282
|
createdAt: d.createdAt,
|
|
75
283
|
url: d.url,
|
|
76
284
|
upvoteCount: d.upvoteCount || 0,
|
|
77
|
-
comments: d.comments?.totalCount || 0
|
|
78
|
-
|
|
285
|
+
comments: d.comments?.totalCount || 0,
|
|
286
|
+
answer: d.answer?.body || void 0,
|
|
287
|
+
topComments: (d.comments?.nodes || []).filter((c) => c.author && !BOT_USERS.has(c.author.login)).map((c) => ({
|
|
288
|
+
body: c.body || "",
|
|
289
|
+
author: c.author.login
|
|
290
|
+
}))
|
|
291
|
+
})).sort((a, b) => {
|
|
292
|
+
const aHigh = HIGH_VALUE_CATEGORIES.has(a.category.toLowerCase()) ? 1 : 0;
|
|
293
|
+
const bHigh = HIGH_VALUE_CATEGORIES.has(b.category.toLowerCase()) ? 1 : 0;
|
|
294
|
+
if (aHigh !== bHigh) return bHigh - aHigh;
|
|
295
|
+
return b.upvoteCount + b.comments - (a.upvoteCount + a.comments);
|
|
296
|
+
}).slice(0, limit);
|
|
79
297
|
} catch {
|
|
80
298
|
return [];
|
|
81
299
|
}
|
|
82
300
|
}
|
|
83
|
-
function
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
301
|
+
function formatDiscussionAsMarkdown(d) {
|
|
302
|
+
const fm = [
|
|
303
|
+
"---",
|
|
304
|
+
`number: ${d.number}`,
|
|
305
|
+
`title: "${d.title.replace(/"/g, "\\\"")}"`,
|
|
306
|
+
`category: ${d.category}`,
|
|
307
|
+
`created: ${d.createdAt.split("T")[0]}`,
|
|
308
|
+
`url: ${d.url}`,
|
|
309
|
+
`upvotes: ${d.upvoteCount}`,
|
|
310
|
+
`comments: ${d.comments}`,
|
|
311
|
+
`answered: ${!!d.answer}`,
|
|
312
|
+
"---"
|
|
313
|
+
];
|
|
314
|
+
const bodyLimit = d.upvoteCount >= 5 ? 1500 : 800;
|
|
315
|
+
const lines = [
|
|
316
|
+
fm.join("\n"),
|
|
317
|
+
"",
|
|
318
|
+
`# ${d.title}`
|
|
319
|
+
];
|
|
320
|
+
if (d.body) {
|
|
321
|
+
const body = d.body.length > bodyLimit ? `${d.body.slice(0, bodyLimit)}...` : d.body;
|
|
322
|
+
lines.push("", body);
|
|
323
|
+
}
|
|
324
|
+
if (d.answer) {
|
|
325
|
+
const answerLimit = 1e3;
|
|
326
|
+
const answer = d.answer.length > answerLimit ? `${d.answer.slice(0, answerLimit)}...` : d.answer;
|
|
327
|
+
lines.push("", "---", "", "## Accepted Answer", "", answer);
|
|
328
|
+
} else if (d.topComments.length > 0) {
|
|
329
|
+
lines.push("", "---", "", "## Top Comments");
|
|
330
|
+
for (const c of d.topComments) {
|
|
331
|
+
const commentBody = c.body.length > 600 ? `${c.body.slice(0, 600)}...` : c.body;
|
|
332
|
+
lines.push("", `**@${c.author}:**`, "", commentBody);
|
|
99
333
|
}
|
|
100
|
-
lines.push("\n---\n");
|
|
101
334
|
}
|
|
102
335
|
return lines.join("\n");
|
|
103
336
|
}
|
|
337
|
+
function generateDiscussionIndex(discussions) {
|
|
338
|
+
const byCategory = /* @__PURE__ */ new Map();
|
|
339
|
+
for (const d of discussions) {
|
|
340
|
+
const cat = d.category || "Uncategorized";
|
|
341
|
+
const list = byCategory.get(cat) || [];
|
|
342
|
+
list.push(d);
|
|
343
|
+
byCategory.set(cat, list);
|
|
344
|
+
}
|
|
345
|
+
const answered = discussions.filter((d) => d.answer).length;
|
|
346
|
+
const sections = [
|
|
347
|
+
[
|
|
348
|
+
"---",
|
|
349
|
+
`total: ${discussions.length}`,
|
|
350
|
+
`answered: ${answered}`,
|
|
351
|
+
"---"
|
|
352
|
+
].join("\n"),
|
|
353
|
+
"",
|
|
354
|
+
"# Discussions Index",
|
|
355
|
+
""
|
|
356
|
+
];
|
|
357
|
+
const cats = [...byCategory.keys()].sort((a, b) => {
|
|
358
|
+
return (HIGH_VALUE_CATEGORIES.has(a.toLowerCase()) ? 0 : 1) - (HIGH_VALUE_CATEGORIES.has(b.toLowerCase()) ? 0 : 1) || a.localeCompare(b);
|
|
359
|
+
});
|
|
360
|
+
for (const cat of cats) {
|
|
361
|
+
const group = byCategory.get(cat);
|
|
362
|
+
sections.push(`## ${cat} (${group.length})`, "");
|
|
363
|
+
for (const d of group) {
|
|
364
|
+
const upvotes = d.upvoteCount > 0 ? ` (+${d.upvoteCount})` : "";
|
|
365
|
+
const answered = d.answer ? " [answered]" : "";
|
|
366
|
+
sections.push(`- [#${d.number}](./discussion-${d.number}.md): ${d.title}${upvotes}${answered}`);
|
|
367
|
+
}
|
|
368
|
+
sections.push("");
|
|
369
|
+
}
|
|
370
|
+
return sections.join("\n");
|
|
371
|
+
}
|
|
104
372
|
const SKIP_DIRS = [
|
|
105
373
|
"node_modules",
|
|
106
374
|
"_vendor",
|
|
@@ -153,29 +421,67 @@ async function resolveEntryFiles(packageDir) {
|
|
|
153
421
|
}
|
|
154
422
|
return entries;
|
|
155
423
|
}
|
|
156
|
-
const DOC_OVERRIDES = {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
424
|
+
const DOC_OVERRIDES = {
|
|
425
|
+
"vue": {
|
|
426
|
+
owner: "vuejs",
|
|
427
|
+
repo: "docs",
|
|
428
|
+
path: "src",
|
|
429
|
+
homepage: "https://vuejs.org"
|
|
430
|
+
},
|
|
431
|
+
"tailwindcss": {
|
|
432
|
+
owner: "tailwindlabs",
|
|
433
|
+
repo: "tailwindcss.com",
|
|
434
|
+
path: "src/docs",
|
|
435
|
+
homepage: "https://tailwindcss.com"
|
|
436
|
+
},
|
|
437
|
+
"astro": {
|
|
438
|
+
owner: "withastro",
|
|
439
|
+
repo: "docs",
|
|
440
|
+
path: "src/content/docs/en",
|
|
441
|
+
homepage: "https://docs.astro.build"
|
|
442
|
+
},
|
|
443
|
+
"@vueuse/core": {
|
|
444
|
+
owner: "vueuse",
|
|
445
|
+
repo: "vueuse",
|
|
446
|
+
path: "packages"
|
|
447
|
+
}
|
|
448
|
+
};
|
|
162
449
|
function getDocOverride(packageName) {
|
|
163
450
|
return DOC_OVERRIDES[packageName];
|
|
164
451
|
}
|
|
165
|
-
const
|
|
452
|
+
const $fetch = ofetch.create({
|
|
453
|
+
retry: 3,
|
|
454
|
+
retryDelay: 500,
|
|
455
|
+
timeout: 15e3,
|
|
456
|
+
headers: { "User-Agent": "skilld/1.0" }
|
|
457
|
+
});
|
|
166
458
|
async function fetchText(url) {
|
|
167
|
-
|
|
168
|
-
if (!res?.ok) return null;
|
|
169
|
-
return res.text();
|
|
459
|
+
return $fetch(url, { responseType: "text" }).catch(() => null);
|
|
170
460
|
}
|
|
171
461
|
async function verifyUrl(url) {
|
|
172
|
-
const res = await fetch(url, {
|
|
173
|
-
|
|
174
|
-
headers: { "User-Agent": USER_AGENT }
|
|
175
|
-
}).catch(() => null);
|
|
176
|
-
if (!res?.ok) return false;
|
|
462
|
+
const res = await $fetch.raw(url, { method: "HEAD" }).catch(() => null);
|
|
463
|
+
if (!res) return false;
|
|
177
464
|
return !(res.headers.get("content-type") || "").includes("text/html");
|
|
178
465
|
}
|
|
466
|
+
const USELESS_HOSTS = new Set([
|
|
467
|
+
"twitter.com",
|
|
468
|
+
"x.com",
|
|
469
|
+
"facebook.com",
|
|
470
|
+
"linkedin.com",
|
|
471
|
+
"youtube.com",
|
|
472
|
+
"instagram.com",
|
|
473
|
+
"npmjs.com",
|
|
474
|
+
"www.npmjs.com",
|
|
475
|
+
"yarnpkg.com"
|
|
476
|
+
]);
|
|
477
|
+
function isUselessDocsUrl(url) {
|
|
478
|
+
try {
|
|
479
|
+
const { hostname } = new URL(url);
|
|
480
|
+
return USELESS_HOSTS.has(hostname);
|
|
481
|
+
} catch {
|
|
482
|
+
return false;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
179
485
|
function isGitHubRepoUrl(url) {
|
|
180
486
|
try {
|
|
181
487
|
const parsed = new URL(url);
|
|
@@ -185,7 +491,7 @@ function isGitHubRepoUrl(url) {
|
|
|
185
491
|
}
|
|
186
492
|
}
|
|
187
493
|
function parseGitHubUrl(url) {
|
|
188
|
-
const match = url.match(/github\.com\/([^/]+)\/([^/]
|
|
494
|
+
const match = url.match(/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?(?:[/#]|$)/);
|
|
189
495
|
if (!match) return null;
|
|
190
496
|
return {
|
|
191
497
|
owner: match[1],
|
|
@@ -193,14 +499,21 @@ function parseGitHubUrl(url) {
|
|
|
193
499
|
};
|
|
194
500
|
}
|
|
195
501
|
function normalizeRepoUrl(url) {
|
|
196
|
-
return url.replace(/^git\+/, "").replace(/\.git$/, "").replace(/^git:\/\//, "https://").replace(/^ssh:\/\/git@github\.com/, "https://github.com");
|
|
502
|
+
return url.replace(/^git\+/, "").replace(/#.*$/, "").replace(/\.git$/, "").replace(/^git:\/\//, "https://").replace(/^ssh:\/\/git@github\.com/, "https://github.com").replace(/^git@github\.com:/, "https://github.com/");
|
|
503
|
+
}
|
|
504
|
+
function extractBranchHint(url) {
|
|
505
|
+
const hash = url.indexOf("#");
|
|
506
|
+
if (hash === -1) return void 0;
|
|
507
|
+
const fragment = url.slice(hash + 1);
|
|
508
|
+
if (!fragment || fragment === "readme") return void 0;
|
|
509
|
+
return fragment;
|
|
197
510
|
}
|
|
511
|
+
const MIN_GIT_DOCS = 5;
|
|
512
|
+
const isShallowGitDocs = (n) => n > 0 && n < MIN_GIT_DOCS;
|
|
198
513
|
async function listFilesAtRef(owner, repo, ref) {
|
|
199
|
-
|
|
200
|
-
if (!res?.ok) return [];
|
|
201
|
-
return (await res.json().catch(() => null))?.files?.map((f) => f.path) ?? [];
|
|
514
|
+
return (await $fetch(`https://ungh.cc/repos/${owner}/${repo}/files/${ref}`).catch(() => null))?.files?.map((f) => f.path) ?? [];
|
|
202
515
|
}
|
|
203
|
-
async function findGitTag(owner, repo, version, packageName) {
|
|
516
|
+
async function findGitTag(owner, repo, version, packageName, branchHint) {
|
|
204
517
|
const candidates = [`v${version}`, version];
|
|
205
518
|
if (packageName) candidates.push(`${packageName}@${version}`);
|
|
206
519
|
for (const tag of candidates) {
|
|
@@ -220,7 +533,8 @@ async function findGitTag(owner, repo, version, packageName) {
|
|
|
220
533
|
};
|
|
221
534
|
}
|
|
222
535
|
}
|
|
223
|
-
|
|
536
|
+
const branches = branchHint ? [branchHint, ...["main", "master"].filter((b) => b !== branchHint)] : ["main", "master"];
|
|
537
|
+
for (const branch of branches) {
|
|
224
538
|
const files = await listFilesAtRef(owner, repo, branch);
|
|
225
539
|
if (files.length > 0) return {
|
|
226
540
|
ref: branch,
|
|
@@ -230,9 +544,7 @@ async function findGitTag(owner, repo, version, packageName) {
|
|
|
230
544
|
return null;
|
|
231
545
|
}
|
|
232
546
|
async function findLatestReleaseTag(owner, repo, packageName) {
|
|
233
|
-
const
|
|
234
|
-
if (!res?.ok) return null;
|
|
235
|
-
const data = await res.json().catch(() => null);
|
|
547
|
+
const data = await $fetch(`https://ungh.cc/repos/${owner}/${repo}/releases`).catch(() => null);
|
|
236
548
|
const prefix = `${packageName}@`;
|
|
237
549
|
return data?.releases?.find((r) => r.tag.startsWith(prefix))?.tag ?? null;
|
|
238
550
|
}
|
|
@@ -337,7 +649,7 @@ function discoverDocFiles(allFiles) {
|
|
|
337
649
|
async function listDocsAtRef(owner, repo, ref, pathPrefix = "docs/") {
|
|
338
650
|
return filterDocFiles(await listFilesAtRef(owner, repo, ref), pathPrefix);
|
|
339
651
|
}
|
|
340
|
-
async function fetchGitDocs(owner, repo, version, packageName) {
|
|
652
|
+
async function fetchGitDocs(owner, repo, version, packageName, repoUrl) {
|
|
341
653
|
const override = packageName ? getDocOverride(packageName) : void 0;
|
|
342
654
|
if (override) {
|
|
343
655
|
const ref = override.ref || "main";
|
|
@@ -349,15 +661,17 @@ async function fetchGitDocs(owner, repo, version, packageName) {
|
|
|
349
661
|
files
|
|
350
662
|
};
|
|
351
663
|
}
|
|
352
|
-
const tag = await findGitTag(owner, repo, version, packageName);
|
|
664
|
+
const tag = await findGitTag(owner, repo, version, packageName, repoUrl ? extractBranchHint(repoUrl) : void 0);
|
|
353
665
|
if (!tag) return null;
|
|
354
666
|
let docs = filterDocFiles(tag.files, "docs/");
|
|
355
667
|
let docsPrefix;
|
|
668
|
+
let allFiles;
|
|
356
669
|
if (docs.length === 0) {
|
|
357
670
|
const discovered = discoverDocFiles(tag.files);
|
|
358
671
|
if (discovered) {
|
|
359
672
|
docs = discovered.files;
|
|
360
673
|
docsPrefix = discovered.prefix || void 0;
|
|
674
|
+
allFiles = tag.files;
|
|
361
675
|
}
|
|
362
676
|
}
|
|
363
677
|
if (docs.length === 0) return null;
|
|
@@ -365,51 +679,127 @@ async function fetchGitDocs(owner, repo, version, packageName) {
|
|
|
365
679
|
baseUrl: `https://raw.githubusercontent.com/${owner}/${repo}/${tag.ref}`,
|
|
366
680
|
ref: tag.ref,
|
|
367
681
|
files: docs,
|
|
368
|
-
docsPrefix
|
|
682
|
+
docsPrefix,
|
|
683
|
+
allFiles
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
function normalizePath(p) {
|
|
687
|
+
return p.replace(/^\//, "").replace(/\.(?:md|mdx)$/, "");
|
|
688
|
+
}
|
|
689
|
+
function validateGitDocsWithLlms(llmsLinks, repoFiles) {
|
|
690
|
+
if (llmsLinks.length === 0) return {
|
|
691
|
+
isValid: true,
|
|
692
|
+
matchRatio: 1
|
|
693
|
+
};
|
|
694
|
+
const sample = llmsLinks.slice(0, 10);
|
|
695
|
+
const normalizedLinks = sample.map((link) => {
|
|
696
|
+
let path = link.url;
|
|
697
|
+
if (path.startsWith("http")) try {
|
|
698
|
+
path = new URL(path).pathname;
|
|
699
|
+
} catch {}
|
|
700
|
+
return normalizePath(path);
|
|
701
|
+
});
|
|
702
|
+
const repoNormalized = new Set(repoFiles.map(normalizePath));
|
|
703
|
+
let matches = 0;
|
|
704
|
+
for (const linkPath of normalizedLinks) for (const repoPath of repoNormalized) if (repoPath === linkPath || repoPath.endsWith(`/${linkPath}`)) {
|
|
705
|
+
matches++;
|
|
706
|
+
break;
|
|
707
|
+
}
|
|
708
|
+
const matchRatio = matches / sample.length;
|
|
709
|
+
return {
|
|
710
|
+
isValid: matchRatio >= .3,
|
|
711
|
+
matchRatio
|
|
369
712
|
};
|
|
370
713
|
}
|
|
714
|
+
async function verifyNpmRepo(owner, repo, packageName) {
|
|
715
|
+
const base = `https://raw.githubusercontent.com/${owner}/${repo}/HEAD`;
|
|
716
|
+
const paths = [
|
|
717
|
+
"package.json",
|
|
718
|
+
`packages/${packageName.replace(/^@.*\//, "")}/package.json`,
|
|
719
|
+
`packages/${packageName.replace(/^@/, "").replace("/", "-")}/package.json`
|
|
720
|
+
];
|
|
721
|
+
for (const path of paths) {
|
|
722
|
+
const text = await fetchText(`${base}/${path}`);
|
|
723
|
+
if (!text) continue;
|
|
724
|
+
try {
|
|
725
|
+
if (JSON.parse(text).name === packageName) return true;
|
|
726
|
+
} catch {}
|
|
727
|
+
}
|
|
728
|
+
return false;
|
|
729
|
+
}
|
|
371
730
|
async function searchGitHubRepo(packageName) {
|
|
731
|
+
const shortName = packageName.replace(/^@.*\//, "");
|
|
732
|
+
for (const candidate of [packageName.replace(/^@/, "").replace("/", "/"), shortName]) {
|
|
733
|
+
if (!candidate.includes("/")) {
|
|
734
|
+
if ((await $fetch.raw(`https://ungh.cc/repos/${shortName}/${shortName}`).catch(() => null))?.ok) return `https://github.com/${shortName}/${shortName}`;
|
|
735
|
+
continue;
|
|
736
|
+
}
|
|
737
|
+
if ((await $fetch.raw(`https://ungh.cc/repos/${candidate}`).catch(() => null))?.ok) return `https://github.com/${candidate}`;
|
|
738
|
+
}
|
|
739
|
+
const searchTerm = packageName.replace(/^@/, "");
|
|
372
740
|
if (isGhAvailable()) try {
|
|
373
|
-
const json =
|
|
741
|
+
const { stdout: json } = spawnSync("gh", [
|
|
742
|
+
"search",
|
|
743
|
+
"repos",
|
|
744
|
+
searchTerm,
|
|
745
|
+
"--json",
|
|
746
|
+
"fullName",
|
|
747
|
+
"--limit",
|
|
748
|
+
"5"
|
|
749
|
+
], {
|
|
374
750
|
encoding: "utf-8",
|
|
375
751
|
timeout: 15e3
|
|
376
752
|
});
|
|
753
|
+
if (!json) throw new Error("no output");
|
|
377
754
|
const repos = JSON.parse(json);
|
|
378
|
-
const match = repos.find((r) => r.fullName.toLowerCase().endsWith(`/${packageName.toLowerCase()}`) || r.fullName.toLowerCase().endsWith(`/${
|
|
755
|
+
const match = repos.find((r) => r.fullName.toLowerCase().endsWith(`/${packageName.toLowerCase()}`) || r.fullName.toLowerCase().endsWith(`/${shortName.toLowerCase()}`));
|
|
379
756
|
if (match) return `https://github.com/${match.fullName}`;
|
|
380
|
-
|
|
757
|
+
for (const candidate of repos) {
|
|
758
|
+
const gh = parseGitHubUrl(`https://github.com/${candidate.fullName}`);
|
|
759
|
+
if (gh && await verifyNpmRepo(gh.owner, gh.repo, packageName)) return `https://github.com/${candidate.fullName}`;
|
|
760
|
+
}
|
|
381
761
|
} catch {}
|
|
382
|
-
const
|
|
383
|
-
const res = await fetch(`https://api.github.com/search/repositories?q=${query}&per_page=5`, { headers: { "User-Agent": "skilld/1.0" } }).catch(() => null);
|
|
384
|
-
if (!res?.ok) return null;
|
|
385
|
-
const data = await res.json().catch(() => null);
|
|
762
|
+
const data = await $fetch(`https://api.github.com/search/repositories?q=${encodeURIComponent(`${searchTerm} in:name`)}&per_page=5`).catch(() => null);
|
|
386
763
|
if (!data?.items?.length) return null;
|
|
387
|
-
const match = data.items.find((r) => r.full_name.toLowerCase().endsWith(`/${packageName.toLowerCase()}`) || r.full_name.toLowerCase().endsWith(`/${
|
|
388
|
-
|
|
764
|
+
const match = data.items.find((r) => r.full_name.toLowerCase().endsWith(`/${packageName.toLowerCase()}`) || r.full_name.toLowerCase().endsWith(`/${shortName.toLowerCase()}`));
|
|
765
|
+
if (match) return `https://github.com/${match.full_name}`;
|
|
766
|
+
for (const candidate of data.items) {
|
|
767
|
+
const gh = parseGitHubUrl(`https://github.com/${candidate.full_name}`);
|
|
768
|
+
if (gh && await verifyNpmRepo(gh.owner, gh.repo, packageName)) return `https://github.com/${candidate.full_name}`;
|
|
769
|
+
}
|
|
770
|
+
return null;
|
|
389
771
|
}
|
|
390
772
|
async function fetchGitHubRepoMeta(owner, repo, packageName) {
|
|
391
773
|
const override = packageName ? getDocOverride(packageName) : void 0;
|
|
392
774
|
if (override?.homepage) return { homepage: override.homepage };
|
|
393
775
|
if (isGhAvailable()) try {
|
|
394
|
-
const json =
|
|
776
|
+
const { stdout: json } = spawnSync("gh", [
|
|
777
|
+
"api",
|
|
778
|
+
`repos/${owner}/${repo}`,
|
|
779
|
+
"-q",
|
|
780
|
+
"{homepage}"
|
|
781
|
+
], {
|
|
395
782
|
encoding: "utf-8",
|
|
396
783
|
timeout: 1e4
|
|
397
784
|
});
|
|
785
|
+
if (!json) throw new Error("no output");
|
|
398
786
|
const data = JSON.parse(json);
|
|
399
787
|
return data?.homepage ? { homepage: data.homepage } : null;
|
|
400
788
|
} catch {}
|
|
401
|
-
const
|
|
402
|
-
if (!res?.ok) return null;
|
|
403
|
-
const data = await res.json().catch(() => null);
|
|
789
|
+
const data = await $fetch(`https://api.github.com/repos/${owner}/${repo}`).catch(() => null);
|
|
404
790
|
return data?.homepage ? { homepage: data.homepage } : null;
|
|
405
791
|
}
|
|
406
792
|
async function fetchReadme(owner, repo, subdir) {
|
|
407
793
|
const unghUrl = subdir ? `https://ungh.cc/repos/${owner}/${repo}/files/main/${subdir}/README.md` : `https://ungh.cc/repos/${owner}/${repo}/readme`;
|
|
408
|
-
if ((await fetch(unghUrl
|
|
794
|
+
if ((await $fetch.raw(unghUrl).catch(() => null))?.ok) return `ungh://${owner}/${repo}${subdir ? `/${subdir}` : ""}`;
|
|
409
795
|
const basePath = subdir ? `${subdir}/` : "";
|
|
410
|
-
for (const branch of ["main", "master"]) for (const filename of [
|
|
796
|
+
for (const branch of ["main", "master"]) for (const filename of [
|
|
797
|
+
"README.md",
|
|
798
|
+
"Readme.md",
|
|
799
|
+
"readme.md"
|
|
800
|
+
]) {
|
|
411
801
|
const readmeUrl = `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/${basePath}${filename}`;
|
|
412
|
-
if (await
|
|
802
|
+
if ((await $fetch.raw(readmeUrl).catch(() => null))?.ok) return readmeUrl;
|
|
413
803
|
}
|
|
414
804
|
return null;
|
|
415
805
|
}
|
|
@@ -426,10 +816,8 @@ async function fetchReadmeContent(url) {
|
|
|
426
816
|
const owner = parts[0];
|
|
427
817
|
const repo = parts[1];
|
|
428
818
|
const subdir = parts.slice(2).join("/");
|
|
429
|
-
const
|
|
430
|
-
|
|
431
|
-
if (!res?.ok) return null;
|
|
432
|
-
const text = await res.text();
|
|
819
|
+
const text = await $fetch(subdir ? `https://ungh.cc/repos/${owner}/${repo}/files/main/${subdir}/README.md` : `https://ungh.cc/repos/${owner}/${repo}/readme`, { responseType: "text" }).catch(() => null);
|
|
820
|
+
if (!text) return null;
|
|
433
821
|
try {
|
|
434
822
|
const json = JSON.parse(text);
|
|
435
823
|
return json.markdown || json.file?.contents || null;
|
|
@@ -440,7 +828,7 @@ async function fetchReadmeContent(url) {
|
|
|
440
828
|
return fetchText(url);
|
|
441
829
|
}
|
|
442
830
|
async function fetchLlmsUrl(docsUrl) {
|
|
443
|
-
const llmsUrl = `${docsUrl.
|
|
831
|
+
const llmsUrl = `${new URL(docsUrl).origin}/llms.txt`;
|
|
444
832
|
if (await verifyUrl(llmsUrl)) return llmsUrl;
|
|
445
833
|
return null;
|
|
446
834
|
}
|
|
@@ -468,19 +856,35 @@ function parseMarkdownLinks(content) {
|
|
|
468
856
|
}
|
|
469
857
|
return links;
|
|
470
858
|
}
|
|
859
|
+
function isSafeUrl(url) {
|
|
860
|
+
try {
|
|
861
|
+
const parsed = new URL(url);
|
|
862
|
+
if (parsed.protocol !== "https:") return false;
|
|
863
|
+
const host = parsed.hostname;
|
|
864
|
+
if (host === "localhost" || host === "127.0.0.1" || host === "::1") return false;
|
|
865
|
+
if (host === "169.254.169.254") return false;
|
|
866
|
+
if (/^(?:10\.|172\.(?:1[6-9]|2\d|3[01])\.|192\.168\.)/.test(host)) return false;
|
|
867
|
+
if (host.startsWith("[")) return false;
|
|
868
|
+
return true;
|
|
869
|
+
} catch {
|
|
870
|
+
return false;
|
|
871
|
+
}
|
|
872
|
+
}
|
|
471
873
|
async function downloadLlmsDocs(llmsContent, baseUrl, onProgress) {
|
|
472
|
-
const
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
874
|
+
const limit = pLimit(5);
|
|
875
|
+
let completed = 0;
|
|
876
|
+
return (await Promise.all(llmsContent.links.map((link) => limit(async () => {
|
|
877
|
+
const url = link.url.startsWith("http") ? link.url : `${baseUrl.replace(/\/$/, "")}${link.url.startsWith("/") ? "" : "/"}${link.url}`;
|
|
878
|
+
if (!isSafeUrl(url)) return null;
|
|
879
|
+
onProgress?.(link.url, completed++, llmsContent.links.length);
|
|
880
|
+
const content = await fetchText(url);
|
|
881
|
+
if (content && content.length > 100) return {
|
|
478
882
|
url: link.url,
|
|
479
883
|
title: link.title,
|
|
480
884
|
content
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
|
|
885
|
+
};
|
|
886
|
+
return null;
|
|
887
|
+
})))).filter((d) => d !== null);
|
|
484
888
|
}
|
|
485
889
|
function normalizeLlmsLinks(content, baseUrl) {
|
|
486
890
|
let normalized = content;
|
|
@@ -506,16 +910,23 @@ function extractSections(content, patterns) {
|
|
|
506
910
|
if (sections.length === 0) return null;
|
|
507
911
|
return sections.join("\n\n---\n\n");
|
|
508
912
|
}
|
|
913
|
+
async function searchNpmPackages(query, size = 5) {
|
|
914
|
+
const data = await $fetch(`https://registry.npmjs.org/-/v1/search?text=${encodeURIComponent(query)}&size=${size}`).catch(() => null);
|
|
915
|
+
if (!data?.objects?.length) return [];
|
|
916
|
+
return data.objects.map((o) => ({
|
|
917
|
+
name: o.package.name,
|
|
918
|
+
description: o.package.description,
|
|
919
|
+
version: o.package.version
|
|
920
|
+
}));
|
|
921
|
+
}
|
|
509
922
|
async function fetchNpmPackage(packageName) {
|
|
510
|
-
|
|
511
|
-
if (
|
|
512
|
-
|
|
513
|
-
return res.json();
|
|
923
|
+
const data = await $fetch(`https://unpkg.com/${packageName}/package.json`).catch(() => null);
|
|
924
|
+
if (data) return data;
|
|
925
|
+
return $fetch(`https://registry.npmjs.org/${packageName}/latest`).catch(() => null);
|
|
514
926
|
}
|
|
515
927
|
async function fetchNpmRegistryMeta(packageName, version) {
|
|
516
|
-
const
|
|
517
|
-
if (!
|
|
518
|
-
const data = await res.json();
|
|
928
|
+
const data = await $fetch(`https://registry.npmjs.org/${packageName}`).catch(() => null);
|
|
929
|
+
if (!data) return {};
|
|
519
930
|
const distTags = data["dist-tags"] ? Object.fromEntries(Object.entries(data["dist-tags"]).map(([tag, ver]) => [tag, {
|
|
520
931
|
version: ver,
|
|
521
932
|
releasedAt: data.time?.[ver]
|
|
@@ -560,24 +971,34 @@ async function resolvePackageDocsWithAttempts(packageName, options = {}) {
|
|
|
560
971
|
dependencies: pkg.dependencies,
|
|
561
972
|
distTags: registryMeta.distTags
|
|
562
973
|
};
|
|
974
|
+
let gitDocsAllFiles;
|
|
563
975
|
let subdir;
|
|
976
|
+
let rawRepoUrl;
|
|
564
977
|
if (typeof pkg.repository === "object" && pkg.repository?.url) {
|
|
565
|
-
|
|
978
|
+
rawRepoUrl = pkg.repository.url;
|
|
979
|
+
const normalized = normalizeRepoUrl(rawRepoUrl);
|
|
980
|
+
if (!normalized.includes("://") && normalized.includes("/") && !normalized.includes(":")) result.repoUrl = `https://github.com/${normalized}`;
|
|
981
|
+
else result.repoUrl = normalized;
|
|
566
982
|
subdir = pkg.repository.directory;
|
|
567
|
-
} else if (typeof pkg.repository === "string") {
|
|
983
|
+
} else if (typeof pkg.repository === "string") if (pkg.repository.includes("://")) {
|
|
984
|
+
const gh = parseGitHubUrl(pkg.repository);
|
|
985
|
+
if (gh) result.repoUrl = `https://github.com/${gh.owner}/${gh.repo}`;
|
|
986
|
+
} else {
|
|
568
987
|
const repo = pkg.repository.replace(/^github:/, "");
|
|
569
988
|
if (repo.includes("/") && !repo.includes(":")) result.repoUrl = `https://github.com/${repo}`;
|
|
570
989
|
}
|
|
990
|
+
if (pkg.homepage && !isGitHubRepoUrl(pkg.homepage) && !isUselessDocsUrl(pkg.homepage)) result.docsUrl = pkg.homepage;
|
|
571
991
|
if (result.repoUrl?.includes("github.com")) {
|
|
572
992
|
const gh = parseGitHubUrl(result.repoUrl);
|
|
573
993
|
if (gh) {
|
|
574
994
|
const targetVersion = options.version || pkg.version;
|
|
575
995
|
if (targetVersion) {
|
|
576
996
|
onProgress?.("github-docs");
|
|
577
|
-
const gitDocs = await fetchGitDocs(gh.owner, gh.repo, targetVersion, pkg.name);
|
|
997
|
+
const gitDocs = await fetchGitDocs(gh.owner, gh.repo, targetVersion, pkg.name, rawRepoUrl);
|
|
578
998
|
if (gitDocs) {
|
|
579
999
|
result.gitDocsUrl = gitDocs.baseUrl;
|
|
580
1000
|
result.gitRef = gitDocs.ref;
|
|
1001
|
+
gitDocsAllFiles = gitDocs.allFiles;
|
|
581
1002
|
attempts.push({
|
|
582
1003
|
source: "github-docs",
|
|
583
1004
|
url: gitDocs.baseUrl,
|
|
@@ -594,7 +1015,7 @@ async function resolvePackageDocsWithAttempts(packageName, options = {}) {
|
|
|
594
1015
|
if (!result.docsUrl) {
|
|
595
1016
|
onProgress?.("github-meta");
|
|
596
1017
|
const repoMeta = await fetchGitHubRepoMeta(gh.owner, gh.repo, pkg.name);
|
|
597
|
-
if (repoMeta?.homepage) {
|
|
1018
|
+
if (repoMeta?.homepage && !isUselessDocsUrl(repoMeta.homepage)) {
|
|
598
1019
|
result.docsUrl = repoMeta.homepage;
|
|
599
1020
|
attempts.push({
|
|
600
1021
|
source: "github-meta",
|
|
@@ -645,6 +1066,7 @@ async function resolvePackageDocsWithAttempts(packageName, options = {}) {
|
|
|
645
1066
|
if (gitDocs) {
|
|
646
1067
|
result.gitDocsUrl = gitDocs.baseUrl;
|
|
647
1068
|
result.gitRef = gitDocs.ref;
|
|
1069
|
+
gitDocsAllFiles = gitDocs.allFiles;
|
|
648
1070
|
attempts.push({
|
|
649
1071
|
source: "github-docs",
|
|
650
1072
|
url: gitDocs.baseUrl,
|
|
@@ -656,7 +1078,7 @@ async function resolvePackageDocsWithAttempts(packageName, options = {}) {
|
|
|
656
1078
|
if (!result.docsUrl) {
|
|
657
1079
|
onProgress?.("github-meta");
|
|
658
1080
|
const repoMeta = await fetchGitHubRepoMeta(gh.owner, gh.repo, pkg.name);
|
|
659
|
-
if (repoMeta?.homepage) result.docsUrl = repoMeta.homepage;
|
|
1081
|
+
if (repoMeta?.homepage && !isUselessDocsUrl(repoMeta.homepage)) result.docsUrl = repoMeta.homepage;
|
|
660
1082
|
}
|
|
661
1083
|
onProgress?.("readme");
|
|
662
1084
|
const readmeUrl = await fetchReadme(gh.owner, gh.repo);
|
|
@@ -668,7 +1090,6 @@ async function resolvePackageDocsWithAttempts(packageName, options = {}) {
|
|
|
668
1090
|
message: "No repository URL in package.json and GitHub search found no match"
|
|
669
1091
|
});
|
|
670
1092
|
}
|
|
671
|
-
if (pkg.homepage && !isGitHubRepoUrl(pkg.homepage)) result.docsUrl = pkg.homepage;
|
|
672
1093
|
if (result.docsUrl) {
|
|
673
1094
|
onProgress?.("llms.txt");
|
|
674
1095
|
const llmsUrl = await fetchLlmsUrl(result.docsUrl);
|
|
@@ -681,28 +1102,42 @@ async function resolvePackageDocsWithAttempts(packageName, options = {}) {
|
|
|
681
1102
|
});
|
|
682
1103
|
} else attempts.push({
|
|
683
1104
|
source: "llms.txt",
|
|
684
|
-
url: `${result.docsUrl}/llms.txt`,
|
|
1105
|
+
url: `${new URL(result.docsUrl).origin}/llms.txt`,
|
|
685
1106
|
status: "not-found",
|
|
686
1107
|
message: "No llms.txt at docs URL"
|
|
687
1108
|
});
|
|
688
1109
|
}
|
|
689
|
-
if (
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
if (existsSync(readmePath)) {
|
|
695
|
-
result.readmeUrl = pathToFileURL(readmePath).href;
|
|
1110
|
+
if (result.gitDocsUrl && result.llmsUrl && gitDocsAllFiles) {
|
|
1111
|
+
const llmsContent = await fetchLlmsTxt(result.llmsUrl);
|
|
1112
|
+
if (llmsContent && llmsContent.links.length > 0) {
|
|
1113
|
+
const validation = validateGitDocsWithLlms(llmsContent.links, gitDocsAllFiles);
|
|
1114
|
+
if (!validation.isValid) {
|
|
696
1115
|
attempts.push({
|
|
697
|
-
source: "
|
|
698
|
-
url:
|
|
699
|
-
status: "
|
|
700
|
-
message:
|
|
1116
|
+
source: "github-docs",
|
|
1117
|
+
url: result.gitDocsUrl,
|
|
1118
|
+
status: "not-found",
|
|
1119
|
+
message: `Heuristic git docs don't match llms.txt links (${Math.round(validation.matchRatio * 100)}% match), preferring llms.txt`
|
|
701
1120
|
});
|
|
702
|
-
|
|
1121
|
+
result.gitDocsUrl = void 0;
|
|
1122
|
+
result.gitRef = void 0;
|
|
703
1123
|
}
|
|
704
1124
|
}
|
|
705
1125
|
}
|
|
1126
|
+
if (!result.docsUrl && !result.llmsUrl && !result.readmeUrl && !result.gitDocsUrl && options.cwd) {
|
|
1127
|
+
onProgress?.("local");
|
|
1128
|
+
const pkgDir = join(options.cwd, "node_modules", packageName);
|
|
1129
|
+
const readmeFile = existsSync(pkgDir) && readdirSync(pkgDir).find((f) => /^readme\.md$/i.test(f));
|
|
1130
|
+
if (readmeFile) {
|
|
1131
|
+
const readmePath = join(pkgDir, readmeFile);
|
|
1132
|
+
result.readmeUrl = pathToFileURL(readmePath).href;
|
|
1133
|
+
attempts.push({
|
|
1134
|
+
source: "readme",
|
|
1135
|
+
url: readmePath,
|
|
1136
|
+
status: "success",
|
|
1137
|
+
message: "Found local readme in node_modules"
|
|
1138
|
+
});
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
706
1141
|
if (!result.docsUrl && !result.llmsUrl && !result.readmeUrl && !result.gitDocsUrl) return {
|
|
707
1142
|
package: null,
|
|
708
1143
|
attempts
|
|
@@ -724,27 +1159,46 @@ function parseVersionSpecifier(name, version, cwd) {
|
|
|
724
1159
|
}
|
|
725
1160
|
return null;
|
|
726
1161
|
}
|
|
727
|
-
if (version.startsWith("workspace:")) return {
|
|
728
|
-
name,
|
|
729
|
-
version: version.slice(10).replace(/^[\^~*]/, "") || "*"
|
|
730
|
-
};
|
|
731
1162
|
if (version.startsWith("npm:")) {
|
|
732
1163
|
const specifier = version.slice(4);
|
|
733
1164
|
const atIndex = specifier.startsWith("@") ? specifier.indexOf("@", 1) : specifier.indexOf("@");
|
|
734
|
-
|
|
735
|
-
name: specifier.slice(0, atIndex),
|
|
736
|
-
version: specifier.slice(atIndex + 1)
|
|
737
|
-
};
|
|
1165
|
+
const realName = atIndex > 0 ? specifier.slice(0, atIndex) : specifier;
|
|
738
1166
|
return {
|
|
739
|
-
name:
|
|
740
|
-
version: "*"
|
|
1167
|
+
name: realName,
|
|
1168
|
+
version: resolveInstalledVersion(realName, cwd) || "*"
|
|
741
1169
|
};
|
|
742
1170
|
}
|
|
743
1171
|
if (version.startsWith("file:") || version.startsWith("git:") || version.startsWith("git+")) return null;
|
|
744
|
-
|
|
1172
|
+
const installed = resolveInstalledVersion(name, cwd);
|
|
1173
|
+
if (installed) return {
|
|
1174
|
+
name,
|
|
1175
|
+
version: installed
|
|
1176
|
+
};
|
|
1177
|
+
if (/^[\^~>=<\d]/.test(version)) return {
|
|
745
1178
|
name,
|
|
746
1179
|
version: version.replace(/^[\^~>=<]/, "")
|
|
747
1180
|
};
|
|
1181
|
+
if (version.startsWith("catalog:") || version.startsWith("workspace:")) return {
|
|
1182
|
+
name,
|
|
1183
|
+
version: "*"
|
|
1184
|
+
};
|
|
1185
|
+
return null;
|
|
1186
|
+
}
|
|
1187
|
+
function resolveInstalledVersion(name, cwd) {
|
|
1188
|
+
try {
|
|
1189
|
+
const resolved = resolvePathSync(`${name}/package.json`, { url: cwd });
|
|
1190
|
+
return JSON.parse(readFileSync(resolved, "utf-8")).version || null;
|
|
1191
|
+
} catch {
|
|
1192
|
+
try {
|
|
1193
|
+
let dir = dirname(resolvePathSync(name, { url: cwd }));
|
|
1194
|
+
while (dir && basename(dir) !== "node_modules") {
|
|
1195
|
+
const pkgPath = join(dir, "package.json");
|
|
1196
|
+
if (existsSync(pkgPath)) return JSON.parse(readFileSync(pkgPath, "utf-8")).version || null;
|
|
1197
|
+
dir = dirname(dir);
|
|
1198
|
+
}
|
|
1199
|
+
} catch {}
|
|
1200
|
+
return null;
|
|
1201
|
+
}
|
|
748
1202
|
}
|
|
749
1203
|
async function readLocalDependencies(cwd) {
|
|
750
1204
|
const pkgPath = join(cwd, "package.json");
|
|
@@ -805,8 +1259,8 @@ async function resolveLocalPackageDocs(localPath) {
|
|
|
805
1259
|
}
|
|
806
1260
|
}
|
|
807
1261
|
if (!result.readmeUrl && !result.gitDocsUrl) {
|
|
808
|
-
const
|
|
809
|
-
if (
|
|
1262
|
+
const readmeFile = readdirSync(localPath).find((f) => /^readme\.md$/i.test(f));
|
|
1263
|
+
if (readmeFile) result.readmeUrl = pathToFileURL(join(localPath, readmeFile)).href;
|
|
810
1264
|
}
|
|
811
1265
|
if (!result.readmeUrl && !result.gitDocsUrl) return null;
|
|
812
1266
|
return result;
|
|
@@ -815,9 +1269,9 @@ async function fetchPkgDist(name, version) {
|
|
|
815
1269
|
const cacheDir = getCacheDir(name, version);
|
|
816
1270
|
const pkgDir = join(cacheDir, "pkg");
|
|
817
1271
|
if (existsSync(join(pkgDir, "package.json"))) return pkgDir;
|
|
818
|
-
const
|
|
819
|
-
if (!
|
|
820
|
-
const tarballUrl =
|
|
1272
|
+
const data = await $fetch(`https://registry.npmjs.org/${name}/${version}`).catch(() => null);
|
|
1273
|
+
if (!data) return null;
|
|
1274
|
+
const tarballUrl = data.dist?.tarball;
|
|
821
1275
|
if (!tarballUrl) return null;
|
|
822
1276
|
const tarballRes = await fetch(tarballUrl, { headers: { "User-Agent": "skilld/1.0" } }).catch(() => null);
|
|
823
1277
|
if (!tarballRes?.ok || !tarballRes.body) return null;
|
|
@@ -845,9 +1299,14 @@ async function fetchPkgDist(name, version) {
|
|
|
845
1299
|
}
|
|
846
1300
|
pump();
|
|
847
1301
|
});
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
1302
|
+
const { status } = spawnSync("tar", [
|
|
1303
|
+
"xzf",
|
|
1304
|
+
tmpTarball,
|
|
1305
|
+
"--strip-components=1",
|
|
1306
|
+
"-C",
|
|
1307
|
+
pkgDir
|
|
1308
|
+
], { stdio: "ignore" });
|
|
1309
|
+
if (status !== 0) {
|
|
851
1310
|
rmSync(pkgDir, {
|
|
852
1311
|
recursive: true,
|
|
853
1312
|
force: true
|
|
@@ -859,9 +1318,7 @@ async function fetchPkgDist(name, version) {
|
|
|
859
1318
|
return pkgDir;
|
|
860
1319
|
}
|
|
861
1320
|
async function fetchLatestVersion(packageName) {
|
|
862
|
-
|
|
863
|
-
if (!res?.ok) return null;
|
|
864
|
-
return (await res.json()).version || null;
|
|
1321
|
+
return (await $fetch(`https://unpkg.com/${packageName}/package.json`).catch(() => null))?.version || null;
|
|
865
1322
|
}
|
|
866
1323
|
function getInstalledSkillVersion(skillDir) {
|
|
867
1324
|
const skillPath = join(skillDir, "SKILL.md");
|
|
@@ -901,7 +1358,12 @@ function compareSemver(a, b) {
|
|
|
901
1358
|
}
|
|
902
1359
|
function fetchReleasesViaGh(owner, repo) {
|
|
903
1360
|
try {
|
|
904
|
-
const json =
|
|
1361
|
+
const { stdout: json } = spawnSync("gh", [
|
|
1362
|
+
"api",
|
|
1363
|
+
`repos/${owner}/${repo}/releases?per_page=100`,
|
|
1364
|
+
"--jq",
|
|
1365
|
+
"[.[] | {id: .id, tag: .tag_name, name: .name, prerelease: .prerelease, createdAt: .created_at, publishedAt: .published_at, markdown: .body}]"
|
|
1366
|
+
], {
|
|
905
1367
|
encoding: "utf-8",
|
|
906
1368
|
timeout: 15e3,
|
|
907
1369
|
stdio: [
|
|
@@ -910,18 +1372,14 @@ function fetchReleasesViaGh(owner, repo) {
|
|
|
910
1372
|
"ignore"
|
|
911
1373
|
]
|
|
912
1374
|
});
|
|
1375
|
+
if (!json) return [];
|
|
913
1376
|
return JSON.parse(json);
|
|
914
1377
|
} catch {
|
|
915
1378
|
return [];
|
|
916
1379
|
}
|
|
917
1380
|
}
|
|
918
1381
|
async function fetchReleasesViaUngh(owner, repo) {
|
|
919
|
-
|
|
920
|
-
headers: { "User-Agent": "skilld/1.0" },
|
|
921
|
-
signal: AbortSignal.timeout(15e3)
|
|
922
|
-
}).catch(() => null);
|
|
923
|
-
if (!res?.ok) return [];
|
|
924
|
-
return (await res.json().catch(() => null))?.releases ?? [];
|
|
1382
|
+
return (await $fetch(`https://ungh.cc/repos/${owner}/${repo}/releases`, { signal: AbortSignal.timeout(15e3) }).catch(() => null))?.releases ?? [];
|
|
925
1383
|
}
|
|
926
1384
|
async function fetchAllReleases(owner, repo) {
|
|
927
1385
|
if (isGhAvailable()) {
|
|
@@ -947,9 +1405,48 @@ function selectReleases(releases, packageName) {
|
|
|
947
1405
|
return compareSemver(parseSemver(verB), parseSemver(verA));
|
|
948
1406
|
}).slice(0, 20);
|
|
949
1407
|
}
|
|
950
|
-
function formatRelease(release) {
|
|
1408
|
+
function formatRelease(release, packageName) {
|
|
951
1409
|
const date = (release.publishedAt || release.createdAt).split("T")[0];
|
|
952
|
-
|
|
1410
|
+
const version = extractVersion(release.tag, packageName) || release.tag;
|
|
1411
|
+
const fm = [
|
|
1412
|
+
"---",
|
|
1413
|
+
`tag: ${release.tag}`,
|
|
1414
|
+
`version: ${version}`,
|
|
1415
|
+
`published: ${date}`
|
|
1416
|
+
];
|
|
1417
|
+
if (release.name && release.name !== release.tag) fm.push(`name: "${release.name.replace(/"/g, "\\\"")}"`);
|
|
1418
|
+
fm.push("---");
|
|
1419
|
+
return `${fm.join("\n")}\n\n# ${release.name || release.tag}\n\n${release.markdown}`;
|
|
1420
|
+
}
|
|
1421
|
+
function generateReleaseIndex(releases, packageName) {
|
|
1422
|
+
const lines = [
|
|
1423
|
+
[
|
|
1424
|
+
"---",
|
|
1425
|
+
`total: ${releases.length}`,
|
|
1426
|
+
`latest: ${releases[0]?.tag || "unknown"}`,
|
|
1427
|
+
"---"
|
|
1428
|
+
].join("\n"),
|
|
1429
|
+
"",
|
|
1430
|
+
"# Releases Index",
|
|
1431
|
+
""
|
|
1432
|
+
];
|
|
1433
|
+
for (const r of releases) {
|
|
1434
|
+
const date = (r.publishedAt || r.createdAt).split("T")[0];
|
|
1435
|
+
const filename = r.tag.includes("@") || r.tag.startsWith("v") ? r.tag : `v${r.tag}`;
|
|
1436
|
+
const sv = parseSemver(extractVersion(r.tag, packageName) || r.tag);
|
|
1437
|
+
const label = sv?.patch === 0 && sv.minor === 0 ? " **[MAJOR]**" : sv?.patch === 0 ? " **[MINOR]**" : "";
|
|
1438
|
+
lines.push(`- [${r.tag}](./${filename}.md): ${r.name || r.tag} (${date})${label}`);
|
|
1439
|
+
}
|
|
1440
|
+
lines.push("");
|
|
1441
|
+
return lines.join("\n");
|
|
1442
|
+
}
|
|
1443
|
+
function isChangelogRedirectPattern(releases) {
|
|
1444
|
+
const sample = releases.slice(0, 3);
|
|
1445
|
+
if (sample.length === 0) return false;
|
|
1446
|
+
return sample.every((r) => {
|
|
1447
|
+
const body = (r.markdown || "").trim();
|
|
1448
|
+
return body.length < 500 && /changelog\.md/i.test(body);
|
|
1449
|
+
});
|
|
953
1450
|
}
|
|
954
1451
|
async function fetchChangelog(owner, repo, ref) {
|
|
955
1452
|
for (const filename of [
|
|
@@ -957,23 +1454,36 @@ async function fetchChangelog(owner, repo, ref) {
|
|
|
957
1454
|
"changelog.md",
|
|
958
1455
|
"CHANGES.md"
|
|
959
1456
|
]) {
|
|
960
|
-
const
|
|
961
|
-
|
|
962
|
-
headers: { "User-Agent": "skilld/1.0" },
|
|
1457
|
+
const content = await $fetch(`https://raw.githubusercontent.com/${owner}/${repo}/${ref}/${filename}`, {
|
|
1458
|
+
responseType: "text",
|
|
963
1459
|
signal: AbortSignal.timeout(1e4)
|
|
964
1460
|
}).catch(() => null);
|
|
965
|
-
if (
|
|
1461
|
+
if (content) return content;
|
|
966
1462
|
}
|
|
967
1463
|
return null;
|
|
968
1464
|
}
|
|
969
1465
|
async function fetchReleaseNotes(owner, repo, installedVersion, gitRef, packageName) {
|
|
970
1466
|
const selected = selectReleases(await fetchAllReleases(owner, repo), packageName);
|
|
971
|
-
if (selected.length > 0)
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
1467
|
+
if (selected.length > 0) {
|
|
1468
|
+
if (isChangelogRedirectPattern(selected)) {
|
|
1469
|
+
const changelog = await fetchChangelog(owner, repo, gitRef || selected[0].tag);
|
|
1470
|
+
if (changelog) return [{
|
|
1471
|
+
path: "CHANGELOG.md",
|
|
1472
|
+
content: changelog
|
|
1473
|
+
}];
|
|
1474
|
+
}
|
|
1475
|
+
const docs = selected.map((r) => {
|
|
1476
|
+
return {
|
|
1477
|
+
path: `releases/${r.tag.includes("@") || r.tag.startsWith("v") ? r.tag : `v${r.tag}`}.md`,
|
|
1478
|
+
content: formatRelease(r, packageName)
|
|
1479
|
+
};
|
|
1480
|
+
});
|
|
1481
|
+
docs.push({
|
|
1482
|
+
path: "releases/_INDEX.md",
|
|
1483
|
+
content: generateReleaseIndex(selected, packageName)
|
|
1484
|
+
});
|
|
1485
|
+
return docs;
|
|
1486
|
+
}
|
|
977
1487
|
const changelog = await fetchChangelog(owner, repo, gitRef || "main");
|
|
978
1488
|
if (!changelog) return [];
|
|
979
1489
|
return [{
|
|
@@ -981,6 +1491,6 @@ async function fetchReleaseNotes(owner, repo, installedVersion, gitRef, packageN
|
|
|
981
1491
|
content: changelog
|
|
982
1492
|
}];
|
|
983
1493
|
}
|
|
984
|
-
export {
|
|
1494
|
+
export { extractBranchHint as A, formatDiscussionAsMarkdown as B, fetchGitDocs as C, isShallowGitDocs as D, fetchReadmeContent as E, verifyUrl as F, isGhAvailable as G, fetchGitHubIssues as H, DOC_OVERRIDES as I, getDocOverride as L, isGitHubRepoUrl as M, normalizeRepoUrl as N, validateGitDocsWithLlms as O, parseGitHubUrl as P, resolveEntryFiles as R, MIN_GIT_DOCS as S, fetchReadme as T, formatIssueAsMarkdown as U, generateDiscussionIndex as V, generateIssueIndex as W, extractSections as _, fetchNpmRegistryMeta as a, normalizeLlmsLinks as b, parseVersionSpecifier as c, resolveInstalledVersion as d, resolveLocalPackageDocs as f, downloadLlmsDocs as g, searchNpmPackages as h, fetchNpmPackage as i, fetchText as j, $fetch as k, readLocalDependencies as l, resolvePackageDocsWithAttempts as m, generateReleaseIndex as n, fetchPkgDist as o, resolvePackageDocs as p, fetchLatestVersion as r, getInstalledSkillVersion as s, fetchReleaseNotes as t, readLocalPackageInfo as u, fetchLlmsTxt as v, fetchGitHubRepoMeta as w, parseMarkdownLinks as x, fetchLlmsUrl as y, fetchGitHubDiscussions as z };
|
|
985
1495
|
|
|
986
1496
|
//# sourceMappingURL=releases.mjs.map
|