skilld 1.4.0 → 1.5.1
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 +54 -4
- package/dist/_chunks/agent.mjs +4 -3
- package/dist/_chunks/agent.mjs.map +1 -1
- package/dist/_chunks/assemble.mjs +2 -0
- package/dist/_chunks/assemble.mjs.map +1 -1
- package/dist/_chunks/author.mjs +480 -0
- package/dist/_chunks/author.mjs.map +1 -0
- package/dist/_chunks/cache.mjs +2 -39
- package/dist/_chunks/cache.mjs.map +1 -1
- package/dist/_chunks/cache2.mjs +2 -1
- package/dist/_chunks/cache2.mjs.map +1 -1
- package/dist/_chunks/cli-helpers.mjs +110 -1
- package/dist/_chunks/cli-helpers.mjs.map +1 -1
- package/dist/_chunks/cli-helpers2.mjs +11 -0
- package/dist/_chunks/core.mjs +1 -0
- package/dist/_chunks/detect.mjs.map +1 -1
- package/dist/_chunks/embedding-cache.mjs +3 -60
- package/dist/_chunks/embedding-cache2.mjs +61 -0
- package/dist/_chunks/embedding-cache2.mjs.map +1 -0
- package/dist/_chunks/index.d.mts +13 -21
- package/dist/_chunks/index.d.mts.map +1 -1
- package/dist/_chunks/index2.d.mts +32 -598
- package/dist/_chunks/index2.d.mts.map +1 -1
- package/dist/_chunks/index3.d.mts +615 -0
- package/dist/_chunks/index3.d.mts.map +1 -0
- package/dist/_chunks/install.mjs +12 -20
- package/dist/_chunks/install.mjs.map +1 -1
- package/dist/_chunks/list.mjs +3 -1
- package/dist/_chunks/list.mjs.map +1 -1
- package/dist/_chunks/lockfile.mjs +140 -0
- package/dist/_chunks/lockfile.mjs.map +1 -0
- package/dist/_chunks/pool.mjs +2 -123
- package/dist/_chunks/pool2.mjs +118 -0
- package/dist/_chunks/pool2.mjs.map +1 -0
- package/dist/_chunks/prepare.mjs +50 -0
- package/dist/_chunks/prepare.mjs.map +1 -0
- package/dist/_chunks/prepare2.mjs +93 -0
- package/dist/_chunks/prepare2.mjs.map +1 -0
- package/dist/_chunks/prompts.mjs +32 -43
- package/dist/_chunks/prompts.mjs.map +1 -1
- package/dist/_chunks/retriv.mjs +172 -0
- package/dist/_chunks/retriv.mjs.map +1 -0
- package/dist/_chunks/sanitize.mjs.map +1 -1
- package/dist/_chunks/search-interactive.mjs +5 -3
- package/dist/_chunks/search-interactive.mjs.map +1 -1
- package/dist/_chunks/search.mjs +13 -184
- package/dist/_chunks/search2.mjs +319 -0
- package/dist/_chunks/search2.mjs.map +1 -0
- package/dist/_chunks/setup.mjs +3 -2
- package/dist/_chunks/setup.mjs.map +1 -1
- package/dist/_chunks/skills.mjs +28 -142
- package/dist/_chunks/skills.mjs.map +1 -1
- package/dist/_chunks/sources.mjs +4 -2
- package/dist/_chunks/sources.mjs.map +1 -1
- package/dist/_chunks/sync-shared.mjs +16 -0
- package/dist/_chunks/sync-shared2.mjs +1055 -0
- package/dist/_chunks/sync-shared2.mjs.map +1 -0
- package/dist/_chunks/sync.mjs +75 -1068
- package/dist/_chunks/sync.mjs.map +1 -1
- package/dist/_chunks/sync2.mjs +21 -0
- package/dist/_chunks/uninstall.mjs +9 -4
- package/dist/_chunks/uninstall.mjs.map +1 -1
- package/dist/_chunks/wizard.mjs +186 -0
- package/dist/_chunks/wizard.mjs.map +1 -0
- package/dist/agent/index.d.mts +4 -2
- package/dist/agent/index.d.mts.map +1 -1
- package/dist/agent/index.mjs +1 -0
- package/dist/cache/index.d.mts +1 -1
- package/dist/cache/index.mjs +2 -1
- package/dist/cli-entry.d.mts +1 -0
- package/dist/cli-entry.mjs +11 -0
- package/dist/cli-entry.mjs.map +1 -0
- package/dist/cli.mjs +86 -196
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +3 -3
- package/dist/index.mjs +3 -2
- package/dist/prepare.d.mts +1 -0
- package/dist/prepare.mjs +93 -0
- package/dist/prepare.mjs.map +1 -0
- package/dist/retriv/index.d.mts +2 -46
- package/dist/retriv/index.mjs +2 -171
- package/dist/sources/index.d.mts +1 -1
- package/dist/types.d.mts +1 -1
- package/package.json +7 -6
- package/dist/_chunks/embedding-cache.mjs.map +0 -1
- package/dist/_chunks/pool.mjs.map +0 -1
- package/dist/_chunks/search.mjs.map +0 -1
- package/dist/retriv/index.d.mts.map +0 -1
- package/dist/retriv/index.mjs.map +0 -1
package/dist/_chunks/sync.mjs
CHANGED
|
@@ -1,1061 +1,27 @@
|
|
|
1
|
-
import { a as getModelLabel, i as getAvailableModels,
|
|
2
|
-
import {
|
|
1
|
+
import { a as getModelLabel, i as getAvailableModels, s as optimizeDocs, t as detectImportedPackages } from "./agent.mjs";
|
|
2
|
+
import { c as getVersionKey, o as getCacheDir, t as CACHE_DIR } from "./config.mjs";
|
|
3
|
+
import { r as resolvePkgDir } from "./prepare.mjs";
|
|
3
4
|
import { n as sanitizeMarkdown } from "./sanitize.mjs";
|
|
4
|
-
import {
|
|
5
|
-
import "./yaml.mjs";
|
|
5
|
+
import { a as hasShippedDocs, f as listReferenceFiles, i as getPkgKeyFiles, l as linkPkgNamed, m as readCachedSection, o as isCached, r as ensureCacheDir } from "./cache.mjs";
|
|
6
6
|
import { i as parseFrontmatter } from "./markdown.mjs";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { A as parseGitSkillInput, B as isGhAvailable, C as downloadLlmsDocs, D as normalizeLlmsLinks, F as formatDiscussionAsMarkdown, G as fetchReleaseNotes, H as toCrawlPattern, I as generateDiscussionIndex, K as generateReleaseIndex, L as fetchGitHubIssues, M as resolveEntryFiles, N as generateDocsIndex, P as fetchGitHubDiscussions, R as formatIssueAsMarkdown, T as fetchLlmsTxt, U as fetchBlogReleases, V as fetchCrawledDocs, Z as fetchGitHubRaw, b as isShallowGitDocs, f as resolvePackageDocsWithAttempts, h as fetchGitDocs, i as fetchPkgDist, k as fetchGitSkills, n as fetchNpmPackage, nt as parsePackageSpec, p as searchNpmPackages, q as isPrerelease, s as readLocalDependencies, tt as parseGitHubUrl, u as resolveLocalPackageDocs, v as fetchReadmeContent, x as resolveGitHubRepo, y as filterFrameworkDocs, z as generateIssueIndex } from "./sources.mjs";
|
|
7
|
+
import { a as semverDiff, n as getSharedSkillsDir, t as SHARED_SKILLS_DIR } from "./shared.mjs";
|
|
8
|
+
import { A as parseGitSkillInput, f as resolvePackageDocsWithAttempts, i as fetchPkgDist, k as fetchGitSkills, nt as parsePackageSpec, p as searchNpmPackages, q as isPrerelease, s as readLocalDependencies, x as resolveGitHubRepo } from "./sources.mjs";
|
|
10
9
|
import { a as targets } from "./detect.mjs";
|
|
11
|
-
import { a as sanitizeName, c as SECTION_OUTPUT_FILES,
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
10
|
+
import { a as sanitizeName, c as SECTION_OUTPUT_FILES, i as linkSkillToAgents, l as buildAllSectionPrompts, m as wrapSection, n as computeSkillDirName, p as portabilizePrompt, s as SECTION_MERGE_ORDER, t as generateSkillMd } from "./prompts.mjs";
|
|
11
|
+
import { A as registerProject, D as hasCompletedWizard, T as defaultFeatures, b as sharedArgs, d as introLine, f as isInteractive, g as promptForAgent, k as readConfig, o as getInstalledGenerators, x as suggestPrepareHook, y as resolveAgent } from "./cli-helpers.mjs";
|
|
12
|
+
import { a as removeLockEntry, i as readLock, n as parsePackages, s as writeLock } from "./lockfile.mjs";
|
|
13
|
+
import { t as getProjectState } from "./skills.mjs";
|
|
14
14
|
import { l as timedSpinner, n as formatDuration } from "./formatting.mjs";
|
|
15
|
-
import { t as runWizard } from "
|
|
16
|
-
import { n as
|
|
15
|
+
import { t as runWizard } from "./wizard.mjs";
|
|
16
|
+
import { S as writePromptFiles, _ as linkAllReferences, b as selectLlmConfig, c as ejectReferences, d as ensureGitignore, f as fetchAndCacheResources, g as indexResources, h as handleShippedSkills, l as enhanceSkillWithLLM, m as forceClearCache, n as RESOLVE_STEP_LABELS, o as detectChangelog, p as findRelatedSkills, t as DEFAULT_SECTIONS, u as ensureAgentInstructions, v as resolveBaseDir, y as resolveLocalDep } from "./sync-shared2.mjs";
|
|
17
|
+
import { n as shutdownWorker } from "./pool2.mjs";
|
|
17
18
|
import { dirname, join, relative, resolve } from "pathe";
|
|
18
|
-
import {
|
|
19
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
19
20
|
import { isCI } from "std-env";
|
|
20
21
|
import pLimit from "p-limit";
|
|
21
22
|
import * as p from "@clack/prompts";
|
|
22
23
|
import { defineCommand } from "citty";
|
|
23
24
|
import logUpdate from "log-update";
|
|
24
|
-
//#region src/commands/sync-shared.ts
|
|
25
|
-
/** Max docs sent to the embedding pipeline to prevent oversized indexes */
|
|
26
|
-
const MAX_INDEX_DOCS = 250;
|
|
27
|
-
const RESOLVE_STEP_LABELS = {
|
|
28
|
-
"npm": "npm registry",
|
|
29
|
-
"github-docs": "GitHub docs",
|
|
30
|
-
"github-meta": "GitHub meta",
|
|
31
|
-
"github-search": "GitHub search",
|
|
32
|
-
"readme": "README",
|
|
33
|
-
"llms.txt": "llms.txt",
|
|
34
|
-
"crawl": "website crawl",
|
|
35
|
-
"local": "node_modules"
|
|
36
|
-
};
|
|
37
|
-
/** Classify a cached doc path into the right metadata type */
|
|
38
|
-
function classifyCachedDoc(path) {
|
|
39
|
-
const issueMatch = path.match(/^issues\/issue-(\d+)\.md$/);
|
|
40
|
-
if (issueMatch) return {
|
|
41
|
-
type: "issue",
|
|
42
|
-
number: Number(issueMatch[1])
|
|
43
|
-
};
|
|
44
|
-
const discussionMatch = path.match(/^discussions\/discussion-(\d+)\.md$/);
|
|
45
|
-
if (discussionMatch) return {
|
|
46
|
-
type: "discussion",
|
|
47
|
-
number: Number(discussionMatch[1])
|
|
48
|
-
};
|
|
49
|
-
if (path.startsWith("releases/")) return { type: "release" };
|
|
50
|
-
return { type: "doc" };
|
|
51
|
-
}
|
|
52
|
-
async function findRelatedSkills(packageName, skillsDir) {
|
|
53
|
-
const related = [];
|
|
54
|
-
const npmInfo = await fetchNpmPackage(packageName);
|
|
55
|
-
if (!npmInfo?.dependencies) return related;
|
|
56
|
-
const deps = new Set(Object.keys(npmInfo.dependencies));
|
|
57
|
-
if (!existsSync(skillsDir)) return related;
|
|
58
|
-
const lock = readLock(skillsDir);
|
|
59
|
-
const pkgToDirName = /* @__PURE__ */ new Map();
|
|
60
|
-
if (lock) for (const [dirName, info] of Object.entries(lock.skills)) {
|
|
61
|
-
if (info.packageName) pkgToDirName.set(info.packageName, dirName);
|
|
62
|
-
for (const pkg of parsePackages(info.packages)) pkgToDirName.set(pkg.name, dirName);
|
|
63
|
-
}
|
|
64
|
-
const installedSkills = readdirSync(skillsDir);
|
|
65
|
-
const installedSet = new Set(installedSkills);
|
|
66
|
-
for (const dep of deps) {
|
|
67
|
-
const dirName = pkgToDirName.get(dep);
|
|
68
|
-
if (dirName && installedSet.has(dirName)) related.push(dirName);
|
|
69
|
-
}
|
|
70
|
-
return related.slice(0, 5);
|
|
71
|
-
}
|
|
72
|
-
/** Clear cache + db for --force flag */
|
|
73
|
-
function forceClearCache(packageName, version, repoInfo) {
|
|
74
|
-
clearCache(packageName, version);
|
|
75
|
-
const forcedDbPath = getPackageDbPath(packageName, version);
|
|
76
|
-
if (existsSync(forcedDbPath)) rmSync(forcedDbPath, {
|
|
77
|
-
recursive: true,
|
|
78
|
-
force: true
|
|
79
|
-
});
|
|
80
|
-
if (repoInfo) {
|
|
81
|
-
const repoDir = getRepoCacheDir(repoInfo.owner, repoInfo.repo);
|
|
82
|
-
if (existsSync(repoDir)) rmSync(repoDir, {
|
|
83
|
-
recursive: true,
|
|
84
|
-
force: true
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
/** Link all reference symlinks (pkg, docs, issues, discussions, releases) */
|
|
89
|
-
function linkAllReferences(skillDir, packageName, cwd, version, docsType, extraPackages, features, repoInfo) {
|
|
90
|
-
const f = features ?? readConfig().features ?? defaultFeatures;
|
|
91
|
-
try {
|
|
92
|
-
linkPkg(skillDir, packageName, cwd, version);
|
|
93
|
-
linkPkgNamed(skillDir, packageName, cwd, version);
|
|
94
|
-
if (!hasShippedDocs(packageName, cwd, version) && docsType !== "readme") linkCachedDir(skillDir, packageName, version, "docs");
|
|
95
|
-
if (f.issues) if (repoInfo) linkRepoCachedDir(skillDir, repoInfo.owner, repoInfo.repo, "issues");
|
|
96
|
-
else linkCachedDir(skillDir, packageName, version, "issues");
|
|
97
|
-
if (f.discussions) if (repoInfo) linkRepoCachedDir(skillDir, repoInfo.owner, repoInfo.repo, "discussions");
|
|
98
|
-
else linkCachedDir(skillDir, packageName, version, "discussions");
|
|
99
|
-
if (f.releases) if (repoInfo) linkRepoCachedDir(skillDir, repoInfo.owner, repoInfo.repo, "releases");
|
|
100
|
-
else linkCachedDir(skillDir, packageName, version, "releases");
|
|
101
|
-
linkCachedDir(skillDir, packageName, version, "sections");
|
|
102
|
-
if (extraPackages) {
|
|
103
|
-
for (const pkg of extraPackages) if (pkg.name !== packageName) linkPkgNamed(skillDir, pkg.name, cwd, pkg.version);
|
|
104
|
-
}
|
|
105
|
-
} catch {}
|
|
106
|
-
}
|
|
107
|
-
/** Detect docs type from cached directory contents */
|
|
108
|
-
function detectDocsType(packageName, version, repoUrl, llmsUrl) {
|
|
109
|
-
const cacheDir = getCacheDir(packageName, version);
|
|
110
|
-
if (existsSync(join(cacheDir, "docs", "index.md")) || existsSync(join(cacheDir, "docs", "guide"))) return {
|
|
111
|
-
docsType: "docs",
|
|
112
|
-
docSource: repoUrl ? `${repoUrl}/tree/v${version}/docs` : "git"
|
|
113
|
-
};
|
|
114
|
-
if (existsSync(join(cacheDir, "llms.txt"))) return {
|
|
115
|
-
docsType: "llms.txt",
|
|
116
|
-
docSource: llmsUrl || "llms.txt"
|
|
117
|
-
};
|
|
118
|
-
if (existsSync(join(cacheDir, "docs", "README.md"))) return { docsType: "readme" };
|
|
119
|
-
return { docsType: "readme" };
|
|
120
|
-
}
|
|
121
|
-
/** Link shipped skills, write lock entries, register project. Returns result or null if no shipped skills. */
|
|
122
|
-
function handleShippedSkills(packageName, version, cwd, agent, global) {
|
|
123
|
-
const shippedSkills = getShippedSkills(packageName, cwd, version);
|
|
124
|
-
if (shippedSkills.length === 0) return null;
|
|
125
|
-
const baseDir = resolveBaseDir(cwd, agent, global);
|
|
126
|
-
mkdirSync(baseDir, { recursive: true });
|
|
127
|
-
for (const shipped of shippedSkills) {
|
|
128
|
-
linkShippedSkill(baseDir, shipped.skillName, shipped.skillDir);
|
|
129
|
-
writeLock(baseDir, shipped.skillName, {
|
|
130
|
-
packageName,
|
|
131
|
-
version,
|
|
132
|
-
source: "shipped",
|
|
133
|
-
syncedAt: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
|
|
134
|
-
generator: "skilld"
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
if (!global) registerProject(cwd);
|
|
138
|
-
return {
|
|
139
|
-
shipped: shippedSkills,
|
|
140
|
-
baseDir
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
/** Resolve the base skills directory for an agent */
|
|
144
|
-
function resolveBaseDir(cwd, agent, global) {
|
|
145
|
-
if (global) return targets[agent].globalSkillsDir;
|
|
146
|
-
const shared = getSharedSkillsDir(cwd);
|
|
147
|
-
if (shared) return shared;
|
|
148
|
-
const agentConfig = targets[agent];
|
|
149
|
-
return join(cwd, agentConfig.skillsDir);
|
|
150
|
-
}
|
|
151
|
-
/** Try resolving a `link:` dependency to local package docs. Returns null if not a link dep or resolution fails. */
|
|
152
|
-
async function resolveLocalDep(packageName, cwd) {
|
|
153
|
-
const pkgPath = join(cwd, "package.json");
|
|
154
|
-
if (!existsSync(pkgPath)) return null;
|
|
155
|
-
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
156
|
-
const depVersion = {
|
|
157
|
-
...pkg.dependencies,
|
|
158
|
-
...pkg.devDependencies
|
|
159
|
-
}[packageName];
|
|
160
|
-
if (!depVersion?.startsWith("link:")) return null;
|
|
161
|
-
return resolveLocalPackageDocs(resolve(cwd, depVersion.slice(5)));
|
|
162
|
-
}
|
|
163
|
-
/** Detect CHANGELOG.md in a package directory or cached releases */
|
|
164
|
-
function detectChangelog(pkgDir, cacheDir) {
|
|
165
|
-
if (pkgDir) {
|
|
166
|
-
const found = ["CHANGELOG.md", "changelog.md"].find((f) => existsSync(join(pkgDir, f)));
|
|
167
|
-
if (found) return `pkg/${found}`;
|
|
168
|
-
}
|
|
169
|
-
if (cacheDir && existsSync(join(cacheDir, "releases", "CHANGELOG.md"))) return "releases/CHANGELOG.md";
|
|
170
|
-
return false;
|
|
171
|
-
}
|
|
172
|
-
/** Fetch and cache all resources for a package (docs cascade + issues + discussions + releases) */
|
|
173
|
-
async function fetchAndCacheResources(opts) {
|
|
174
|
-
const { packageName, resolved, version, onProgress } = opts;
|
|
175
|
-
const features = opts.features ?? readConfig().features ?? defaultFeatures;
|
|
176
|
-
const cacheInvalidated = opts.useCache && resolved.crawlUrl && detectDocsType(packageName, version, resolved.repoUrl, resolved.llmsUrl).docsType === "readme";
|
|
177
|
-
const useCache = opts.useCache && !cacheInvalidated;
|
|
178
|
-
let docSource = resolved.readmeUrl || "readme";
|
|
179
|
-
let docsType = "readme";
|
|
180
|
-
const docsToIndex = [];
|
|
181
|
-
const warnings = [];
|
|
182
|
-
if (cacheInvalidated) warnings.push(`Retrying crawl for ${resolved.crawlUrl} (previous attempt only cached README)`);
|
|
183
|
-
if (!useCache) {
|
|
184
|
-
const cachedDocs = [];
|
|
185
|
-
const isFrameworkDoc = (path) => filterFrameworkDocs([path], packageName).length > 0;
|
|
186
|
-
if (resolved.gitDocsUrl && resolved.repoUrl) {
|
|
187
|
-
const gh = parseGitHubUrl(resolved.repoUrl);
|
|
188
|
-
if (gh) {
|
|
189
|
-
onProgress("Fetching git docs");
|
|
190
|
-
const gitDocs = await fetchGitDocs(gh.owner, gh.repo, version, packageName);
|
|
191
|
-
if (gitDocs?.fallback) warnings.push(`Docs fetched from ${gitDocs.ref} branch (no tag found for v${version})`);
|
|
192
|
-
if (gitDocs && gitDocs.files.length > 0) {
|
|
193
|
-
const BATCH_SIZE = 20;
|
|
194
|
-
const results = [];
|
|
195
|
-
for (let i = 0; i < gitDocs.files.length; i += BATCH_SIZE) {
|
|
196
|
-
const batch = gitDocs.files.slice(i, i + BATCH_SIZE);
|
|
197
|
-
onProgress(`Downloading docs ${Math.min(i + BATCH_SIZE, gitDocs.files.length)}/${gitDocs.files.length} from ${gitDocs.ref}`);
|
|
198
|
-
const batchResults = await Promise.all(batch.map(async (file) => {
|
|
199
|
-
const content = await fetchGitHubRaw(`${gitDocs.baseUrl}/${file}`);
|
|
200
|
-
if (!content) return null;
|
|
201
|
-
return {
|
|
202
|
-
file,
|
|
203
|
-
content
|
|
204
|
-
};
|
|
205
|
-
}));
|
|
206
|
-
results.push(...batchResults);
|
|
207
|
-
}
|
|
208
|
-
for (const r of results) if (r) {
|
|
209
|
-
const stripped = gitDocs.docsPrefix ? r.file.replace(gitDocs.docsPrefix, "") : r.file;
|
|
210
|
-
const cachePath = stripped.startsWith("docs/") ? stripped : `docs/${stripped}`;
|
|
211
|
-
cachedDocs.push({
|
|
212
|
-
path: cachePath,
|
|
213
|
-
content: r.content
|
|
214
|
-
});
|
|
215
|
-
docsToIndex.push({
|
|
216
|
-
id: cachePath,
|
|
217
|
-
content: r.content,
|
|
218
|
-
metadata: {
|
|
219
|
-
package: packageName,
|
|
220
|
-
source: cachePath,
|
|
221
|
-
type: "doc"
|
|
222
|
-
}
|
|
223
|
-
});
|
|
224
|
-
}
|
|
225
|
-
const downloaded = results.filter(Boolean).length;
|
|
226
|
-
if (downloaded > 0) if (isShallowGitDocs(downloaded) && resolved.llmsUrl) {
|
|
227
|
-
onProgress(`Shallow git-docs (${downloaded} files), trying llms.txt`);
|
|
228
|
-
cachedDocs.length = 0;
|
|
229
|
-
docsToIndex.length = 0;
|
|
230
|
-
} else {
|
|
231
|
-
docSource = `${resolved.repoUrl}/tree/${gitDocs.ref}/docs`;
|
|
232
|
-
docsType = "docs";
|
|
233
|
-
writeToCache(packageName, version, cachedDocs);
|
|
234
|
-
if (resolved.llmsUrl) {
|
|
235
|
-
onProgress("Caching supplementary llms.txt");
|
|
236
|
-
const llmsContent = await fetchLlmsTxt(resolved.llmsUrl);
|
|
237
|
-
if (llmsContent) {
|
|
238
|
-
const baseUrl = resolved.docsUrl || new URL(resolved.llmsUrl).origin;
|
|
239
|
-
const supplementary = [{
|
|
240
|
-
path: "llms.txt",
|
|
241
|
-
content: normalizeLlmsLinks(llmsContent.raw, baseUrl)
|
|
242
|
-
}];
|
|
243
|
-
if (llmsContent.links.length > 0) {
|
|
244
|
-
onProgress(`Downloading ${llmsContent.links.length} supplementary docs`);
|
|
245
|
-
const docs = await downloadLlmsDocs(llmsContent, baseUrl, (url, done, total) => {
|
|
246
|
-
onProgress(`Downloading supplementary doc ${done + 1}/${total}`);
|
|
247
|
-
});
|
|
248
|
-
for (const doc of docs) {
|
|
249
|
-
if (!isFrameworkDoc(doc.url)) continue;
|
|
250
|
-
const localPath = doc.url.startsWith("/") ? doc.url.slice(1) : doc.url;
|
|
251
|
-
supplementary.push({
|
|
252
|
-
path: join("llms-docs", ...localPath.split("/")),
|
|
253
|
-
content: doc.content
|
|
254
|
-
});
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
writeToCache(packageName, version, supplementary);
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
if (resolved.crawlUrl && cachedDocs.length === 0) {
|
|
265
|
-
onProgress("Crawling website");
|
|
266
|
-
const crawledDocs = await fetchCrawledDocs(resolved.crawlUrl, onProgress).catch((err) => {
|
|
267
|
-
warnings.push(`Crawl failed for ${resolved.crawlUrl}: ${err?.message || err}`);
|
|
268
|
-
return [];
|
|
269
|
-
});
|
|
270
|
-
if (crawledDocs.length === 0 && resolved.crawlUrl) warnings.push(`Crawl returned 0 docs from ${resolved.crawlUrl}`);
|
|
271
|
-
if (crawledDocs.length > 0) {
|
|
272
|
-
for (const doc of crawledDocs) {
|
|
273
|
-
if (!isFrameworkDoc(doc.path)) continue;
|
|
274
|
-
cachedDocs.push(doc);
|
|
275
|
-
docsToIndex.push({
|
|
276
|
-
id: doc.path,
|
|
277
|
-
content: doc.content,
|
|
278
|
-
metadata: {
|
|
279
|
-
package: packageName,
|
|
280
|
-
source: doc.path,
|
|
281
|
-
type: "doc"
|
|
282
|
-
}
|
|
283
|
-
});
|
|
284
|
-
}
|
|
285
|
-
docSource = resolved.crawlUrl;
|
|
286
|
-
docsType = "docs";
|
|
287
|
-
writeToCache(packageName, version, cachedDocs);
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
if (resolved.llmsUrl && cachedDocs.length === 0) {
|
|
291
|
-
onProgress("Fetching llms.txt");
|
|
292
|
-
const llmsContent = await fetchLlmsTxt(resolved.llmsUrl);
|
|
293
|
-
if (llmsContent) {
|
|
294
|
-
docSource = resolved.llmsUrl;
|
|
295
|
-
docsType = "llms.txt";
|
|
296
|
-
const baseUrl = resolved.docsUrl || new URL(resolved.llmsUrl).origin;
|
|
297
|
-
cachedDocs.push({
|
|
298
|
-
path: "llms.txt",
|
|
299
|
-
content: normalizeLlmsLinks(llmsContent.raw, baseUrl)
|
|
300
|
-
});
|
|
301
|
-
if (llmsContent.links.length > 0) {
|
|
302
|
-
onProgress(`Downloading ${llmsContent.links.length} linked docs`);
|
|
303
|
-
const docs = await downloadLlmsDocs(llmsContent, baseUrl, (url, done, total) => {
|
|
304
|
-
onProgress(`Downloading linked doc ${done + 1}/${total}`);
|
|
305
|
-
});
|
|
306
|
-
for (const doc of docs) {
|
|
307
|
-
if (!isFrameworkDoc(doc.url)) continue;
|
|
308
|
-
const cachePath = join("docs", ...(doc.url.startsWith("/") ? doc.url.slice(1) : doc.url).split("/"));
|
|
309
|
-
cachedDocs.push({
|
|
310
|
-
path: cachePath,
|
|
311
|
-
content: doc.content
|
|
312
|
-
});
|
|
313
|
-
docsToIndex.push({
|
|
314
|
-
id: doc.url,
|
|
315
|
-
content: doc.content,
|
|
316
|
-
metadata: {
|
|
317
|
-
package: packageName,
|
|
318
|
-
source: cachePath,
|
|
319
|
-
type: "doc"
|
|
320
|
-
}
|
|
321
|
-
});
|
|
322
|
-
}
|
|
323
|
-
if (docs.length > 0) docsType = "docs";
|
|
324
|
-
}
|
|
325
|
-
writeToCache(packageName, version, cachedDocs);
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
if (resolved.docsUrl && !cachedDocs.some((d) => d.path.startsWith("docs/"))) {
|
|
329
|
-
const crawlPattern = resolved.crawlUrl || toCrawlPattern(resolved.docsUrl);
|
|
330
|
-
onProgress("Crawling docs site");
|
|
331
|
-
const crawledDocs = await fetchCrawledDocs(crawlPattern, onProgress, resolved.crawlUrl ? 200 : 400).catch((err) => {
|
|
332
|
-
warnings.push(`Crawl failed for ${crawlPattern}: ${err?.message || err}`);
|
|
333
|
-
return [];
|
|
334
|
-
});
|
|
335
|
-
if (crawledDocs.length > 0) {
|
|
336
|
-
for (const doc of crawledDocs) {
|
|
337
|
-
if (!isFrameworkDoc(doc.path)) continue;
|
|
338
|
-
cachedDocs.push(doc);
|
|
339
|
-
docsToIndex.push({
|
|
340
|
-
id: doc.path,
|
|
341
|
-
content: doc.content,
|
|
342
|
-
metadata: {
|
|
343
|
-
package: packageName,
|
|
344
|
-
source: doc.path,
|
|
345
|
-
type: "doc"
|
|
346
|
-
}
|
|
347
|
-
});
|
|
348
|
-
}
|
|
349
|
-
docSource = crawlPattern;
|
|
350
|
-
docsType = "docs";
|
|
351
|
-
writeToCache(packageName, version, cachedDocs);
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
if (resolved.readmeUrl && cachedDocs.length === 0) {
|
|
355
|
-
onProgress("Fetching README");
|
|
356
|
-
const content = await fetchReadmeContent(resolved.readmeUrl);
|
|
357
|
-
if (content) {
|
|
358
|
-
cachedDocs.push({
|
|
359
|
-
path: "docs/README.md",
|
|
360
|
-
content
|
|
361
|
-
});
|
|
362
|
-
docsToIndex.push({
|
|
363
|
-
id: "README.md",
|
|
364
|
-
content,
|
|
365
|
-
metadata: {
|
|
366
|
-
package: packageName,
|
|
367
|
-
source: "docs/README.md",
|
|
368
|
-
type: "doc"
|
|
369
|
-
}
|
|
370
|
-
});
|
|
371
|
-
writeToCache(packageName, version, cachedDocs);
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
if (docsType !== "readme" && cachedDocs.filter((d) => d.path.startsWith("docs/") && d.path.endsWith(".md")).length > 1) {
|
|
375
|
-
const docsIndex = generateDocsIndex(cachedDocs);
|
|
376
|
-
if (docsIndex) writeToCache(packageName, version, [{
|
|
377
|
-
path: "docs/_INDEX.md",
|
|
378
|
-
content: docsIndex
|
|
379
|
-
}]);
|
|
380
|
-
}
|
|
381
|
-
} else {
|
|
382
|
-
onProgress("Loading cached docs");
|
|
383
|
-
const detected = detectDocsType(packageName, version, resolved.repoUrl, resolved.llmsUrl);
|
|
384
|
-
docsType = detected.docsType;
|
|
385
|
-
if (detected.docSource) docSource = detected.docSource;
|
|
386
|
-
if (!existsSync(getPackageDbPath(packageName, version))) {
|
|
387
|
-
onProgress("Reading cached docs for indexing");
|
|
388
|
-
const cached = readCachedDocs(packageName, version);
|
|
389
|
-
for (const doc of cached) docsToIndex.push({
|
|
390
|
-
id: doc.path,
|
|
391
|
-
content: doc.content,
|
|
392
|
-
metadata: {
|
|
393
|
-
package: packageName,
|
|
394
|
-
source: doc.path,
|
|
395
|
-
...classifyCachedDoc(doc.path)
|
|
396
|
-
}
|
|
397
|
-
});
|
|
398
|
-
}
|
|
399
|
-
if (docsType !== "readme" && !existsSync(join(getCacheDir(packageName, version), "docs", "_INDEX.md"))) {
|
|
400
|
-
onProgress("Generating docs index");
|
|
401
|
-
const cached = readCachedDocs(packageName, version);
|
|
402
|
-
if (cached.filter((d) => d.path.startsWith("docs/") && d.path.endsWith(".md")).length > 1) {
|
|
403
|
-
const docsIndex = generateDocsIndex(cached);
|
|
404
|
-
if (docsIndex) writeToCache(packageName, version, [{
|
|
405
|
-
path: "docs/_INDEX.md",
|
|
406
|
-
content: docsIndex
|
|
407
|
-
}]);
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
const gh = resolved.repoUrl ? parseGitHubUrl(resolved.repoUrl) : null;
|
|
412
|
-
const repoInfo = gh ? {
|
|
413
|
-
owner: gh.owner,
|
|
414
|
-
repo: gh.repo
|
|
415
|
-
} : void 0;
|
|
416
|
-
const repoCacheDir = repoInfo ? getRepoCacheDir(repoInfo.owner, repoInfo.repo) : null;
|
|
417
|
-
const cacheDir = getCacheDir(packageName, version);
|
|
418
|
-
const issuesDir = repoCacheDir ? join(repoCacheDir, "issues") : join(cacheDir, "issues");
|
|
419
|
-
const discussionsDir = repoCacheDir ? join(repoCacheDir, "discussions") : join(cacheDir, "discussions");
|
|
420
|
-
const releasesPath = repoCacheDir ? join(repoCacheDir, "releases") : join(cacheDir, "releases");
|
|
421
|
-
if (features.issues && gh && isGhAvailable() && !existsSync(issuesDir)) {
|
|
422
|
-
onProgress("Fetching issues via GitHub API");
|
|
423
|
-
const issues = await fetchGitHubIssues(gh.owner, gh.repo, 30, resolved.releasedAt, opts.from).catch(() => []);
|
|
424
|
-
if (issues.length > 0) {
|
|
425
|
-
onProgress(`Caching ${issues.length} issues`);
|
|
426
|
-
const issueDocs = [...issues.map((issue) => ({
|
|
427
|
-
path: `issues/issue-${issue.number}.md`,
|
|
428
|
-
content: formatIssueAsMarkdown(issue)
|
|
429
|
-
})), {
|
|
430
|
-
path: "issues/_INDEX.md",
|
|
431
|
-
content: generateIssueIndex(issues)
|
|
432
|
-
}];
|
|
433
|
-
if (repoInfo) writeToRepoCache(repoInfo.owner, repoInfo.repo, issueDocs);
|
|
434
|
-
else writeToCache(packageName, version, issueDocs);
|
|
435
|
-
for (const issue of issues) docsToIndex.push({
|
|
436
|
-
id: `issue-${issue.number}`,
|
|
437
|
-
content: sanitizeMarkdown(`#${issue.number}: ${issue.title}\n\n${issue.body || ""}`),
|
|
438
|
-
metadata: {
|
|
439
|
-
package: packageName,
|
|
440
|
-
source: `issues/issue-${issue.number}.md`,
|
|
441
|
-
type: "issue",
|
|
442
|
-
number: issue.number
|
|
443
|
-
}
|
|
444
|
-
});
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
if (features.discussions && gh && isGhAvailable() && !existsSync(discussionsDir)) {
|
|
448
|
-
onProgress("Fetching discussions via GitHub API");
|
|
449
|
-
const discussions = await fetchGitHubDiscussions(gh.owner, gh.repo, 20, resolved.releasedAt, opts.from).catch(() => []);
|
|
450
|
-
if (discussions.length > 0) {
|
|
451
|
-
onProgress(`Caching ${discussions.length} discussions`);
|
|
452
|
-
const discussionDocs = [...discussions.map((d) => ({
|
|
453
|
-
path: `discussions/discussion-${d.number}.md`,
|
|
454
|
-
content: formatDiscussionAsMarkdown(d)
|
|
455
|
-
})), {
|
|
456
|
-
path: "discussions/_INDEX.md",
|
|
457
|
-
content: generateDiscussionIndex(discussions)
|
|
458
|
-
}];
|
|
459
|
-
if (repoInfo) writeToRepoCache(repoInfo.owner, repoInfo.repo, discussionDocs);
|
|
460
|
-
else writeToCache(packageName, version, discussionDocs);
|
|
461
|
-
for (const d of discussions) docsToIndex.push({
|
|
462
|
-
id: `discussion-${d.number}`,
|
|
463
|
-
content: sanitizeMarkdown(`#${d.number}: ${d.title}\n\n${d.body || ""}`),
|
|
464
|
-
metadata: {
|
|
465
|
-
package: packageName,
|
|
466
|
-
source: `discussions/discussion-${d.number}.md`,
|
|
467
|
-
type: "discussion",
|
|
468
|
-
number: d.number
|
|
469
|
-
}
|
|
470
|
-
});
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
if (features.releases && gh && isGhAvailable() && !existsSync(releasesPath)) {
|
|
474
|
-
onProgress("Fetching releases via GitHub API");
|
|
475
|
-
const changelogRef = isPrerelease(version) ? getPrereleaseChangelogRef(packageName) : void 0;
|
|
476
|
-
const releaseDocs = await fetchReleaseNotes(gh.owner, gh.repo, version, resolved.gitRef, packageName, opts.from, changelogRef).catch(() => []);
|
|
477
|
-
let blogDocs = [];
|
|
478
|
-
if (getBlogPreset(packageName)) {
|
|
479
|
-
onProgress("Fetching blog release notes");
|
|
480
|
-
blogDocs = await fetchBlogReleases(packageName, version).catch(() => []);
|
|
481
|
-
}
|
|
482
|
-
const allDocs = [...releaseDocs, ...blogDocs];
|
|
483
|
-
const blogEntries = blogDocs.filter((d) => !d.path.endsWith("_INDEX.md")).map((d) => {
|
|
484
|
-
const versionMatch = d.path.match(/blog-(.+)\.md$/);
|
|
485
|
-
const fm = parseFrontmatter(d.content);
|
|
486
|
-
return {
|
|
487
|
-
version: versionMatch?.[1] ?? "",
|
|
488
|
-
title: fm.title ?? `Release ${versionMatch?.[1]}`,
|
|
489
|
-
date: fm.date ?? ""
|
|
490
|
-
};
|
|
491
|
-
}).filter((b) => b.version);
|
|
492
|
-
const ghReleases = releaseDocs.filter((d) => d.path.startsWith("releases/") && !d.path.endsWith("CHANGELOG.md")).map((d) => {
|
|
493
|
-
const fm = parseFrontmatter(d.content);
|
|
494
|
-
const tag = fm.tag ?? "";
|
|
495
|
-
const name = fm.name ?? tag;
|
|
496
|
-
const published = fm.published ?? "";
|
|
497
|
-
return {
|
|
498
|
-
id: 0,
|
|
499
|
-
tag,
|
|
500
|
-
name,
|
|
501
|
-
prerelease: false,
|
|
502
|
-
createdAt: published,
|
|
503
|
-
publishedAt: published,
|
|
504
|
-
markdown: ""
|
|
505
|
-
};
|
|
506
|
-
}).filter((r) => r.tag);
|
|
507
|
-
const hasChangelog = allDocs.some((d) => d.path === "releases/CHANGELOG.md");
|
|
508
|
-
if (ghReleases.length > 0 || blogEntries.length > 0) allDocs.push({
|
|
509
|
-
path: "releases/_INDEX.md",
|
|
510
|
-
content: generateReleaseIndex({
|
|
511
|
-
releases: ghReleases,
|
|
512
|
-
packageName,
|
|
513
|
-
blogReleases: blogEntries,
|
|
514
|
-
hasChangelog
|
|
515
|
-
})
|
|
516
|
-
});
|
|
517
|
-
if (allDocs.length > 0) {
|
|
518
|
-
onProgress(`Caching ${allDocs.length} releases`);
|
|
519
|
-
if (repoInfo) writeToRepoCache(repoInfo.owner, repoInfo.repo, allDocs);
|
|
520
|
-
else writeToCache(packageName, version, allDocs);
|
|
521
|
-
for (const doc of allDocs) docsToIndex.push({
|
|
522
|
-
id: doc.path,
|
|
523
|
-
content: doc.content,
|
|
524
|
-
metadata: {
|
|
525
|
-
package: packageName,
|
|
526
|
-
source: doc.path,
|
|
527
|
-
type: "release"
|
|
528
|
-
}
|
|
529
|
-
});
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
return {
|
|
533
|
-
docSource,
|
|
534
|
-
docsType,
|
|
535
|
-
docsToIndex,
|
|
536
|
-
hasIssues: features.issues && existsSync(issuesDir),
|
|
537
|
-
hasDiscussions: features.discussions && existsSync(discussionsDir),
|
|
538
|
-
hasReleases: features.releases && existsSync(releasesPath),
|
|
539
|
-
warnings,
|
|
540
|
-
repoInfo,
|
|
541
|
-
usedCache: useCache
|
|
542
|
-
};
|
|
543
|
-
}
|
|
544
|
-
/**
|
|
545
|
-
* Extract the parent document ID from a chunk ID.
|
|
546
|
-
* Chunk IDs have the form "docId#chunk-N"; non-chunk IDs return as-is.
|
|
547
|
-
*/
|
|
548
|
-
function parentDocId(id) {
|
|
549
|
-
const idx = id.indexOf("#chunk-");
|
|
550
|
-
return idx === -1 ? id : id.slice(0, idx);
|
|
551
|
-
}
|
|
552
|
-
/** Cap and sort docs by type priority, mutates and truncates allDocs in place */
|
|
553
|
-
function capDocs(allDocs, max, onProgress) {
|
|
554
|
-
if (allDocs.length <= max) return;
|
|
555
|
-
const TYPE_PRIORITY = {
|
|
556
|
-
doc: 0,
|
|
557
|
-
issue: 1,
|
|
558
|
-
discussion: 2,
|
|
559
|
-
release: 3,
|
|
560
|
-
source: 4,
|
|
561
|
-
types: 5
|
|
562
|
-
};
|
|
563
|
-
allDocs.sort((a, b) => {
|
|
564
|
-
const ta = TYPE_PRIORITY[a.metadata?.type || "doc"] ?? 3;
|
|
565
|
-
const tb = TYPE_PRIORITY[b.metadata?.type || "doc"] ?? 3;
|
|
566
|
-
if (ta !== tb) return ta - tb;
|
|
567
|
-
return a.id.localeCompare(b.id);
|
|
568
|
-
});
|
|
569
|
-
onProgress(`Indexing capped at ${max}/${allDocs.length} docs (prioritized by type)`);
|
|
570
|
-
allDocs.length = max;
|
|
571
|
-
}
|
|
572
|
-
/** Index all resources into the search database, with incremental support */
|
|
573
|
-
async function indexResources(opts) {
|
|
574
|
-
const { packageName, version, cwd, onProgress } = opts;
|
|
575
|
-
const features = opts.features ?? readConfig().features ?? defaultFeatures;
|
|
576
|
-
if (!features.search) return;
|
|
577
|
-
const dbPath = getPackageDbPath(packageName, version);
|
|
578
|
-
const dbExists = existsSync(dbPath);
|
|
579
|
-
const allDocs = [...opts.docsToIndex];
|
|
580
|
-
const pkgDir = resolvePkgDir(packageName, cwd, version);
|
|
581
|
-
if (features.search && pkgDir) {
|
|
582
|
-
onProgress("Scanning exports");
|
|
583
|
-
const entryFiles = await resolveEntryFiles(pkgDir);
|
|
584
|
-
for (const e of entryFiles) allDocs.push({
|
|
585
|
-
id: e.path,
|
|
586
|
-
content: e.content,
|
|
587
|
-
metadata: {
|
|
588
|
-
package: packageName,
|
|
589
|
-
source: `pkg/${e.path}`,
|
|
590
|
-
type: e.type
|
|
591
|
-
}
|
|
592
|
-
});
|
|
593
|
-
}
|
|
594
|
-
if (allDocs.length === 0) return;
|
|
595
|
-
capDocs(allDocs, MAX_INDEX_DOCS, onProgress);
|
|
596
|
-
if (!dbExists) {
|
|
597
|
-
onProgress(`Building search index (${allDocs.length} docs)`);
|
|
598
|
-
try {
|
|
599
|
-
await createIndex(allDocs, {
|
|
600
|
-
dbPath,
|
|
601
|
-
onProgress: ({ phase, current, total }) => {
|
|
602
|
-
if (phase === "storing") {
|
|
603
|
-
const d = allDocs[current - 1];
|
|
604
|
-
onProgress(`Storing ${d?.metadata?.type === "source" || d?.metadata?.type === "types" ? "code" : d?.metadata?.type || "doc"} (${current}/${total})`);
|
|
605
|
-
} else if (phase === "embedding") onProgress(`Creating embeddings (${current}/${total})`);
|
|
606
|
-
}
|
|
607
|
-
});
|
|
608
|
-
} catch (err) {
|
|
609
|
-
if (err instanceof SearchDepsUnavailableError) onProgress("Search indexing skipped (native deps unavailable)");
|
|
610
|
-
else throw err;
|
|
611
|
-
}
|
|
612
|
-
return;
|
|
613
|
-
}
|
|
614
|
-
let existingIds;
|
|
615
|
-
try {
|
|
616
|
-
existingIds = await listIndexIds({ dbPath });
|
|
617
|
-
} catch (err) {
|
|
618
|
-
if (err instanceof SearchDepsUnavailableError) {
|
|
619
|
-
onProgress("Search indexing skipped (native deps unavailable)");
|
|
620
|
-
return;
|
|
621
|
-
}
|
|
622
|
-
throw err;
|
|
623
|
-
}
|
|
624
|
-
const existingParentIds = new Set(existingIds.map(parentDocId));
|
|
625
|
-
const incomingIds = new Set(allDocs.map((d) => d.id));
|
|
626
|
-
const newDocs = allDocs.filter((d) => !existingParentIds.has(d.id));
|
|
627
|
-
const removeIds = existingIds.filter((id) => !incomingIds.has(parentDocId(id)));
|
|
628
|
-
if (newDocs.length === 0 && removeIds.length === 0) {
|
|
629
|
-
onProgress("Search index up to date");
|
|
630
|
-
return;
|
|
631
|
-
}
|
|
632
|
-
const parts = [];
|
|
633
|
-
if (newDocs.length > 0) parts.push(`+${newDocs.length} new`);
|
|
634
|
-
if (removeIds.length > 0) parts.push(`-${removeIds.length} stale`);
|
|
635
|
-
onProgress(`Updating search index (${parts.join(", ")})`);
|
|
636
|
-
try {
|
|
637
|
-
await createIndex(newDocs, {
|
|
638
|
-
dbPath,
|
|
639
|
-
removeIds,
|
|
640
|
-
onProgress: ({ phase, current, total }) => {
|
|
641
|
-
if (phase === "storing") {
|
|
642
|
-
const d = newDocs[current - 1];
|
|
643
|
-
onProgress(`Storing ${d?.metadata?.type === "source" || d?.metadata?.type === "types" ? "code" : d?.metadata?.type || "doc"} (${current}/${total})`);
|
|
644
|
-
} else if (phase === "embedding") onProgress(`Creating embeddings (${current}/${total})`);
|
|
645
|
-
}
|
|
646
|
-
});
|
|
647
|
-
} catch (err) {
|
|
648
|
-
if (err instanceof SearchDepsUnavailableError) onProgress("Search indexing skipped (native deps unavailable)");
|
|
649
|
-
else throw err;
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
/**
|
|
653
|
-
* Eject references: copy cached files as real files into references/ dir.
|
|
654
|
-
* Used for portable skills (git repos, sharing). Replaces symlinks with copies.
|
|
655
|
-
* Does NOT copy pkg files — those reference node_modules directly.
|
|
656
|
-
*/
|
|
657
|
-
function ejectReferences(skillDir, packageName, cwd, version, docsType, features, repoInfo) {
|
|
658
|
-
const f = features ?? readConfig().features ?? defaultFeatures;
|
|
659
|
-
const cacheDir = getCacheDir(packageName, version);
|
|
660
|
-
const refsDir = join(skillDir, "references");
|
|
661
|
-
const repoDir = repoInfo ? getRepoCacheDir(repoInfo.owner, repoInfo.repo) : cacheDir;
|
|
662
|
-
if (!hasShippedDocs(packageName, cwd, version) && docsType !== "readme") copyCachedSubdir(cacheDir, refsDir, "docs");
|
|
663
|
-
if (f.issues) copyCachedSubdir(repoDir, refsDir, "issues");
|
|
664
|
-
if (f.discussions) copyCachedSubdir(repoDir, refsDir, "discussions");
|
|
665
|
-
if (f.releases) copyCachedSubdir(repoDir, refsDir, "releases");
|
|
666
|
-
}
|
|
667
|
-
/** Recursively copy a cached subdirectory into the references dir */
|
|
668
|
-
function copyCachedSubdir(cacheDir, refsDir, subdir) {
|
|
669
|
-
const srcDir = join(cacheDir, subdir);
|
|
670
|
-
if (!existsSync(srcDir)) return;
|
|
671
|
-
const destDir = join(refsDir, subdir);
|
|
672
|
-
mkdirSync(destDir, { recursive: true });
|
|
673
|
-
function walk(dir, rel) {
|
|
674
|
-
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
675
|
-
const srcPath = join(dir, entry.name);
|
|
676
|
-
const destPath = join(destDir, rel ? `${rel}/${entry.name}` : entry.name);
|
|
677
|
-
if (entry.isDirectory()) {
|
|
678
|
-
mkdirSync(destPath, { recursive: true });
|
|
679
|
-
walk(srcPath, rel ? `${rel}/${entry.name}` : entry.name);
|
|
680
|
-
} else copyFileSync(srcPath, destPath);
|
|
681
|
-
}
|
|
682
|
-
}
|
|
683
|
-
walk(srcDir, "");
|
|
684
|
-
}
|
|
685
|
-
/**
|
|
686
|
-
* Check if .gitignore has `.skilld` entry.
|
|
687
|
-
* If missing, prompt to add it. Skipped for global installs.
|
|
688
|
-
*/
|
|
689
|
-
async function ensureGitignore(skillsDir, cwd, isGlobal) {
|
|
690
|
-
if (isGlobal) return;
|
|
691
|
-
const gitignorePath = join(cwd, ".gitignore");
|
|
692
|
-
const pattern = ".skilld";
|
|
693
|
-
if (existsSync(gitignorePath)) {
|
|
694
|
-
if (readFileSync(gitignorePath, "utf-8").split("\n").some((line) => line.trim() === pattern)) return;
|
|
695
|
-
}
|
|
696
|
-
if (!isInteractive()) {
|
|
697
|
-
const entry = `\n# Skilld references (recreated by \`skilld install\`)\n${pattern}\n`;
|
|
698
|
-
if (existsSync(gitignorePath)) appendFileSync(gitignorePath, `${readFileSync(gitignorePath, "utf-8").endsWith("\n") ? "" : "\n"}${entry}`);
|
|
699
|
-
else writeFileSync(gitignorePath, entry);
|
|
700
|
-
return;
|
|
701
|
-
}
|
|
702
|
-
const relSkillsDir = relative(cwd, skillsDir) || ".";
|
|
703
|
-
p.log.info(`\x1B[1mGit guidance:\x1B[0m\n \x1B[32m✓\x1B[0m Commit: \x1B[36m${relSkillsDir}/*/SKILL.md\x1B[0m\n \x1B[32m✓\x1B[0m Commit: \x1B[36m${relSkillsDir}/skilld-lock.yaml\x1B[0m\n \x1B[31m✗\x1B[0m Ignore: \x1B[36m${pattern}\x1B[0m \x1B[90m(recreated by \`skilld install\`)\x1B[0m`);
|
|
704
|
-
const add = await p.confirm({
|
|
705
|
-
message: `Add \`${pattern}\` to .gitignore?`,
|
|
706
|
-
initialValue: true
|
|
707
|
-
});
|
|
708
|
-
if (p.isCancel(add) || !add) return;
|
|
709
|
-
const entry = `\n# Skilld references (recreated by \`skilld install\`)\n${pattern}\n`;
|
|
710
|
-
if (existsSync(gitignorePath)) appendFileSync(gitignorePath, `${readFileSync(gitignorePath, "utf-8").endsWith("\n") ? "" : "\n"}${entry}`);
|
|
711
|
-
else writeFileSync(gitignorePath, entry);
|
|
712
|
-
p.log.success("Updated .gitignore");
|
|
713
|
-
}
|
|
714
|
-
const SKILLD_MARKER_START = "<!-- skilld -->";
|
|
715
|
-
const SKILLD_MARKER_END = "<!-- /skilld -->";
|
|
716
|
-
const DEFAULT_SKILL_HINT = "Before modifying code, evaluate each installed skill against the current task.\nFor each skill, determine YES/NO relevance and invoke all YES skills before proceeding.";
|
|
717
|
-
function getSkillInstructions(agent) {
|
|
718
|
-
return `${SKILLD_MARKER_START}\n${targets[agent].skillActivationHint || DEFAULT_SKILL_HINT}\n${SKILLD_MARKER_END}`;
|
|
719
|
-
}
|
|
720
|
-
function getMdcSkillInstructions(agent) {
|
|
721
|
-
return `---\ndescription: "Activates installed skilld skills before code changes"\nalwaysApply: true\n---\n\n${targets[agent].skillActivationHint || DEFAULT_SKILL_HINT}`;
|
|
722
|
-
}
|
|
723
|
-
/**
|
|
724
|
-
* Check if agent instruction file has skilld skill-activation snippet.
|
|
725
|
-
* If missing, prompt to add it. Skipped for global installs or agents without an instructionFile.
|
|
726
|
-
*/
|
|
727
|
-
async function ensureAgentInstructions(agent, cwd, isGlobal) {
|
|
728
|
-
if (isGlobal) return;
|
|
729
|
-
const agentConfig = targets[agent];
|
|
730
|
-
if (!agentConfig.instructionFile) return;
|
|
731
|
-
const filePath = join(cwd, agentConfig.instructionFile);
|
|
732
|
-
if (agentConfig.instructionFile.endsWith(".mdc")) {
|
|
733
|
-
if (existsSync(filePath)) return;
|
|
734
|
-
const content = `${getMdcSkillInstructions(agent)}\n`;
|
|
735
|
-
if (!isInteractive()) {
|
|
736
|
-
mkdirSync(join(filePath, ".."), { recursive: true });
|
|
737
|
-
writeFileSync(filePath, content);
|
|
738
|
-
return;
|
|
739
|
-
}
|
|
740
|
-
p.note(`This tells your agent to check installed skills before making
|
|
741
|
-
code changes. Without it, skills are available but may not
|
|
742
|
-
activate automatically.
|
|
743
|
-
|
|
744
|
-
\x1B[90m${getMdcSkillInstructions(agent)}\x1B[0m`, `Create ${agentConfig.instructionFile}`);
|
|
745
|
-
const add = await p.confirm({
|
|
746
|
-
message: `Create ${agentConfig.instructionFile} with skill activation instructions?`,
|
|
747
|
-
initialValue: true
|
|
748
|
-
});
|
|
749
|
-
if (p.isCancel(add) || !add) return;
|
|
750
|
-
mkdirSync(join(filePath, ".."), { recursive: true });
|
|
751
|
-
writeFileSync(filePath, content);
|
|
752
|
-
p.log.success(`Created ${agentConfig.instructionFile}`);
|
|
753
|
-
return;
|
|
754
|
-
}
|
|
755
|
-
if (existsSync(filePath)) {
|
|
756
|
-
if (readFileSync(filePath, "utf-8").includes("<!-- skilld -->")) return;
|
|
757
|
-
}
|
|
758
|
-
if (!isInteractive()) {
|
|
759
|
-
if (existsSync(filePath)) appendFileSync(filePath, `${readFileSync(filePath, "utf-8").endsWith("\n") ? "" : "\n"}\n${getSkillInstructions(agent)}\n`);
|
|
760
|
-
else writeFileSync(filePath, `${getSkillInstructions(agent)}\n`);
|
|
761
|
-
return;
|
|
762
|
-
}
|
|
763
|
-
const action = existsSync(filePath) ? "Append to" : "Create";
|
|
764
|
-
p.note(`This tells your agent to check installed skills before making
|
|
765
|
-
code changes. Without it, skills are available but may not
|
|
766
|
-
activate automatically.
|
|
767
|
-
|
|
768
|
-
\x1B[90m${getSkillInstructions(agent).replace(/\n/g, "\n")}\x1B[0m`, `${action} ${agentConfig.instructionFile}`);
|
|
769
|
-
const add = await p.confirm({
|
|
770
|
-
message: `${action} ${agentConfig.instructionFile} with skill activation instructions?`,
|
|
771
|
-
initialValue: true
|
|
772
|
-
});
|
|
773
|
-
if (p.isCancel(add) || !add) return;
|
|
774
|
-
if (existsSync(filePath)) appendFileSync(filePath, `${readFileSync(filePath, "utf-8").endsWith("\n") ? "" : "\n"}\n${getSkillInstructions(agent)}\n`);
|
|
775
|
-
else writeFileSync(filePath, `${getSkillInstructions(agent)}\n`);
|
|
776
|
-
p.log.success(`Updated ${agentConfig.instructionFile}`);
|
|
777
|
-
}
|
|
778
|
-
/** Default sections when model is pre-set (non-interactive) */
|
|
779
|
-
const DEFAULT_SECTIONS = ["best-practices", "api-changes"];
|
|
780
|
-
async function selectSkillSections(message = "Enhance SKILL.md") {
|
|
781
|
-
p.log.info("Budgets adapt to package release density.");
|
|
782
|
-
const selected = await p.multiselect({
|
|
783
|
-
message,
|
|
784
|
-
options: [
|
|
785
|
-
{
|
|
786
|
-
label: "API changes",
|
|
787
|
-
value: "api-changes",
|
|
788
|
-
hint: "new/deprecated APIs from version history"
|
|
789
|
-
},
|
|
790
|
-
{
|
|
791
|
-
label: "Best practices",
|
|
792
|
-
value: "best-practices",
|
|
793
|
-
hint: "gotchas, pitfalls, patterns"
|
|
794
|
-
},
|
|
795
|
-
{
|
|
796
|
-
label: "Custom section",
|
|
797
|
-
value: "custom",
|
|
798
|
-
hint: "add your own section"
|
|
799
|
-
}
|
|
800
|
-
],
|
|
801
|
-
initialValues: DEFAULT_SECTIONS,
|
|
802
|
-
required: false
|
|
803
|
-
});
|
|
804
|
-
if (p.isCancel(selected)) return {
|
|
805
|
-
sections: [],
|
|
806
|
-
cancelled: true
|
|
807
|
-
};
|
|
808
|
-
const sections = selected;
|
|
809
|
-
if (sections.length === 0) return {
|
|
810
|
-
sections: [],
|
|
811
|
-
cancelled: false
|
|
812
|
-
};
|
|
813
|
-
if (sections.length > 1) {
|
|
814
|
-
const n = sections.length;
|
|
815
|
-
const budgetLines = [];
|
|
816
|
-
for (const s of sections) switch (s) {
|
|
817
|
-
case "api-changes":
|
|
818
|
-
budgetLines.push(` API changes ${maxItems(6, 12, n)}–${maxItems(6, Math.round(12 * 1.6), n)} items (adapts to release churn)`);
|
|
819
|
-
break;
|
|
820
|
-
case "best-practices":
|
|
821
|
-
budgetLines.push(` Best practices ${maxItems(4, 10, n)}–${maxItems(4, Math.round(10 * 1.3), n)} items`);
|
|
822
|
-
break;
|
|
823
|
-
case "custom":
|
|
824
|
-
budgetLines.push(` Custom ≤${maxLines(50, 80, n)} lines`);
|
|
825
|
-
break;
|
|
826
|
-
}
|
|
827
|
-
p.log.info(`Budget (${n} sections):\n${budgetLines.join("\n")}`);
|
|
828
|
-
}
|
|
829
|
-
let customPrompt;
|
|
830
|
-
if (sections.includes("custom")) {
|
|
831
|
-
const heading = await p.text({
|
|
832
|
-
message: "Section heading",
|
|
833
|
-
placeholder: "e.g. \"Migration from v2\" or \"SSR Patterns\""
|
|
834
|
-
});
|
|
835
|
-
if (p.isCancel(heading)) return {
|
|
836
|
-
sections: [],
|
|
837
|
-
cancelled: true
|
|
838
|
-
};
|
|
839
|
-
const body = await p.text({
|
|
840
|
-
message: "Instructions for this section",
|
|
841
|
-
placeholder: "e.g. \"Document breaking changes and migration steps from v2 to v3\""
|
|
842
|
-
});
|
|
843
|
-
if (p.isCancel(body)) return {
|
|
844
|
-
sections: [],
|
|
845
|
-
cancelled: true
|
|
846
|
-
};
|
|
847
|
-
customPrompt = {
|
|
848
|
-
heading,
|
|
849
|
-
body
|
|
850
|
-
};
|
|
851
|
-
}
|
|
852
|
-
return {
|
|
853
|
-
sections,
|
|
854
|
-
customPrompt,
|
|
855
|
-
cancelled: false
|
|
856
|
-
};
|
|
857
|
-
}
|
|
858
|
-
/**
|
|
859
|
-
* Resolve sections + model for LLM enhancement.
|
|
860
|
-
* If presetModel is provided, uses DEFAULT_SECTIONS without prompting.
|
|
861
|
-
* Returns null if cancelled or no sections/model selected.
|
|
862
|
-
*/
|
|
863
|
-
async function selectLlmConfig(presetModel, message, updateCtx) {
|
|
864
|
-
if (presetModel) {
|
|
865
|
-
if ((await getAvailableModels()).some((m) => m.id === presetModel)) return {
|
|
866
|
-
model: presetModel,
|
|
867
|
-
sections: DEFAULT_SECTIONS
|
|
868
|
-
};
|
|
869
|
-
if (!isInteractive()) return null;
|
|
870
|
-
}
|
|
871
|
-
if (!isInteractive()) return null;
|
|
872
|
-
const config = readConfig();
|
|
873
|
-
const available = await getAvailableModels();
|
|
874
|
-
if (available.length === 0) {
|
|
875
|
-
p.log.warn(NO_MODELS_MESSAGE);
|
|
876
|
-
return null;
|
|
877
|
-
}
|
|
878
|
-
let defaultModel;
|
|
879
|
-
if (config.model && available.some((m) => m.id === config.model)) defaultModel = config.model;
|
|
880
|
-
else {
|
|
881
|
-
if (config.model) p.log.warn(`Configured model \x1B[36m${config.model}\x1B[0m is unavailable — using auto-selected fallback`);
|
|
882
|
-
defaultModel = available.find((m) => m.recommended)?.id ?? available[0].id;
|
|
883
|
-
}
|
|
884
|
-
const defaultModelName = getModelName(defaultModel);
|
|
885
|
-
const providerHint = available.find((m) => m.id === defaultModel)?.providerName ?? "";
|
|
886
|
-
const sourceHint = config.model === defaultModel ? "configured" : "recommended";
|
|
887
|
-
const defaultHint = providerHint ? `${providerHint} · ${sourceHint}` : sourceHint;
|
|
888
|
-
let enhanceMessage = "Enhance SKILL.md?";
|
|
889
|
-
let defaultToSkip = false;
|
|
890
|
-
if (updateCtx) {
|
|
891
|
-
const diff = updateCtx.bumpType ?? (updateCtx.oldVersion && updateCtx.newVersion ? semverDiff(updateCtx.oldVersion, updateCtx.newVersion) : null);
|
|
892
|
-
const isSmallBump = diff === "patch" || diff === "prerelease" || diff === "prepatch" || diff === "preminor" || diff === "premajor";
|
|
893
|
-
const ageParts = [];
|
|
894
|
-
if (diff) ageParts.push(diff);
|
|
895
|
-
if (updateCtx.syncedAt) {
|
|
896
|
-
const syncedAtMs = new Date(updateCtx.syncedAt).getTime();
|
|
897
|
-
if (Number.isFinite(syncedAtMs)) {
|
|
898
|
-
const days = Math.floor((Date.now() - syncedAtMs) / 864e5);
|
|
899
|
-
ageParts.push(days === 0 ? "today" : days === 1 ? "1d ago" : `${days}d ago`);
|
|
900
|
-
}
|
|
901
|
-
}
|
|
902
|
-
if (updateCtx.wasEnhanced) ageParts.push("LLM-enhanced");
|
|
903
|
-
const hint = [updateCtx.oldVersion && updateCtx.newVersion ? `${updateCtx.oldVersion} → ${updateCtx.newVersion}` : null, ...ageParts].filter(Boolean).join(" · ");
|
|
904
|
-
if (hint) enhanceMessage = `Enhance SKILL.md? \x1B[90m(${hint})\x1B[0m`;
|
|
905
|
-
if (updateCtx.wasEnhanced && isSmallBump) defaultToSkip = true;
|
|
906
|
-
}
|
|
907
|
-
const choice = await p.select({
|
|
908
|
-
message: enhanceMessage,
|
|
909
|
-
options: [
|
|
910
|
-
{
|
|
911
|
-
label: defaultModelName,
|
|
912
|
-
value: "default",
|
|
913
|
-
hint: defaultHint
|
|
914
|
-
},
|
|
915
|
-
{
|
|
916
|
-
label: "Different model",
|
|
917
|
-
value: "pick",
|
|
918
|
-
hint: "choose another enhancement model"
|
|
919
|
-
},
|
|
920
|
-
{
|
|
921
|
-
label: "Prompt only",
|
|
922
|
-
value: "prompt",
|
|
923
|
-
hint: "write prompts for manual use"
|
|
924
|
-
},
|
|
925
|
-
{
|
|
926
|
-
label: "Skip",
|
|
927
|
-
value: "skip",
|
|
928
|
-
hint: "base skill with docs, issues, and types"
|
|
929
|
-
}
|
|
930
|
-
],
|
|
931
|
-
...defaultToSkip ? { initialValue: "skip" } : {}
|
|
932
|
-
});
|
|
933
|
-
if (p.isCancel(choice)) return null;
|
|
934
|
-
if (choice === "skip") return null;
|
|
935
|
-
if (choice === "prompt") {
|
|
936
|
-
const { sections, customPrompt, cancelled } = await selectSkillSections(message ? `${message} (prompt only)` : "Select sections for prompt generation");
|
|
937
|
-
if (cancelled || sections.length === 0) return null;
|
|
938
|
-
return {
|
|
939
|
-
model: defaultModel,
|
|
940
|
-
sections,
|
|
941
|
-
customPrompt,
|
|
942
|
-
promptOnly: true
|
|
943
|
-
};
|
|
944
|
-
}
|
|
945
|
-
let model;
|
|
946
|
-
if (choice === "pick") {
|
|
947
|
-
const picked = await pickModel(available);
|
|
948
|
-
if (!picked) return null;
|
|
949
|
-
updateConfig({ model: picked });
|
|
950
|
-
model = picked;
|
|
951
|
-
} else model = defaultModel;
|
|
952
|
-
if (!model) return null;
|
|
953
|
-
const modelName = getModelName(model);
|
|
954
|
-
const { sections, customPrompt, cancelled } = await selectSkillSections(message ? `${message} (${modelName})` : `Enhance SKILL.md with ${modelName}`);
|
|
955
|
-
if (cancelled || sections.length === 0) return null;
|
|
956
|
-
return {
|
|
957
|
-
model,
|
|
958
|
-
sections,
|
|
959
|
-
customPrompt
|
|
960
|
-
};
|
|
961
|
-
}
|
|
962
|
-
async function enhanceSkillWithLLM(opts) {
|
|
963
|
-
const { packageName, version, skillDir, dirName, model, resolved, relatedSkills, hasIssues, hasDiscussions, hasReleases, hasChangelog, docsType, hasShippedDocs: shippedDocs, pkgFiles, force, debug, sections, customPrompt, packages, features, eject } = opts;
|
|
964
|
-
const effectiveFeatures = features;
|
|
965
|
-
const llmLog = p.taskLog({ title: `Agent exploring ${packageName}` });
|
|
966
|
-
const docFiles = listReferenceFiles(skillDir);
|
|
967
|
-
const { optimized, wasOptimized, usage, cost, warnings, error, debugLogsDir } = await optimizeDocs({
|
|
968
|
-
packageName,
|
|
969
|
-
skillDir,
|
|
970
|
-
model,
|
|
971
|
-
version,
|
|
972
|
-
hasGithub: hasIssues || hasDiscussions,
|
|
973
|
-
hasReleases,
|
|
974
|
-
hasChangelog,
|
|
975
|
-
docFiles,
|
|
976
|
-
docsType,
|
|
977
|
-
hasShippedDocs: shippedDocs,
|
|
978
|
-
noCache: force,
|
|
979
|
-
debug,
|
|
980
|
-
sections,
|
|
981
|
-
customPrompt,
|
|
982
|
-
features: effectiveFeatures,
|
|
983
|
-
pkgFiles,
|
|
984
|
-
onProgress: createToolProgress(llmLog)
|
|
985
|
-
});
|
|
986
|
-
if (wasOptimized) {
|
|
987
|
-
const costParts = [];
|
|
988
|
-
if (usage) {
|
|
989
|
-
const totalK = Math.round(usage.totalTokens / 1e3);
|
|
990
|
-
costParts.push(`${totalK}k tokens`);
|
|
991
|
-
}
|
|
992
|
-
if (cost) costParts.push(`$${cost.toFixed(2)}`);
|
|
993
|
-
const costSuffix = costParts.length > 0 ? ` (${costParts.join(", ")})` : "";
|
|
994
|
-
llmLog.success(`Generated best practices${costSuffix}`);
|
|
995
|
-
if (debugLogsDir) p.log.info(`Debug logs: ${relative(process.cwd(), debugLogsDir)}`);
|
|
996
|
-
if (error) p.log.warn(`\x1B[33mPartial failure: ${error}\x1B[0m`);
|
|
997
|
-
if (warnings?.length) for (const w of warnings) p.log.warn(`\x1B[33m${w}\x1B[0m`);
|
|
998
|
-
const skillMd = generateSkillMd({
|
|
999
|
-
name: packageName,
|
|
1000
|
-
version,
|
|
1001
|
-
releasedAt: resolved.releasedAt,
|
|
1002
|
-
dependencies: resolved.dependencies,
|
|
1003
|
-
distTags: resolved.distTags,
|
|
1004
|
-
body: optimized,
|
|
1005
|
-
relatedSkills,
|
|
1006
|
-
hasIssues,
|
|
1007
|
-
hasDiscussions,
|
|
1008
|
-
hasReleases,
|
|
1009
|
-
hasChangelog,
|
|
1010
|
-
docsType,
|
|
1011
|
-
hasShippedDocs: shippedDocs,
|
|
1012
|
-
pkgFiles,
|
|
1013
|
-
generatedBy: getModelLabel(model),
|
|
1014
|
-
dirName,
|
|
1015
|
-
packages,
|
|
1016
|
-
repoUrl: resolved.repoUrl,
|
|
1017
|
-
features,
|
|
1018
|
-
eject
|
|
1019
|
-
});
|
|
1020
|
-
writeFileSync(join(skillDir, "SKILL.md"), skillMd);
|
|
1021
|
-
} else llmLog.error(`Enhancement failed${error ? `: ${error}` : ""}`);
|
|
1022
|
-
}
|
|
1023
|
-
/**
|
|
1024
|
-
* Build and write PROMPT_*.md files for manual LLM use.
|
|
1025
|
-
* Returns the list of sections that had prompts written.
|
|
1026
|
-
*/
|
|
1027
|
-
function writePromptFiles(opts) {
|
|
1028
|
-
const { skillDir, sections, customPrompt, features } = opts;
|
|
1029
|
-
const docFiles = listReferenceFiles(skillDir);
|
|
1030
|
-
const prompts = buildAllSectionPrompts({
|
|
1031
|
-
packageName: opts.packageName,
|
|
1032
|
-
skillDir,
|
|
1033
|
-
version: opts.version,
|
|
1034
|
-
hasIssues: opts.hasIssues,
|
|
1035
|
-
hasDiscussions: opts.hasDiscussions,
|
|
1036
|
-
hasReleases: opts.hasReleases,
|
|
1037
|
-
hasChangelog: opts.hasChangelog,
|
|
1038
|
-
docFiles,
|
|
1039
|
-
docsType: opts.docsType,
|
|
1040
|
-
hasShippedDocs: opts.hasShippedDocs,
|
|
1041
|
-
pkgFiles: opts.pkgFiles,
|
|
1042
|
-
customPrompt,
|
|
1043
|
-
features,
|
|
1044
|
-
sections
|
|
1045
|
-
});
|
|
1046
|
-
const skilldDir = join(skillDir, ".skilld");
|
|
1047
|
-
mkdirSync(skilldDir, { recursive: true });
|
|
1048
|
-
for (const [section, prompt] of prompts) writeFileSync(join(skilldDir, `PROMPT_${section}.md`), prompt);
|
|
1049
|
-
const written = [...prompts.keys()];
|
|
1050
|
-
if (written.length > 0) {
|
|
1051
|
-
const relDir = relative(process.cwd(), skillDir);
|
|
1052
|
-
const promptFiles = written.map((s) => `PROMPT_${s}.md`).join(", ");
|
|
1053
|
-
const outputFileList = written.map((s) => SECTION_OUTPUT_FILES[s]).join(", ");
|
|
1054
|
-
p.log.info(`Prompt files written to ${relDir}/.skilld/\n\x1B[2m\x1B[3m 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\x1B[0m`);
|
|
1055
|
-
}
|
|
1056
|
-
return written;
|
|
1057
|
-
}
|
|
1058
|
-
//#endregion
|
|
1059
25
|
//#region src/telemetry.ts
|
|
1060
26
|
/**
|
|
1061
27
|
* Anonymous telemetry — fire-and-forget GET to add-skill.vercel.sh/t
|
|
@@ -1419,7 +385,6 @@ async function syncPackagesParallel(config) {
|
|
|
1419
385
|
name: resolvedName,
|
|
1420
386
|
version: data.version,
|
|
1421
387
|
releasedAt: data.resolved.releasedAt,
|
|
1422
|
-
dependencies: data.resolved.dependencies,
|
|
1423
388
|
distTags: data.resolved.distTags,
|
|
1424
389
|
body: cachedBody,
|
|
1425
390
|
relatedSkills: data.relatedSkills,
|
|
@@ -1499,7 +464,8 @@ async function syncPackagesParallel(config) {
|
|
|
1499
464
|
pkgFiles: data.pkgFiles,
|
|
1500
465
|
sections: llmConfig.sections,
|
|
1501
466
|
customPrompt: llmConfig.customPrompt,
|
|
1502
|
-
features: data.features
|
|
467
|
+
features: data.features,
|
|
468
|
+
overheadLines: data.overheadLines
|
|
1503
469
|
});
|
|
1504
470
|
}
|
|
1505
471
|
else if (llmConfig) {
|
|
@@ -1523,12 +489,18 @@ async function syncPackagesParallel(config) {
|
|
|
1523
489
|
await ensureAgentInstructions(config.agent, cwd, config.global);
|
|
1524
490
|
await shutdownWorker();
|
|
1525
491
|
p.outro(`${pastVerb} ${successfulPkgs.length}/${packages.length} packages`);
|
|
492
|
+
const { suggestPrepareHook } = await import("./cli-helpers2.mjs");
|
|
493
|
+
try {
|
|
494
|
+
await suggestPrepareHook(cwd);
|
|
495
|
+
} catch (err) {
|
|
496
|
+
p.log.warn(`Failed to suggest prepare hook: ${err instanceof Error ? err.message : String(err)}`);
|
|
497
|
+
}
|
|
1526
498
|
}
|
|
1527
499
|
/** Phase 1: Generate base skill (no LLM). Returns 'shipped' if shipped skill was linked, or BaseSkillData. */
|
|
1528
500
|
async function syncBaseSkill(packageSpec, config, cwd, update) {
|
|
1529
501
|
const { name: packageName, tag: requestedTag } = parsePackageSpec(packageSpec);
|
|
1530
502
|
const localVersion = (await readLocalDependencies(cwd).catch(() => [])).find((d) => d.name === packageName)?.version;
|
|
1531
|
-
const { package: resolvedPkg, attempts } = await resolvePackageDocsWithAttempts(requestedTag ? packageSpec : packageName, {
|
|
503
|
+
const { package: resolvedPkg, attempts, registryVersion } = await resolvePackageDocsWithAttempts(requestedTag ? packageSpec : packageName, {
|
|
1532
504
|
version: localVersion,
|
|
1533
505
|
cwd,
|
|
1534
506
|
onProgress: (step) => update(packageName, "resolving", RESOLVE_STEP_LABELS[step])
|
|
@@ -1539,6 +511,14 @@ async function syncBaseSkill(packageSpec, config, cwd, update) {
|
|
|
1539
511
|
resolved = await resolveLocalDep(packageName, cwd);
|
|
1540
512
|
}
|
|
1541
513
|
if (!resolved) {
|
|
514
|
+
const shippedVersion = localVersion || registryVersion || "latest";
|
|
515
|
+
const earlyShipped = handleShippedSkills(packageName, shippedVersion, cwd, config.agent, config.global);
|
|
516
|
+
if (earlyShipped) {
|
|
517
|
+
const shared = !config.global && getSharedSkillsDir(cwd);
|
|
518
|
+
if (shared) for (const shipped of earlyShipped.shipped) linkSkillToAgents(shipped.skillName, shared, cwd, config.agent);
|
|
519
|
+
update(packageName, "done", "Published SKILL.md", getVersionKey(shippedVersion));
|
|
520
|
+
return "shipped";
|
|
521
|
+
}
|
|
1542
522
|
const npmAttempt = attempts.find((a) => a.source === "npm");
|
|
1543
523
|
let reason;
|
|
1544
524
|
if (npmAttempt?.status === "not-found") {
|
|
@@ -1629,7 +609,6 @@ async function syncBaseSkill(packageSpec, config, cwd, update) {
|
|
|
1629
609
|
version,
|
|
1630
610
|
releasedAt: resolved.releasedAt,
|
|
1631
611
|
description: resolved.description,
|
|
1632
|
-
dependencies: resolved.dependencies,
|
|
1633
612
|
distTags: resolved.distTags,
|
|
1634
613
|
relatedSkills,
|
|
1635
614
|
hasIssues: resources.hasIssues,
|
|
@@ -1645,6 +624,7 @@ async function syncBaseSkill(packageSpec, config, cwd, update) {
|
|
|
1645
624
|
features
|
|
1646
625
|
});
|
|
1647
626
|
writeFileSync(join(skillDir, "SKILL.md"), skillMd);
|
|
627
|
+
const overheadLines = skillMd.split("\n").length;
|
|
1648
628
|
const shared = !config.global && getSharedSkillsDir(cwd);
|
|
1649
629
|
if (shared) linkSkillToAgents(skillDirName, shared, cwd, config.agent);
|
|
1650
630
|
if (!config.global) registerProject(cwd);
|
|
@@ -1667,7 +647,8 @@ async function syncBaseSkill(packageSpec, config, cwd, update) {
|
|
|
1667
647
|
usedCache: resources.usedCache,
|
|
1668
648
|
oldVersion: preLock?.version,
|
|
1669
649
|
oldSyncedAt: preLock?.syncedAt,
|
|
1670
|
-
wasEnhanced: preEnhanced
|
|
650
|
+
wasEnhanced: preEnhanced,
|
|
651
|
+
overheadLines
|
|
1671
652
|
};
|
|
1672
653
|
}
|
|
1673
654
|
/** Phase 2: Enhance skill with LLM */
|
|
@@ -1694,6 +675,7 @@ async function enhanceWithLLM(packageName, data, config, cwd, update, sections,
|
|
|
1694
675
|
customPrompt,
|
|
1695
676
|
features: data.features,
|
|
1696
677
|
pkgFiles: data.pkgFiles,
|
|
678
|
+
overheadLines: data.overheadLines,
|
|
1697
679
|
onProgress: (progress) => {
|
|
1698
680
|
const status = progress.type === "reasoning" ? "exploring" : "generating";
|
|
1699
681
|
const sectionPrefix = progress.section ? `[${progress.section}] ` : "";
|
|
@@ -1709,7 +691,6 @@ async function enhanceWithLLM(packageName, data, config, cwd, update, sections,
|
|
|
1709
691
|
name: packageName,
|
|
1710
692
|
version: data.version,
|
|
1711
693
|
releasedAt: data.resolved.releasedAt,
|
|
1712
|
-
dependencies: data.resolved.dependencies,
|
|
1713
694
|
distTags: data.resolved.distTags,
|
|
1714
695
|
body: optimized,
|
|
1715
696
|
relatedSkills: data.relatedSkills,
|
|
@@ -1856,6 +837,16 @@ async function syncSinglePackage(packageSpec, config) {
|
|
|
1856
837
|
resolved = await resolveLocalDep(packageName, cwd);
|
|
1857
838
|
}
|
|
1858
839
|
if (!resolved) {
|
|
840
|
+
const earlyShipped = handleShippedSkills(packageName, localVersion || resolveResult.registryVersion || "latest", cwd, config.agent, config.global);
|
|
841
|
+
if (earlyShipped) {
|
|
842
|
+
const shared = !config.global && getSharedSkillsDir(cwd);
|
|
843
|
+
for (const shipped of earlyShipped.shipped) {
|
|
844
|
+
if (shared) linkSkillToAgents(shipped.skillName, shared, cwd, config.agent);
|
|
845
|
+
p.log.success(`Using published SKILL.md: ${shipped.skillName} → ${relative(cwd, shipped.skillDir)}`);
|
|
846
|
+
}
|
|
847
|
+
spin.stop(`Using published SKILL.md(s) from ${packageName}`);
|
|
848
|
+
return;
|
|
849
|
+
}
|
|
1859
850
|
spin.message(`Searching npm for "${packageName}"...`);
|
|
1860
851
|
const suggestions = await searchNpmPackages(packageName);
|
|
1861
852
|
if (suggestions.length > 0) {
|
|
@@ -2010,14 +1001,25 @@ async function syncSinglePackage(packageSpec, config) {
|
|
|
2010
1001
|
const pkgFiles = getPkgKeyFiles(packageName, cwd, version);
|
|
2011
1002
|
const repoSlug = resolved.repoUrl?.match(/github\.com\/([^/]+\/[^/]+?)(?:\.git)?(?:[/#]|$)/)?.[1];
|
|
2012
1003
|
if (!config.eject) linkPkgNamed(skillDir, packageName, cwd, version);
|
|
2013
|
-
if (!config.eject)
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
1004
|
+
if (!config.eject) {
|
|
1005
|
+
writeLock(baseDir, skillDirName, {
|
|
1006
|
+
packageName,
|
|
1007
|
+
version,
|
|
1008
|
+
repo: repoSlug,
|
|
1009
|
+
source: resources.docSource,
|
|
1010
|
+
syncedAt: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
|
|
1011
|
+
generator: "skilld"
|
|
1012
|
+
});
|
|
1013
|
+
const lock = readLock(baseDir);
|
|
1014
|
+
if (lock) for (const [name, info] of Object.entries(lock.skills)) {
|
|
1015
|
+
if (name === skillDirName) continue;
|
|
1016
|
+
if (info.packageName === packageName || parsePackages(info.packages).some((p) => p.name === packageName)) {
|
|
1017
|
+
removeLockEntry(baseDir, name);
|
|
1018
|
+
const staleDir = join(baseDir, name);
|
|
1019
|
+
if (existsSync(staleDir)) rmSync(staleDir, { recursive: true });
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
2021
1023
|
const allPackages = parsePackages((config.eject ? void 0 : readLock(baseDir)?.skills[skillDirName])?.packages).map((p) => ({ name: p.name }));
|
|
2022
1024
|
const isEject = !!config.eject;
|
|
2023
1025
|
const baseSkillMd = generateSkillMd({
|
|
@@ -2025,7 +1027,6 @@ async function syncSinglePackage(packageSpec, config) {
|
|
|
2025
1027
|
version,
|
|
2026
1028
|
releasedAt: resolved.releasedAt,
|
|
2027
1029
|
description: resolved.description,
|
|
2028
|
-
dependencies: resolved.dependencies,
|
|
2029
1030
|
distTags: resolved.distTags,
|
|
2030
1031
|
relatedSkills,
|
|
2031
1032
|
hasIssues: resources.hasIssues,
|
|
@@ -2042,6 +1043,7 @@ async function syncSinglePackage(packageSpec, config) {
|
|
|
2042
1043
|
eject: isEject
|
|
2043
1044
|
});
|
|
2044
1045
|
writeFileSync(join(skillDir, "SKILL.md"), baseSkillMd);
|
|
1046
|
+
const overheadLines = baseSkillMd.split("\n").length;
|
|
2045
1047
|
p.log.success(config.mode === "update" ? `Updated skill: ${relative(cwd, skillDir)}` : `Created base skill: ${relative(cwd, skillDir)}`);
|
|
2046
1048
|
const allSectionsCached = !config.force && DEFAULT_SECTIONS.every((s) => {
|
|
2047
1049
|
const outputFile = SECTION_OUTPUT_FILES[s];
|
|
@@ -2061,7 +1063,6 @@ async function syncSinglePackage(packageSpec, config) {
|
|
|
2061
1063
|
version,
|
|
2062
1064
|
releasedAt: resolved.releasedAt,
|
|
2063
1065
|
description: resolved.description,
|
|
2064
|
-
dependencies: resolved.dependencies,
|
|
2065
1066
|
distTags: resolved.distTags,
|
|
2066
1067
|
body: cachedBody,
|
|
2067
1068
|
relatedSkills,
|
|
@@ -2104,7 +1105,8 @@ async function syncSinglePackage(packageSpec, config) {
|
|
|
2104
1105
|
pkgFiles,
|
|
2105
1106
|
sections: llmConfig.sections,
|
|
2106
1107
|
customPrompt: llmConfig.customPrompt,
|
|
2107
|
-
features
|
|
1108
|
+
features,
|
|
1109
|
+
overheadLines
|
|
2108
1110
|
});
|
|
2109
1111
|
else if (llmConfig) {
|
|
2110
1112
|
p.log.step(getModelLabel(llmConfig.model));
|
|
@@ -2129,7 +1131,8 @@ async function syncSinglePackage(packageSpec, config) {
|
|
|
2129
1131
|
customPrompt: llmConfig.customPrompt,
|
|
2130
1132
|
packages: allPackages.length > 1 ? allPackages : void 0,
|
|
2131
1133
|
features,
|
|
2132
|
-
eject: isEject
|
|
1134
|
+
eject: isEject,
|
|
1135
|
+
overheadLines
|
|
2133
1136
|
});
|
|
2134
1137
|
}
|
|
2135
1138
|
}
|
|
@@ -2152,6 +1155,11 @@ async function syncSinglePackage(packageSpec, config) {
|
|
|
2152
1155
|
const ejectMsg = isEject ? " (ejected)" : "";
|
|
2153
1156
|
const relDir = relative(cwd, skillDir);
|
|
2154
1157
|
p.outro(config.mode === "update" ? `Updated ${packageName}${ejectMsg}` : `Synced ${packageName} → ${relDir}${ejectMsg}`);
|
|
1158
|
+
try {
|
|
1159
|
+
await suggestPrepareHook(cwd);
|
|
1160
|
+
} catch (err) {
|
|
1161
|
+
p.log.warn(`Failed to suggest prepare hook: ${err instanceof Error ? err.message : String(err)}`);
|
|
1162
|
+
}
|
|
2155
1163
|
}
|
|
2156
1164
|
const addCommandDef = defineCommand({
|
|
2157
1165
|
meta: {
|
|
@@ -2464,7 +1472,6 @@ async function exportPortablePrompts(packageSpec, opts) {
|
|
|
2464
1472
|
version,
|
|
2465
1473
|
releasedAt: resolved.releasedAt,
|
|
2466
1474
|
description: resolved.description,
|
|
2467
|
-
dependencies: resolved.dependencies,
|
|
2468
1475
|
distTags: resolved.distTags,
|
|
2469
1476
|
relatedSkills,
|
|
2470
1477
|
hasIssues: resources.hasIssues,
|
|
@@ -2503,6 +1510,6 @@ async function exportPortablePrompts(packageSpec, opts) {
|
|
|
2503
1510
|
p.log.info(`Have your agent enhance the skill. Give it this prompt:\n\x1B[2m\x1B[3m Read each prompt file (${promptFiles}) in ${relDir}/, read the\n referenced files, then write your output to the matching file (${outputFileList}).\n When done, run: skilld assemble\x1B[0m`);
|
|
2504
1511
|
}
|
|
2505
1512
|
//#endregion
|
|
2506
|
-
export {
|
|
1513
|
+
export { updateCommandDef as a, syncCommand as i, ejectCommandDef as n, exportPortablePrompts as r, addCommandDef as t };
|
|
2507
1514
|
|
|
2508
1515
|
//# sourceMappingURL=sync.mjs.map
|