skilld 1.7.3 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +737 -619
- 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 +11 -8
- package/dist/_chunks/assemble.mjs.map +1 -1
- package/dist/_chunks/author.mjs +77 -131
- package/dist/_chunks/author.mjs.map +1 -1
- package/dist/_chunks/cache.mjs +320 -54
- 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 +7 -4
- package/dist/_chunks/detect.mjs +54 -44
- 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 +2 -2
- 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 +86 -371
- 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 +31 -7
- 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 +73 -0
- package/dist/_chunks/monorepo.mjs.map +1 -0
- package/dist/_chunks/package-json.mjs.map +1 -1
- package/dist/_chunks/paths.mjs +47 -0
- package/dist/_chunks/paths.mjs.map +1 -0
- package/dist/_chunks/pipeline.mjs +985 -0
- package/dist/_chunks/pipeline.mjs.map +1 -0
- package/dist/_chunks/pool2.mjs +2 -2
- 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 +9 -8
- package/dist/_chunks/prepare2.mjs.map +1 -1
- package/dist/_chunks/prompts.mjs +784 -26
- 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 +9 -8
- 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 -4
- package/dist/_chunks/search.mjs.map +1 -1
- package/dist/_chunks/{sources.mjs → semver.mjs} +1128 -838
- package/dist/_chunks/semver.mjs.map +1 -0
- package/dist/_chunks/skill-installer.mjs +2 -0
- package/dist/_chunks/skill-installer2.mjs +154 -0
- package/dist/_chunks/skill-installer2.mjs.map +1 -0
- package/dist/_chunks/skills.mjs +12 -12
- 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 +761 -1349
- package/dist/_chunks/sync.mjs.map +1 -1
- package/dist/_chunks/sync2.mjs +2 -3
- package/dist/_chunks/telemetry.mjs +26 -0
- package/dist/_chunks/telemetry.mjs.map +1 -0
- package/dist/_chunks/uninstall.mjs +15 -13
- 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 +4 -4
- package/dist/_chunks/validate.mjs +1 -1
- package/dist/_chunks/version.mjs +16 -27
- 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 +77 -59
- package/dist/cli.mjs.map +1 -1
- package/dist/prepare.mjs +5 -4
- 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 +20 -29
- 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/config.mjs +0 -122
- package/dist/_chunks/config.mjs.map +0 -1
- package/dist/_chunks/index.d.mts +0 -151
- package/dist/_chunks/index.d.mts.map +0 -1
- package/dist/_chunks/index2.d.mts +0 -44
- package/dist/_chunks/index2.d.mts.map +0 -1
- package/dist/_chunks/index3.d.mts +0 -589
- package/dist/_chunks/index3.d.mts.map +0 -1
- package/dist/_chunks/prefix.mjs +0 -108
- package/dist/_chunks/prefix.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/shared.mjs +0 -503
- package/dist/_chunks/shared.mjs.map +0 -1
- package/dist/_chunks/skill.mjs +0 -329
- package/dist/_chunks/skill.mjs.map +0 -1
- package/dist/_chunks/sources.mjs.map +0 -1
- package/dist/_chunks/sync-registry.mjs +0 -59
- package/dist/_chunks/sync-registry.mjs.map +0 -1
- package/dist/_chunks/sync-shared.mjs +0 -2
- package/dist/_chunks/sync-shared2.mjs +0 -1020
- package/dist/_chunks/sync-shared2.mjs.map +0 -1
- package/dist/_chunks/types.d.mts +0 -88
- package/dist/_chunks/types.d.mts.map +0 -1
- package/dist/_chunks/wizard.mjs.map +0 -1
- package/dist/agent/index.d.mts +0 -346
- package/dist/agent/index.d.mts.map +0 -1
- package/dist/agent/index.mjs +0 -5
- package/dist/cache/index.d.mts +0 -2
- package/dist/cache/index.mjs +0 -4
- package/dist/index.d.mts +0 -5
- package/dist/index.mjs +0 -5
- package/dist/retriv/index.d.mts +0 -3
- package/dist/retriv/index.mjs +0 -2
- package/dist/sources/index.d.mts +0 -2
- package/dist/sources/index.mjs +0 -3
- package/dist/types.d.mts +0 -4
- package/dist/types.mjs +0 -1
|
@@ -0,0 +1,985 @@
|
|
|
1
|
+
import { a as getModelName, i as getModelLabel, o as createToolProgress, r as getAvailableModels, t as optimizeDocs } from "./agent.mjs";
|
|
2
|
+
import { C as SECTION_OUTPUT_FILES, E as getBlogPreset, S as SECTION_MERGE_ORDER, T as maxLines, _ as buildAllSectionPrompts, k as getPrereleaseChangelogRef, t as writeGeneratedSkillMd, w as maxItems, x as wrapSection } from "./prompts.mjs";
|
|
3
|
+
import { d as getRepoCacheDir, m as skillInternalDir, u as getPackageDbPath } from "./paths.mjs";
|
|
4
|
+
import { n as sanitizeMarkdown } from "./sanitize.mjs";
|
|
5
|
+
import { a as resolvePkgDir, s as getCacheDir } from "./prepare.mjs";
|
|
6
|
+
import { a as writeToRepoCache, d as readConfig, i as listReferenceFiles, m as updateConfig, o as defaultFeatures, t as createReferenceCache } from "./cache.mjs";
|
|
7
|
+
import { t as isInteractive } from "./env.mjs";
|
|
8
|
+
import { r as pickModel, t as NO_MODELS_MESSAGE } from "./model-picker.mjs";
|
|
9
|
+
import { A as fetchGitHubIssues, B as fetchBlogReleases, C as fetchCrawledDocs, D as downloadLlmsDocs, F as filterFrameworkDocs, H as generateReleaseIndex, I as isShallowGitDocs, M as generateIssueIndex, N as isGhAvailable, O as fetchLlmsTxt, P as fetchGitDocs, R as parseGitHubUrl, S as generateDiscussionIndex, T as fetchReadmeContent, U as isPrerelease, V as fetchReleaseNotes, W as fetchGitHubRaw, b as fetchGitHubDiscussions, d as fetchNpmPackage, j as formatIssueAsMarkdown, k as normalizeLlmsLinks, t as semverDiff, v as resolveEntryFiles, w as toCrawlPattern, x as formatDiscussionAsMarkdown, y as generateDocsIndex } from "./semver.mjs";
|
|
10
|
+
import { i as parseFrontmatter } from "./markdown.mjs";
|
|
11
|
+
import { c as readLock, t as buildPackageDirMap } from "./lockfile.mjs";
|
|
12
|
+
import { a as listIndexIds, r as createIndex, t as SearchDepsUnavailableError } from "./retriv2.mjs";
|
|
13
|
+
import { existsSync, mkdirSync, readdirSync, writeFileSync } from "node:fs";
|
|
14
|
+
import { styleText } from "node:util";
|
|
15
|
+
import * as p from "@clack/prompts";
|
|
16
|
+
import { join, relative } from "pathe";
|
|
17
|
+
const RATE_LIMIT_RE = /\b429\b|rate.?limit|exhausted.*capacity|quota.*reset/i;
|
|
18
|
+
async function runSkillEnhancement(ctx, run, onProgress) {
|
|
19
|
+
const { packageName, cachePackageName, version, skillDir, dirName, resolved, relatedSkills, references, packages, features, overheadLines } = ctx;
|
|
20
|
+
const { docsType, hasShippedDocs: shippedDocs, pkgFiles, hasIssues, hasDiscussions, hasReleases, hasChangelog } = references;
|
|
21
|
+
const { model, force, debug, sections, customPrompt, eject } = run;
|
|
22
|
+
const cacheKey = cachePackageName || packageName;
|
|
23
|
+
const docFiles = listReferenceFiles(skillDir);
|
|
24
|
+
const result = await optimizeDocs({
|
|
25
|
+
packageName: cacheKey,
|
|
26
|
+
skillDir,
|
|
27
|
+
model,
|
|
28
|
+
version,
|
|
29
|
+
hasGithub: hasIssues || hasDiscussions,
|
|
30
|
+
hasReleases,
|
|
31
|
+
hasChangelog,
|
|
32
|
+
docFiles,
|
|
33
|
+
docsType,
|
|
34
|
+
hasShippedDocs: shippedDocs,
|
|
35
|
+
noCache: force,
|
|
36
|
+
debug,
|
|
37
|
+
sections,
|
|
38
|
+
customPrompt,
|
|
39
|
+
features,
|
|
40
|
+
pkgFiles,
|
|
41
|
+
overheadLines,
|
|
42
|
+
onProgress
|
|
43
|
+
});
|
|
44
|
+
if (result.wasOptimized) writeGeneratedSkillMd(skillDir, {
|
|
45
|
+
name: packageName,
|
|
46
|
+
version,
|
|
47
|
+
releasedAt: resolved.releasedAt,
|
|
48
|
+
distTags: resolved.distTags,
|
|
49
|
+
body: result.optimized,
|
|
50
|
+
relatedSkills,
|
|
51
|
+
hasIssues,
|
|
52
|
+
hasDiscussions,
|
|
53
|
+
hasReleases,
|
|
54
|
+
hasChangelog,
|
|
55
|
+
docsType,
|
|
56
|
+
hasShippedDocs: shippedDocs,
|
|
57
|
+
pkgFiles,
|
|
58
|
+
generatedBy: getModelLabel(model),
|
|
59
|
+
dirName,
|
|
60
|
+
packages,
|
|
61
|
+
repoUrl: resolved.repoUrl,
|
|
62
|
+
features,
|
|
63
|
+
eject
|
|
64
|
+
});
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
67
|
+
async function enhanceSkillWithLLM(ctx, run) {
|
|
68
|
+
const llmLog = p.taskLog({
|
|
69
|
+
title: `Agent exploring ${ctx.packageName}`,
|
|
70
|
+
limit: 3
|
|
71
|
+
});
|
|
72
|
+
const { wasOptimized, usage, cost, warnings, error, debugLogsDir } = await runSkillEnhancement(ctx, run, createToolProgress(llmLog));
|
|
73
|
+
if (wasOptimized) {
|
|
74
|
+
const costParts = [];
|
|
75
|
+
if (usage) {
|
|
76
|
+
const totalK = Math.round(usage.totalTokens / 1e3);
|
|
77
|
+
costParts.push(`${totalK}k tokens`);
|
|
78
|
+
}
|
|
79
|
+
if (cost) costParts.push(`$${cost.toFixed(2)}`);
|
|
80
|
+
const costSuffix = costParts.length > 0 ? ` (${costParts.join(", ")})` : "";
|
|
81
|
+
llmLog.success(`Generated best practices${costSuffix}`);
|
|
82
|
+
if (debugLogsDir) p.log.info(`Debug logs: ${relative(process.cwd(), debugLogsDir)}`);
|
|
83
|
+
if (error) p.log.warn(styleText("yellow", `Partial failure: ${error}`));
|
|
84
|
+
if (warnings?.length) for (const w of warnings) p.log.warn(styleText("yellow", w));
|
|
85
|
+
} else if (error && RATE_LIMIT_RE.test(error)) llmLog.error(`Rate limited by LLM provider. Try again shortly or use a different model via \`skilld config\``);
|
|
86
|
+
else llmLog.error(`Enhancement failed${error ? `: ${error}` : ""}`);
|
|
87
|
+
}
|
|
88
|
+
function writeBaseSkill(ctx, opts = {}) {
|
|
89
|
+
const { packageName, version, skillDir, dirName, references, resolved, relatedSkills, packages, features } = ctx;
|
|
90
|
+
return writeGeneratedSkillMd(skillDir, {
|
|
91
|
+
name: packageName,
|
|
92
|
+
version,
|
|
93
|
+
releasedAt: resolved.releasedAt,
|
|
94
|
+
description: resolved.description,
|
|
95
|
+
distTags: resolved.distTags,
|
|
96
|
+
body: opts.body,
|
|
97
|
+
relatedSkills,
|
|
98
|
+
hasIssues: references.hasIssues,
|
|
99
|
+
hasDiscussions: references.hasDiscussions,
|
|
100
|
+
hasReleases: references.hasReleases,
|
|
101
|
+
hasChangelog: references.hasChangelog,
|
|
102
|
+
docsType: references.docsType,
|
|
103
|
+
hasShippedDocs: references.hasShippedDocs,
|
|
104
|
+
pkgFiles: references.pkgFiles,
|
|
105
|
+
generatedBy: opts.generatedBy,
|
|
106
|
+
dirName,
|
|
107
|
+
packages,
|
|
108
|
+
repoUrl: resolved.repoUrl,
|
|
109
|
+
features,
|
|
110
|
+
eject: opts.eject
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
function applyCachedSections(ctx, sections, opts = {}) {
|
|
114
|
+
const cache = createReferenceCache(ctx.cachePackageName || ctx.packageName, ctx.version);
|
|
115
|
+
if (!sections.every((s) => cache.readSection(SECTION_OUTPUT_FILES[s]) !== null)) return false;
|
|
116
|
+
const parts = [];
|
|
117
|
+
for (const s of SECTION_MERGE_ORDER) {
|
|
118
|
+
if (!sections.includes(s)) continue;
|
|
119
|
+
const content = cache.readSection(SECTION_OUTPUT_FILES[s]);
|
|
120
|
+
if (content) parts.push(wrapSection(s, content));
|
|
121
|
+
}
|
|
122
|
+
writeBaseSkill(ctx, {
|
|
123
|
+
body: parts.join("\n\n"),
|
|
124
|
+
generatedBy: "cached",
|
|
125
|
+
eject: opts.eject
|
|
126
|
+
});
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
function writePromptFiles(ctx, run) {
|
|
130
|
+
const { packageName, version, skillDir, references, features, overheadLines } = ctx;
|
|
131
|
+
const { sections, customPrompt } = run;
|
|
132
|
+
const docFiles = listReferenceFiles(skillDir);
|
|
133
|
+
const prompts = buildAllSectionPrompts({
|
|
134
|
+
packageName,
|
|
135
|
+
skillDir,
|
|
136
|
+
version,
|
|
137
|
+
hasIssues: references.hasIssues,
|
|
138
|
+
hasDiscussions: references.hasDiscussions,
|
|
139
|
+
hasReleases: references.hasReleases,
|
|
140
|
+
hasChangelog: references.hasChangelog,
|
|
141
|
+
docFiles,
|
|
142
|
+
docsType: references.docsType,
|
|
143
|
+
hasShippedDocs: references.hasShippedDocs,
|
|
144
|
+
pkgFiles: references.pkgFiles,
|
|
145
|
+
customPrompt,
|
|
146
|
+
features,
|
|
147
|
+
overheadLines,
|
|
148
|
+
sections
|
|
149
|
+
});
|
|
150
|
+
const skilldDir = skillInternalDir(skillDir);
|
|
151
|
+
mkdirSync(skilldDir, { recursive: true });
|
|
152
|
+
for (const [section, prompt] of prompts) writeFileSync(join(skilldDir, `PROMPT_${section}.md`), prompt);
|
|
153
|
+
const written = [...prompts.keys()];
|
|
154
|
+
if (written.length > 0) {
|
|
155
|
+
const relDir = relative(process.cwd(), skillDir);
|
|
156
|
+
const promptFiles = written.map((s) => `PROMPT_${s}.md`).join(", ");
|
|
157
|
+
const outputFileList = written.map((s) => SECTION_OUTPUT_FILES[s]).join(", ");
|
|
158
|
+
p.log.info(`Prompt files written to ${relDir}/.skilld/\n${styleText(["dim", "italic"], ` Read each prompt file (${promptFiles}) in ${relDir}/.skilld/, read the\n referenced files, then write your output to the matching file (${outputFileList}).\n When done, run: skilld assemble`)}`);
|
|
159
|
+
}
|
|
160
|
+
return written;
|
|
161
|
+
}
|
|
162
|
+
const DEFAULT_SECTIONS = ["best-practices", "api-changes"];
|
|
163
|
+
async function resolveAutoModel(override, yes) {
|
|
164
|
+
if (override) return override;
|
|
165
|
+
const config = readConfig();
|
|
166
|
+
if (!yes || config.skipLlm) return void 0;
|
|
167
|
+
if (config.model) return config.model;
|
|
168
|
+
const available = await getAvailableModels();
|
|
169
|
+
return available.find((m) => m.recommended)?.id ?? available[0]?.id;
|
|
170
|
+
}
|
|
171
|
+
async function selectSkillSections(message = "Enhance SKILL.md") {
|
|
172
|
+
p.log.info("Budgets adapt to package release density.");
|
|
173
|
+
const selected = await p.multiselect({
|
|
174
|
+
message,
|
|
175
|
+
options: [
|
|
176
|
+
{
|
|
177
|
+
label: "API changes",
|
|
178
|
+
value: "api-changes",
|
|
179
|
+
hint: "new/deprecated APIs from version history"
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
label: "Best practices",
|
|
183
|
+
value: "best-practices",
|
|
184
|
+
hint: "gotchas, pitfalls, patterns"
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
label: "Custom section",
|
|
188
|
+
value: "custom",
|
|
189
|
+
hint: "add your own section"
|
|
190
|
+
}
|
|
191
|
+
],
|
|
192
|
+
initialValues: DEFAULT_SECTIONS,
|
|
193
|
+
required: false
|
|
194
|
+
});
|
|
195
|
+
if (p.isCancel(selected)) return {
|
|
196
|
+
sections: [],
|
|
197
|
+
cancelled: true
|
|
198
|
+
};
|
|
199
|
+
const sections = selected;
|
|
200
|
+
if (sections.length === 0) return {
|
|
201
|
+
sections: [],
|
|
202
|
+
cancelled: false
|
|
203
|
+
};
|
|
204
|
+
if (sections.length > 1) {
|
|
205
|
+
const n = sections.length;
|
|
206
|
+
const budgetLines = [];
|
|
207
|
+
for (const s of sections) switch (s) {
|
|
208
|
+
case "api-changes":
|
|
209
|
+
budgetLines.push(` API changes ${maxItems(6, 12, n)}–${maxItems(6, Math.round(12 * 1.6), n)} items (adapts to release churn)`);
|
|
210
|
+
break;
|
|
211
|
+
case "best-practices":
|
|
212
|
+
budgetLines.push(` Best practices ${maxItems(4, 10, n)}–${maxItems(4, Math.round(10 * 1.3), n)} items`);
|
|
213
|
+
break;
|
|
214
|
+
case "custom":
|
|
215
|
+
budgetLines.push(` Custom ≤${maxLines(50, 80, n)} lines`);
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
p.log.info(`Budget (${n} sections):\n${budgetLines.join("\n")}`);
|
|
219
|
+
}
|
|
220
|
+
let customPrompt;
|
|
221
|
+
if (sections.includes("custom")) {
|
|
222
|
+
const heading = await p.text({
|
|
223
|
+
message: "Section heading",
|
|
224
|
+
placeholder: "e.g. \"Migration from v2\" or \"SSR Patterns\""
|
|
225
|
+
});
|
|
226
|
+
if (p.isCancel(heading)) return {
|
|
227
|
+
sections: [],
|
|
228
|
+
cancelled: true
|
|
229
|
+
};
|
|
230
|
+
const body = await p.text({
|
|
231
|
+
message: "Instructions for this section",
|
|
232
|
+
placeholder: "e.g. \"Document breaking changes and migration steps from v2 to v3\""
|
|
233
|
+
});
|
|
234
|
+
if (p.isCancel(body)) return {
|
|
235
|
+
sections: [],
|
|
236
|
+
cancelled: true
|
|
237
|
+
};
|
|
238
|
+
customPrompt = {
|
|
239
|
+
heading,
|
|
240
|
+
body
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
return {
|
|
244
|
+
sections,
|
|
245
|
+
customPrompt,
|
|
246
|
+
cancelled: false
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
async function selectLlmConfig(presetModel, message, updateCtx) {
|
|
250
|
+
if (presetModel) {
|
|
251
|
+
if ((await getAvailableModels()).some((m) => m.id === presetModel)) return {
|
|
252
|
+
model: presetModel,
|
|
253
|
+
sections: DEFAULT_SECTIONS
|
|
254
|
+
};
|
|
255
|
+
if (!isInteractive()) return null;
|
|
256
|
+
}
|
|
257
|
+
if (!isInteractive()) return null;
|
|
258
|
+
const config = readConfig();
|
|
259
|
+
const available = await getAvailableModels();
|
|
260
|
+
if (available.length === 0) {
|
|
261
|
+
p.log.warn(NO_MODELS_MESSAGE);
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
let defaultModel;
|
|
265
|
+
if (config.model && available.some((m) => m.id === config.model)) defaultModel = config.model;
|
|
266
|
+
else {
|
|
267
|
+
if (config.model) p.log.warn(`Configured model ${styleText("cyan", config.model)} is unavailable — using auto-selected fallback`);
|
|
268
|
+
defaultModel = available.find((m) => m.recommended)?.id ?? available[0].id;
|
|
269
|
+
}
|
|
270
|
+
const defaultModelName = getModelName(defaultModel);
|
|
271
|
+
const providerHint = available.find((m) => m.id === defaultModel)?.providerName ?? "";
|
|
272
|
+
const sourceHint = config.model === defaultModel ? "configured" : "recommended";
|
|
273
|
+
const defaultHint = providerHint ? `${providerHint} · ${sourceHint}` : sourceHint;
|
|
274
|
+
let enhanceMessage = message ? `${message}?` : "Enhance SKILL.md?";
|
|
275
|
+
let defaultToSkip = false;
|
|
276
|
+
if (updateCtx) {
|
|
277
|
+
const diff = updateCtx.bumpType ?? (updateCtx.oldVersion && updateCtx.newVersion ? semverDiff(updateCtx.oldVersion, updateCtx.newVersion) : null);
|
|
278
|
+
const isSmallBump = diff === "patch" || diff === "prerelease" || diff === "prepatch" || diff === "preminor" || diff === "premajor";
|
|
279
|
+
const ageParts = [];
|
|
280
|
+
if (diff) ageParts.push(diff);
|
|
281
|
+
if (updateCtx.syncedAt) {
|
|
282
|
+
const syncedAtMs = new Date(updateCtx.syncedAt).getTime();
|
|
283
|
+
if (Number.isFinite(syncedAtMs)) {
|
|
284
|
+
const days = Math.floor((Date.now() - syncedAtMs) / 864e5);
|
|
285
|
+
ageParts.push(days === 0 ? "today" : days === 1 ? "1d ago" : `${days}d ago`);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
if (updateCtx.wasEnhanced) ageParts.push("LLM-enhanced");
|
|
289
|
+
const hint = [updateCtx.oldVersion && updateCtx.newVersion ? `${updateCtx.oldVersion} → ${updateCtx.newVersion}` : null, ...ageParts].filter(Boolean).join(" · ");
|
|
290
|
+
if (hint) enhanceMessage = `Enhance SKILL.md? ${styleText("gray", `(${hint})`)}`;
|
|
291
|
+
if (updateCtx.wasEnhanced && isSmallBump) defaultToSkip = true;
|
|
292
|
+
}
|
|
293
|
+
const choice = await p.select({
|
|
294
|
+
message: enhanceMessage,
|
|
295
|
+
options: [
|
|
296
|
+
{
|
|
297
|
+
label: defaultModelName,
|
|
298
|
+
value: "default",
|
|
299
|
+
hint: defaultHint
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
label: "Different model",
|
|
303
|
+
value: "pick",
|
|
304
|
+
hint: "choose another enhancement model"
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
label: "Prompt only",
|
|
308
|
+
value: "prompt",
|
|
309
|
+
hint: "write prompts for manual use"
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
label: "Skip",
|
|
313
|
+
value: "skip",
|
|
314
|
+
hint: "base skill with docs, issues, and types"
|
|
315
|
+
}
|
|
316
|
+
],
|
|
317
|
+
...defaultToSkip ? { initialValue: "skip" } : {}
|
|
318
|
+
});
|
|
319
|
+
if (p.isCancel(choice)) return null;
|
|
320
|
+
if (choice === "skip") return null;
|
|
321
|
+
if (choice === "prompt") {
|
|
322
|
+
const { sections, customPrompt, cancelled } = await selectSkillSections(message ? `${message} (prompt only)` : "Select sections for prompt generation");
|
|
323
|
+
if (cancelled || sections.length === 0) return null;
|
|
324
|
+
return {
|
|
325
|
+
model: defaultModel,
|
|
326
|
+
sections,
|
|
327
|
+
customPrompt,
|
|
328
|
+
promptOnly: true
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
let model;
|
|
332
|
+
if (choice === "pick") {
|
|
333
|
+
const picked = await pickModel(available);
|
|
334
|
+
if (!picked) return null;
|
|
335
|
+
updateConfig({ model: picked });
|
|
336
|
+
model = picked;
|
|
337
|
+
} else model = defaultModel;
|
|
338
|
+
if (!model) return null;
|
|
339
|
+
const modelName = getModelName(model);
|
|
340
|
+
const { sections, customPrompt, cancelled } = await selectSkillSections(message ? `${message} (${modelName})` : `Enhance SKILL.md with ${modelName}`);
|
|
341
|
+
if (cancelled || sections.length === 0) return null;
|
|
342
|
+
return {
|
|
343
|
+
model,
|
|
344
|
+
sections,
|
|
345
|
+
customPrompt
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
function parentDocId(id) {
|
|
349
|
+
const idx = id.indexOf("#chunk-");
|
|
350
|
+
return idx === -1 ? id : id.slice(0, idx);
|
|
351
|
+
}
|
|
352
|
+
function capDocs(allDocs, max, onProgress) {
|
|
353
|
+
if (allDocs.length <= max) return;
|
|
354
|
+
const TYPE_PRIORITY = {
|
|
355
|
+
doc: 0,
|
|
356
|
+
issue: 1,
|
|
357
|
+
discussion: 2,
|
|
358
|
+
release: 3,
|
|
359
|
+
source: 4,
|
|
360
|
+
types: 5
|
|
361
|
+
};
|
|
362
|
+
allDocs.sort((a, b) => {
|
|
363
|
+
const ta = TYPE_PRIORITY[a.metadata?.type || "doc"] ?? 3;
|
|
364
|
+
const tb = TYPE_PRIORITY[b.metadata?.type || "doc"] ?? 3;
|
|
365
|
+
if (ta !== tb) return ta - tb;
|
|
366
|
+
return a.id.localeCompare(b.id);
|
|
367
|
+
});
|
|
368
|
+
onProgress(`Indexing capped at ${max}/${allDocs.length} docs (prioritized by type)`);
|
|
369
|
+
allDocs.length = max;
|
|
370
|
+
}
|
|
371
|
+
async function indexResources(opts) {
|
|
372
|
+
const { packageName, version, cwd, onProgress } = opts;
|
|
373
|
+
const features = opts.features ?? readConfig().features ?? defaultFeatures;
|
|
374
|
+
if (!features.search) return;
|
|
375
|
+
const dbPath = getPackageDbPath(packageName, version);
|
|
376
|
+
const dbExists = existsSync(dbPath);
|
|
377
|
+
const allDocs = [...opts.docsToIndex];
|
|
378
|
+
const pkgDir = resolvePkgDir(packageName, cwd, version);
|
|
379
|
+
if (features.search && pkgDir) {
|
|
380
|
+
onProgress("Scanning exports");
|
|
381
|
+
const entryFiles = await resolveEntryFiles(pkgDir);
|
|
382
|
+
for (const e of entryFiles) allDocs.push({
|
|
383
|
+
id: e.path,
|
|
384
|
+
content: e.content,
|
|
385
|
+
metadata: {
|
|
386
|
+
package: packageName,
|
|
387
|
+
source: `pkg/${e.path}`,
|
|
388
|
+
type: e.type
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
if (allDocs.length === 0) return;
|
|
393
|
+
capDocs(allDocs, 250, onProgress);
|
|
394
|
+
if (!dbExists) {
|
|
395
|
+
onProgress(`Building search index (${allDocs.length} docs)`);
|
|
396
|
+
try {
|
|
397
|
+
await createIndex(allDocs, {
|
|
398
|
+
dbPath,
|
|
399
|
+
onProgress: ({ phase, current, total }) => {
|
|
400
|
+
if (phase === "storing") {
|
|
401
|
+
const d = allDocs[current - 1];
|
|
402
|
+
onProgress(`Storing ${d?.metadata?.type === "source" || d?.metadata?.type === "types" ? "code" : d?.metadata?.type || "doc"} (${current}/${total})`);
|
|
403
|
+
} else if (phase === "embedding") onProgress(`Creating embeddings (${current}/${total})`);
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
} catch (err) {
|
|
407
|
+
if (err instanceof SearchDepsUnavailableError) onProgress("Search indexing skipped (native deps unavailable)");
|
|
408
|
+
else throw err;
|
|
409
|
+
}
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
let existingIds;
|
|
413
|
+
try {
|
|
414
|
+
existingIds = await listIndexIds({ dbPath });
|
|
415
|
+
} catch (err) {
|
|
416
|
+
if (err instanceof SearchDepsUnavailableError) {
|
|
417
|
+
onProgress("Search indexing skipped (native deps unavailable)");
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
throw err;
|
|
421
|
+
}
|
|
422
|
+
const existingParentIds = new Set(existingIds.map(parentDocId));
|
|
423
|
+
const incomingIds = new Set(allDocs.map((d) => d.id));
|
|
424
|
+
const newDocs = allDocs.filter((d) => !existingParentIds.has(d.id));
|
|
425
|
+
const removeIds = existingIds.filter((id) => !incomingIds.has(parentDocId(id)));
|
|
426
|
+
if (newDocs.length === 0 && removeIds.length === 0) {
|
|
427
|
+
onProgress("Search index up to date");
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
const parts = [];
|
|
431
|
+
if (newDocs.length > 0) parts.push(`+${newDocs.length} new`);
|
|
432
|
+
if (removeIds.length > 0) parts.push(`-${removeIds.length} stale`);
|
|
433
|
+
onProgress(`Updating search index (${parts.join(", ")})`);
|
|
434
|
+
try {
|
|
435
|
+
await createIndex(newDocs, {
|
|
436
|
+
dbPath,
|
|
437
|
+
removeIds,
|
|
438
|
+
onProgress: ({ phase, current, total }) => {
|
|
439
|
+
if (phase === "storing") {
|
|
440
|
+
const d = newDocs[current - 1];
|
|
441
|
+
onProgress(`Storing ${d?.metadata?.type === "source" || d?.metadata?.type === "types" ? "code" : d?.metadata?.type || "doc"} (${current}/${total})`);
|
|
442
|
+
} else if (phase === "embedding") onProgress(`Creating embeddings (${current}/${total})`);
|
|
443
|
+
}
|
|
444
|
+
});
|
|
445
|
+
} catch (err) {
|
|
446
|
+
if (err instanceof SearchDepsUnavailableError) onProgress("Search indexing skipped (native deps unavailable)");
|
|
447
|
+
else throw err;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
function defineStep(step) {
|
|
451
|
+
return step;
|
|
452
|
+
}
|
|
453
|
+
async function walkSteps(steps, ctx) {
|
|
454
|
+
for (const step of steps) {
|
|
455
|
+
if (step.canResolve && !step.canResolve(ctx)) continue;
|
|
456
|
+
await step.run(ctx);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
const crawlUrlStep = defineStep({
|
|
460
|
+
id: "crawl-url",
|
|
461
|
+
canResolve: (ctx) => !!ctx.resolved.crawlUrl && ctx.docs.length === 0,
|
|
462
|
+
async run(ctx) {
|
|
463
|
+
const crawlUrl = ctx.resolved.crawlUrl;
|
|
464
|
+
ctx.onProgress("Crawling website");
|
|
465
|
+
const crawled = await fetchCrawledDocs(crawlUrl, ctx.onProgress).catch((err) => {
|
|
466
|
+
ctx.warnings.push(`Crawl failed for ${crawlUrl}: ${err?.message || err}`);
|
|
467
|
+
return [];
|
|
468
|
+
});
|
|
469
|
+
if (crawled.length === 0) ctx.warnings.push(`Crawl returned 0 docs from ${crawlUrl}`);
|
|
470
|
+
let added = 0;
|
|
471
|
+
for (const doc of crawled) {
|
|
472
|
+
if (!ctx.isFrameworkDoc(doc.path)) continue;
|
|
473
|
+
ctx.docs.push(doc);
|
|
474
|
+
ctx.docsToIndex.push({
|
|
475
|
+
id: doc.path,
|
|
476
|
+
content: doc.content,
|
|
477
|
+
metadata: {
|
|
478
|
+
package: ctx.packageName,
|
|
479
|
+
source: doc.path,
|
|
480
|
+
type: "doc"
|
|
481
|
+
}
|
|
482
|
+
});
|
|
483
|
+
added++;
|
|
484
|
+
}
|
|
485
|
+
if (added > 0) {
|
|
486
|
+
ctx.docSource = crawlUrl;
|
|
487
|
+
ctx.docsType = "docs";
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
});
|
|
491
|
+
const docsCrawlStep = defineStep({
|
|
492
|
+
id: "docs-crawl",
|
|
493
|
+
canResolve: (ctx) => !!ctx.resolved.docsUrl && !ctx.docs.some((d) => d.path.startsWith("docs/")),
|
|
494
|
+
async run(ctx) {
|
|
495
|
+
const crawlPattern = ctx.resolved.crawlUrl || toCrawlPattern(ctx.resolved.docsUrl);
|
|
496
|
+
ctx.onProgress("Crawling docs site");
|
|
497
|
+
const maxPages = ctx.resolved.crawlUrl ? 200 : 400;
|
|
498
|
+
const crawled = await fetchCrawledDocs(crawlPattern, ctx.onProgress, maxPages).catch((err) => {
|
|
499
|
+
ctx.warnings.push(`Crawl failed for ${crawlPattern}: ${err?.message || err}`);
|
|
500
|
+
return [];
|
|
501
|
+
});
|
|
502
|
+
let added = 0;
|
|
503
|
+
for (const doc of crawled) {
|
|
504
|
+
if (!ctx.isFrameworkDoc(doc.path)) continue;
|
|
505
|
+
ctx.docs.push(doc);
|
|
506
|
+
ctx.docsToIndex.push({
|
|
507
|
+
id: doc.path,
|
|
508
|
+
content: doc.content,
|
|
509
|
+
metadata: {
|
|
510
|
+
package: ctx.packageName,
|
|
511
|
+
source: doc.path,
|
|
512
|
+
type: "doc"
|
|
513
|
+
}
|
|
514
|
+
});
|
|
515
|
+
added++;
|
|
516
|
+
}
|
|
517
|
+
if (added > 0) {
|
|
518
|
+
ctx.docSource = crawlPattern;
|
|
519
|
+
ctx.docsType = "docs";
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
});
|
|
523
|
+
const BATCH_SIZE = 20;
|
|
524
|
+
const defaultContentSteps = [
|
|
525
|
+
defineStep({
|
|
526
|
+
id: "git-docs",
|
|
527
|
+
canResolve: (ctx) => !!ctx.resolved.gitDocsUrl && !!ctx.resolved.repoUrl,
|
|
528
|
+
async run(ctx) {
|
|
529
|
+
const gh = parseGitHubUrl(ctx.resolved.repoUrl);
|
|
530
|
+
if (!gh) return;
|
|
531
|
+
ctx.onProgress("Fetching git docs");
|
|
532
|
+
const gitDocs = await fetchGitDocs(gh.owner, gh.repo, ctx.version, ctx.packageName);
|
|
533
|
+
if (!gitDocs || gitDocs.files.length === 0) return;
|
|
534
|
+
if (gitDocs.fallback) ctx.warnings.push(`Docs fetched from ${gitDocs.ref} branch (no tag found for v${ctx.version})`);
|
|
535
|
+
const results = [];
|
|
536
|
+
for (let i = 0; i < gitDocs.files.length; i += BATCH_SIZE) {
|
|
537
|
+
const batch = gitDocs.files.slice(i, i + BATCH_SIZE);
|
|
538
|
+
ctx.onProgress(`Downloading docs ${Math.min(i + BATCH_SIZE, gitDocs.files.length)}/${gitDocs.files.length} from ${gitDocs.ref}`);
|
|
539
|
+
const batchResults = await Promise.all(batch.map(async (file) => {
|
|
540
|
+
const content = await fetchGitHubRaw(`${gitDocs.baseUrl}/${file}`);
|
|
541
|
+
return content ? {
|
|
542
|
+
file,
|
|
543
|
+
content
|
|
544
|
+
} : null;
|
|
545
|
+
}));
|
|
546
|
+
results.push(...batchResults);
|
|
547
|
+
}
|
|
548
|
+
const docs = [];
|
|
549
|
+
const docsToIndex = [];
|
|
550
|
+
for (const r of results) {
|
|
551
|
+
if (!r) continue;
|
|
552
|
+
const stripped = gitDocs.docsPrefix ? r.file.replace(gitDocs.docsPrefix, "") : r.file;
|
|
553
|
+
const cachePath = stripped.startsWith("docs/") ? stripped : `docs/${stripped}`;
|
|
554
|
+
docs.push({
|
|
555
|
+
path: cachePath,
|
|
556
|
+
content: r.content
|
|
557
|
+
});
|
|
558
|
+
docsToIndex.push({
|
|
559
|
+
id: cachePath,
|
|
560
|
+
content: r.content,
|
|
561
|
+
metadata: {
|
|
562
|
+
package: ctx.packageName,
|
|
563
|
+
source: cachePath,
|
|
564
|
+
type: "doc"
|
|
565
|
+
}
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
if (isShallowGitDocs(docs.length) && ctx.resolved.llmsUrl) {
|
|
569
|
+
ctx.onProgress(`Shallow git-docs (${docs.length} files), trying llms.txt`);
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
ctx.docs.push(...docs);
|
|
573
|
+
ctx.docsToIndex.push(...docsToIndex);
|
|
574
|
+
ctx.docSource = `${ctx.resolved.repoUrl}/tree/${gitDocs.ref}/docs`;
|
|
575
|
+
ctx.docsType = "docs";
|
|
576
|
+
if (ctx.resolved.llmsUrl) {
|
|
577
|
+
ctx.onProgress("Caching supplementary llms.txt");
|
|
578
|
+
const llmsContent = await fetchLlmsTxt(ctx.resolved.llmsUrl);
|
|
579
|
+
if (llmsContent) {
|
|
580
|
+
const baseUrl = ctx.resolved.docsUrl || new URL(ctx.resolved.llmsUrl).origin;
|
|
581
|
+
ctx.docs.push({
|
|
582
|
+
path: "llms.txt",
|
|
583
|
+
content: normalizeLlmsLinks(llmsContent.raw, baseUrl)
|
|
584
|
+
});
|
|
585
|
+
if (llmsContent.links.length > 0) {
|
|
586
|
+
ctx.onProgress(`Downloading ${llmsContent.links.length} supplementary docs`);
|
|
587
|
+
const supplementary = await downloadLlmsDocs(llmsContent, baseUrl, (_url, done, total) => {
|
|
588
|
+
ctx.onProgress(`Downloading supplementary doc ${done + 1}/${total}`);
|
|
589
|
+
});
|
|
590
|
+
for (const doc of supplementary) {
|
|
591
|
+
if (!ctx.isFrameworkDoc(doc.url)) continue;
|
|
592
|
+
const localPath = doc.url.startsWith("/") ? doc.url.slice(1) : doc.url;
|
|
593
|
+
ctx.docs.push({
|
|
594
|
+
path: join("llms-docs", ...localPath.split("/")),
|
|
595
|
+
content: doc.content
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
}),
|
|
603
|
+
crawlUrlStep,
|
|
604
|
+
defineStep({
|
|
605
|
+
id: "llms.txt",
|
|
606
|
+
canResolve: (ctx) => !!ctx.resolved.llmsUrl && ctx.docs.length === 0,
|
|
607
|
+
async run(ctx) {
|
|
608
|
+
const llmsUrl = ctx.resolved.llmsUrl;
|
|
609
|
+
ctx.onProgress("Fetching llms.txt");
|
|
610
|
+
const llmsContent = await fetchLlmsTxt(llmsUrl);
|
|
611
|
+
if (!llmsContent) return;
|
|
612
|
+
ctx.docSource = llmsUrl;
|
|
613
|
+
ctx.docsType = "llms.txt";
|
|
614
|
+
const baseUrl = ctx.resolved.docsUrl || new URL(llmsUrl).origin;
|
|
615
|
+
ctx.docs.push({
|
|
616
|
+
path: "llms.txt",
|
|
617
|
+
content: normalizeLlmsLinks(llmsContent.raw, baseUrl)
|
|
618
|
+
});
|
|
619
|
+
if (llmsContent.links.length > 0) {
|
|
620
|
+
ctx.onProgress(`Downloading ${llmsContent.links.length} linked docs`);
|
|
621
|
+
const linked = await downloadLlmsDocs(llmsContent, baseUrl, (_url, done, total) => {
|
|
622
|
+
ctx.onProgress(`Downloading linked doc ${done + 1}/${total}`);
|
|
623
|
+
});
|
|
624
|
+
for (const doc of linked) {
|
|
625
|
+
if (!ctx.isFrameworkDoc(doc.url)) continue;
|
|
626
|
+
const cachePath = join("docs", ...(doc.url.startsWith("/") ? doc.url.slice(1) : doc.url).split("/"));
|
|
627
|
+
ctx.docs.push({
|
|
628
|
+
path: cachePath,
|
|
629
|
+
content: doc.content
|
|
630
|
+
});
|
|
631
|
+
ctx.docsToIndex.push({
|
|
632
|
+
id: doc.url,
|
|
633
|
+
content: doc.content,
|
|
634
|
+
metadata: {
|
|
635
|
+
package: ctx.packageName,
|
|
636
|
+
source: cachePath,
|
|
637
|
+
type: "doc"
|
|
638
|
+
}
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
if (linked.length > 0) ctx.docsType = "docs";
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
}),
|
|
645
|
+
docsCrawlStep,
|
|
646
|
+
defineStep({
|
|
647
|
+
id: "readme",
|
|
648
|
+
canResolve: (ctx) => !!ctx.resolved.readmeUrl && ctx.docs.length === 0,
|
|
649
|
+
async run(ctx) {
|
|
650
|
+
ctx.onProgress("Fetching README");
|
|
651
|
+
const content = await fetchReadmeContent(ctx.resolved.readmeUrl);
|
|
652
|
+
if (!content) return;
|
|
653
|
+
ctx.docs.push({
|
|
654
|
+
path: "docs/README.md",
|
|
655
|
+
content
|
|
656
|
+
});
|
|
657
|
+
ctx.docsToIndex.push({
|
|
658
|
+
id: "README.md",
|
|
659
|
+
content,
|
|
660
|
+
metadata: {
|
|
661
|
+
package: ctx.packageName,
|
|
662
|
+
source: "docs/README.md",
|
|
663
|
+
type: "doc"
|
|
664
|
+
}
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
})
|
|
668
|
+
];
|
|
669
|
+
async function resolveContentDocs(opts) {
|
|
670
|
+
const { packageName, resolved, version, onProgress } = opts;
|
|
671
|
+
const ctx = {
|
|
672
|
+
packageName,
|
|
673
|
+
resolved,
|
|
674
|
+
version,
|
|
675
|
+
onProgress,
|
|
676
|
+
isFrameworkDoc: (path) => filterFrameworkDocs([path], packageName).length > 0,
|
|
677
|
+
docs: [],
|
|
678
|
+
docsToIndex: [],
|
|
679
|
+
warnings: [],
|
|
680
|
+
docSource: resolved.readmeUrl || "readme",
|
|
681
|
+
docsType: "readme"
|
|
682
|
+
};
|
|
683
|
+
await walkSteps(defaultContentSteps, ctx);
|
|
684
|
+
return {
|
|
685
|
+
docs: ctx.docs,
|
|
686
|
+
docsToIndex: ctx.docsToIndex,
|
|
687
|
+
docSource: ctx.docSource,
|
|
688
|
+
docsType: ctx.docsType,
|
|
689
|
+
warnings: ctx.warnings
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
const discussionsStep = defineStep({
|
|
693
|
+
id: "discussions",
|
|
694
|
+
canResolve: (ctx) => ctx.features.discussions && !!ctx.repoInfo && isGhAvailable() && !existsSync(ctx.discussionsDir),
|
|
695
|
+
async run(ctx) {
|
|
696
|
+
const { owner, repo } = ctx.repoInfo;
|
|
697
|
+
ctx.onProgress("Fetching discussions via GitHub API");
|
|
698
|
+
const discussions = await fetchGitHubDiscussions(owner, repo, 20, ctx.resolved.releasedAt, ctx.from).catch(() => []);
|
|
699
|
+
if (discussions.length === 0) return;
|
|
700
|
+
ctx.onProgress(`Caching ${discussions.length} discussions`);
|
|
701
|
+
writeToRepoCache(owner, repo, [...discussions.map((d) => ({
|
|
702
|
+
path: `discussions/discussion-${d.number}.md`,
|
|
703
|
+
content: formatDiscussionAsMarkdown(d)
|
|
704
|
+
})), {
|
|
705
|
+
path: "discussions/_INDEX.md",
|
|
706
|
+
content: generateDiscussionIndex(discussions)
|
|
707
|
+
}]);
|
|
708
|
+
for (const d of discussions) ctx.docsToIndex.push({
|
|
709
|
+
id: `discussion-${d.number}`,
|
|
710
|
+
content: sanitizeMarkdown(`#${d.number}: ${d.title}\n\n${d.body || ""}`),
|
|
711
|
+
metadata: {
|
|
712
|
+
package: ctx.packageName,
|
|
713
|
+
source: `discussions/discussion-${d.number}.md`,
|
|
714
|
+
type: "discussion",
|
|
715
|
+
number: d.number
|
|
716
|
+
}
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
});
|
|
720
|
+
const issuesStep = defineStep({
|
|
721
|
+
id: "issues",
|
|
722
|
+
canResolve: (ctx) => ctx.features.issues && !!ctx.repoInfo && isGhAvailable() && !existsSync(ctx.issuesDir),
|
|
723
|
+
async run(ctx) {
|
|
724
|
+
const { owner, repo } = ctx.repoInfo;
|
|
725
|
+
ctx.onProgress("Fetching issues via GitHub API");
|
|
726
|
+
const issues = await fetchGitHubIssues(owner, repo, 30, ctx.resolved.releasedAt, ctx.from).catch(() => []);
|
|
727
|
+
if (issues.length === 0) return;
|
|
728
|
+
ctx.onProgress(`Caching ${issues.length} issues`);
|
|
729
|
+
writeToRepoCache(owner, repo, [...issues.map((issue) => ({
|
|
730
|
+
path: `issues/issue-${issue.number}.md`,
|
|
731
|
+
content: formatIssueAsMarkdown(issue)
|
|
732
|
+
})), {
|
|
733
|
+
path: "issues/_INDEX.md",
|
|
734
|
+
content: generateIssueIndex(issues)
|
|
735
|
+
}]);
|
|
736
|
+
for (const issue of issues) ctx.docsToIndex.push({
|
|
737
|
+
id: `issue-${issue.number}`,
|
|
738
|
+
content: sanitizeMarkdown(`#${issue.number}: ${issue.title}\n\n${issue.body || ""}`),
|
|
739
|
+
metadata: {
|
|
740
|
+
package: ctx.packageName,
|
|
741
|
+
source: `issues/issue-${issue.number}.md`,
|
|
742
|
+
type: "issue",
|
|
743
|
+
number: issue.number
|
|
744
|
+
}
|
|
745
|
+
});
|
|
746
|
+
}
|
|
747
|
+
});
|
|
748
|
+
const BLOG_VERSION_RE = /blog-(.+)\.md$/;
|
|
749
|
+
const defaultTimelineSteps = [
|
|
750
|
+
issuesStep,
|
|
751
|
+
discussionsStep,
|
|
752
|
+
defineStep({
|
|
753
|
+
id: "releases",
|
|
754
|
+
canResolve: (ctx) => ctx.features.releases && !!ctx.repoInfo && isGhAvailable() && !existsSync(ctx.releasesPath),
|
|
755
|
+
async run(ctx) {
|
|
756
|
+
const { owner, repo } = ctx.repoInfo;
|
|
757
|
+
const { packageName, version, resolved, from } = ctx;
|
|
758
|
+
ctx.onProgress("Fetching releases via GitHub API");
|
|
759
|
+
const changelogRef = isPrerelease(version) ? getPrereleaseChangelogRef(packageName) : void 0;
|
|
760
|
+
const releaseDocs = await fetchReleaseNotes(owner, repo, version, resolved.gitRef, packageName, from, changelogRef).catch(() => []);
|
|
761
|
+
let blogDocs = [];
|
|
762
|
+
if (getBlogPreset(packageName)) {
|
|
763
|
+
ctx.onProgress("Fetching blog release notes");
|
|
764
|
+
blogDocs = await fetchBlogReleases(packageName, version).catch(() => []);
|
|
765
|
+
}
|
|
766
|
+
const allDocs = [...releaseDocs, ...blogDocs];
|
|
767
|
+
const blogEntries = blogDocs.filter((d) => !d.path.endsWith("_INDEX.md")).map((d) => {
|
|
768
|
+
const versionMatch = d.path.match(BLOG_VERSION_RE);
|
|
769
|
+
const fm = parseFrontmatter(d.content);
|
|
770
|
+
return {
|
|
771
|
+
version: versionMatch?.[1] ?? "",
|
|
772
|
+
title: fm.title ?? `Release ${versionMatch?.[1]}`,
|
|
773
|
+
date: fm.date ?? ""
|
|
774
|
+
};
|
|
775
|
+
}).filter((b) => b.version);
|
|
776
|
+
const ghReleases = releaseDocs.filter((d) => d.path.startsWith("releases/") && !d.path.endsWith("CHANGELOG.md")).map((d) => {
|
|
777
|
+
const fm = parseFrontmatter(d.content);
|
|
778
|
+
const tag = fm.tag ?? "";
|
|
779
|
+
const name = fm.name ?? tag;
|
|
780
|
+
const published = fm.published ?? "";
|
|
781
|
+
return {
|
|
782
|
+
id: 0,
|
|
783
|
+
tag,
|
|
784
|
+
name,
|
|
785
|
+
prerelease: false,
|
|
786
|
+
createdAt: published,
|
|
787
|
+
publishedAt: published,
|
|
788
|
+
markdown: ""
|
|
789
|
+
};
|
|
790
|
+
}).filter((r) => r.tag);
|
|
791
|
+
const hasChangelog = allDocs.some((d) => d.path === "releases/CHANGELOG.md");
|
|
792
|
+
if (ghReleases.length > 0 || blogEntries.length > 0) allDocs.push({
|
|
793
|
+
path: "releases/_INDEX.md",
|
|
794
|
+
content: generateReleaseIndex({
|
|
795
|
+
releases: ghReleases,
|
|
796
|
+
packageName,
|
|
797
|
+
blogReleases: blogEntries,
|
|
798
|
+
hasChangelog
|
|
799
|
+
})
|
|
800
|
+
});
|
|
801
|
+
if (allDocs.length === 0) return;
|
|
802
|
+
ctx.onProgress(`Caching ${allDocs.length} releases`);
|
|
803
|
+
writeToRepoCache(owner, repo, allDocs);
|
|
804
|
+
for (const doc of allDocs) ctx.docsToIndex.push({
|
|
805
|
+
id: doc.path,
|
|
806
|
+
content: doc.content,
|
|
807
|
+
metadata: {
|
|
808
|
+
package: packageName,
|
|
809
|
+
source: doc.path,
|
|
810
|
+
type: "release"
|
|
811
|
+
}
|
|
812
|
+
});
|
|
813
|
+
}
|
|
814
|
+
})
|
|
815
|
+
];
|
|
816
|
+
async function resolveTimelineReferences(opts) {
|
|
817
|
+
const { packageName, resolved, version, features, from, onProgress } = opts;
|
|
818
|
+
const gh = resolved.repoUrl ? parseGitHubUrl(resolved.repoUrl) : null;
|
|
819
|
+
const repoInfo = gh ? {
|
|
820
|
+
owner: gh.owner,
|
|
821
|
+
repo: gh.repo
|
|
822
|
+
} : void 0;
|
|
823
|
+
const repoCacheDir = repoInfo ? getRepoCacheDir(repoInfo.owner, repoInfo.repo) : null;
|
|
824
|
+
const cacheDir = getCacheDir(packageName, version);
|
|
825
|
+
const ctx = {
|
|
826
|
+
packageName,
|
|
827
|
+
version,
|
|
828
|
+
resolved,
|
|
829
|
+
features,
|
|
830
|
+
from,
|
|
831
|
+
onProgress,
|
|
832
|
+
repoInfo,
|
|
833
|
+
issuesDir: repoCacheDir ? join(repoCacheDir, "issues") : join(cacheDir, "issues"),
|
|
834
|
+
discussionsDir: repoCacheDir ? join(repoCacheDir, "discussions") : join(cacheDir, "discussions"),
|
|
835
|
+
releasesPath: repoCacheDir ? join(repoCacheDir, "releases") : join(cacheDir, "releases"),
|
|
836
|
+
docsToIndex: []
|
|
837
|
+
};
|
|
838
|
+
await walkSteps(defaultTimelineSteps, ctx);
|
|
839
|
+
return {
|
|
840
|
+
docsToIndex: ctx.docsToIndex,
|
|
841
|
+
hasIssues: features.issues && existsSync(ctx.issuesDir),
|
|
842
|
+
hasDiscussions: features.discussions && existsSync(ctx.discussionsDir),
|
|
843
|
+
hasReleases: features.releases && existsSync(ctx.releasesPath),
|
|
844
|
+
repoInfo
|
|
845
|
+
};
|
|
846
|
+
}
|
|
847
|
+
async function findRelatedSkills(packageName, skillsDir) {
|
|
848
|
+
const related = [];
|
|
849
|
+
const npmInfo = await fetchNpmPackage(packageName);
|
|
850
|
+
if (!npmInfo?.dependencies) return related;
|
|
851
|
+
const deps = new Set(Object.keys(npmInfo.dependencies));
|
|
852
|
+
if (!existsSync(skillsDir)) return related;
|
|
853
|
+
const lock = readLock(skillsDir);
|
|
854
|
+
const pkgToDirName = lock ? buildPackageDirMap(lock) : /* @__PURE__ */ new Map();
|
|
855
|
+
const installedSet = new Set(readdirSync(skillsDir));
|
|
856
|
+
for (const dep of deps) {
|
|
857
|
+
const dirName = pkgToDirName.get(dep);
|
|
858
|
+
if (dirName && installedSet.has(dirName)) related.push(dirName);
|
|
859
|
+
}
|
|
860
|
+
return related.slice(0, 5);
|
|
861
|
+
}
|
|
862
|
+
function detectChangelog(pkgDir, cacheDir) {
|
|
863
|
+
if (pkgDir) {
|
|
864
|
+
const found = ["CHANGELOG.md", "changelog.md"].find((f) => existsSync(join(pkgDir, f)));
|
|
865
|
+
if (found) return `pkg/${found}`;
|
|
866
|
+
}
|
|
867
|
+
if (cacheDir && existsSync(join(cacheDir, "releases", "CHANGELOG.md"))) return "releases/CHANGELOG.md";
|
|
868
|
+
return false;
|
|
869
|
+
}
|
|
870
|
+
async function fetchAndCacheResources(opts) {
|
|
871
|
+
const { packageName, resolved, version, onProgress } = opts;
|
|
872
|
+
const features = opts.features ?? readConfig().features ?? defaultFeatures;
|
|
873
|
+
const cache = createReferenceCache(packageName, version);
|
|
874
|
+
const cacheInvalidated = opts.useCache && resolved.crawlUrl && cache.detectDocs(resolved.repoUrl, resolved.llmsUrl).docsType === "readme";
|
|
875
|
+
const useCache = opts.useCache && !cacheInvalidated;
|
|
876
|
+
let docSource = resolved.readmeUrl || "readme";
|
|
877
|
+
let docsType = "readme";
|
|
878
|
+
const docsToIndex = [];
|
|
879
|
+
const warnings = [];
|
|
880
|
+
if (cacheInvalidated) warnings.push(`Retrying crawl for ${resolved.crawlUrl} (previous attempt only cached README)`);
|
|
881
|
+
if (!useCache) {
|
|
882
|
+
const content = await resolveContentDocs({
|
|
883
|
+
packageName,
|
|
884
|
+
resolved,
|
|
885
|
+
version,
|
|
886
|
+
onProgress
|
|
887
|
+
});
|
|
888
|
+
docSource = content.docSource;
|
|
889
|
+
docsType = content.docsType;
|
|
890
|
+
docsToIndex.push(...content.docsToIndex);
|
|
891
|
+
warnings.push(...content.warnings);
|
|
892
|
+
if (content.docs.length > 0) {
|
|
893
|
+
cache.write(content.docs);
|
|
894
|
+
if (docsType !== "readme" && content.docs.filter((d) => d.path.startsWith("docs/") && d.path.endsWith(".md")).length > 1) {
|
|
895
|
+
const docsIndex = generateDocsIndex(content.docs);
|
|
896
|
+
if (docsIndex) cache.write([{
|
|
897
|
+
path: "docs/_INDEX.md",
|
|
898
|
+
content: docsIndex
|
|
899
|
+
}]);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
} else {
|
|
903
|
+
const cached = cache.load({
|
|
904
|
+
repoUrl: resolved.repoUrl,
|
|
905
|
+
llmsUrl: resolved.llmsUrl,
|
|
906
|
+
readmeUrl: resolved.readmeUrl,
|
|
907
|
+
onProgress,
|
|
908
|
+
generateDocsIndex
|
|
909
|
+
});
|
|
910
|
+
docsType = cached.docsType;
|
|
911
|
+
docSource = cached.docSource;
|
|
912
|
+
docsToIndex.push(...cached.docsToIndex);
|
|
913
|
+
if (cached.backfillIndex) cache.write([cached.backfillIndex]);
|
|
914
|
+
}
|
|
915
|
+
const timeline = await resolveTimelineReferences({
|
|
916
|
+
packageName,
|
|
917
|
+
resolved,
|
|
918
|
+
version,
|
|
919
|
+
features,
|
|
920
|
+
from: opts.from,
|
|
921
|
+
onProgress
|
|
922
|
+
});
|
|
923
|
+
docsToIndex.push(...timeline.docsToIndex);
|
|
924
|
+
return {
|
|
925
|
+
docSource,
|
|
926
|
+
docsType,
|
|
927
|
+
docsToIndex,
|
|
928
|
+
hasIssues: timeline.hasIssues,
|
|
929
|
+
hasDiscussions: timeline.hasDiscussions,
|
|
930
|
+
hasReleases: timeline.hasReleases,
|
|
931
|
+
warnings,
|
|
932
|
+
repoInfo: timeline.repoInfo,
|
|
933
|
+
usedCache: useCache
|
|
934
|
+
};
|
|
935
|
+
}
|
|
936
|
+
async function prepareSkillReferences(opts) {
|
|
937
|
+
const { packageName, version, cwd, skillDir, resources, features, baseDir, extraPackages, onIndexProgress } = opts;
|
|
938
|
+
const cache = createReferenceCache(packageName, version);
|
|
939
|
+
cache.linkInto(skillDir, cwd, resources.docsType, {
|
|
940
|
+
features,
|
|
941
|
+
repoInfo: resources.repoInfo,
|
|
942
|
+
extraPackages
|
|
943
|
+
});
|
|
944
|
+
if (features.search) await indexResources({
|
|
945
|
+
packageName,
|
|
946
|
+
version,
|
|
947
|
+
cwd,
|
|
948
|
+
docsToIndex: resources.docsToIndex,
|
|
949
|
+
features,
|
|
950
|
+
onProgress: onIndexProgress ?? (() => {})
|
|
951
|
+
});
|
|
952
|
+
return {
|
|
953
|
+
hasChangelog: detectChangelog(cache.pkgDir(cwd), cache.dir),
|
|
954
|
+
shippedDocs: cache.hasShipped(cwd),
|
|
955
|
+
pkgFiles: cache.keyFiles(cwd),
|
|
956
|
+
relatedSkills: baseDir ? await findRelatedSkills(packageName, baseDir) : []
|
|
957
|
+
};
|
|
958
|
+
}
|
|
959
|
+
function buildSkillContext(opts) {
|
|
960
|
+
const { packages, cachePackageName, packageName } = opts;
|
|
961
|
+
return {
|
|
962
|
+
packageName,
|
|
963
|
+
...cachePackageName && cachePackageName !== packageName ? { cachePackageName } : {},
|
|
964
|
+
version: opts.version,
|
|
965
|
+
skillDir: opts.skillDir,
|
|
966
|
+
dirName: opts.skillDirName,
|
|
967
|
+
references: {
|
|
968
|
+
docsType: opts.resources.docsType,
|
|
969
|
+
hasShippedDocs: opts.prepared.shippedDocs,
|
|
970
|
+
pkgFiles: opts.prepared.pkgFiles,
|
|
971
|
+
hasIssues: opts.resources.hasIssues,
|
|
972
|
+
hasDiscussions: opts.resources.hasDiscussions,
|
|
973
|
+
hasReleases: opts.resources.hasReleases,
|
|
974
|
+
hasChangelog: opts.prepared.hasChangelog
|
|
975
|
+
},
|
|
976
|
+
resolved: opts.resolved,
|
|
977
|
+
relatedSkills: opts.prepared.relatedSkills,
|
|
978
|
+
packages: packages && packages.length > 1 ? packages : void 0,
|
|
979
|
+
features: opts.features,
|
|
980
|
+
overheadLines: opts.overheadLines
|
|
981
|
+
};
|
|
982
|
+
}
|
|
983
|
+
export { prepareSkillReferences as a, selectLlmConfig as c, runSkillEnhancement as d, writeBaseSkill as f, findRelatedSkills as i, applyCachedSections as l, detectChangelog as n, DEFAULT_SECTIONS as o, writePromptFiles as p, fetchAndCacheResources as r, resolveAutoModel as s, buildSkillContext as t, enhanceSkillWithLLM as u };
|
|
984
|
+
|
|
985
|
+
//# sourceMappingURL=pipeline.mjs.map
|