rulesync 7.15.1 → 7.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/{chunk-L5AQUWUM.js → chunk-E5YWRHGW.js} +146 -40
- package/dist/cli/index.cjs +313 -174
- package/dist/cli/index.js +173 -138
- package/dist/index.cjs +144 -41
- package/dist/index.js +1 -1
- package/package.json +11 -11
package/dist/cli/index.js
CHANGED
|
@@ -47,6 +47,7 @@ import {
|
|
|
47
47
|
directoryExists,
|
|
48
48
|
ensureDir,
|
|
49
49
|
fileExists,
|
|
50
|
+
findControlCharacter,
|
|
50
51
|
findFilesByGlobs,
|
|
51
52
|
formatError,
|
|
52
53
|
generate,
|
|
@@ -62,7 +63,7 @@ import {
|
|
|
62
63
|
removeTempDirectory,
|
|
63
64
|
stringifyFrontmatter,
|
|
64
65
|
writeFileContent
|
|
65
|
-
} from "../chunk-
|
|
66
|
+
} from "../chunk-E5YWRHGW.js";
|
|
66
67
|
|
|
67
68
|
// src/cli/index.ts
|
|
68
69
|
import { Command } from "commander";
|
|
@@ -1618,21 +1619,12 @@ import { Semaphore as Semaphore2 } from "es-toolkit/promise";
|
|
|
1618
1619
|
|
|
1619
1620
|
// src/lib/git-client.ts
|
|
1620
1621
|
import { execFile } from "child_process";
|
|
1621
|
-
import { join as join4 } from "path";
|
|
1622
|
+
import { isAbsolute, join as join4, relative } from "path";
|
|
1622
1623
|
import { promisify } from "util";
|
|
1623
1624
|
var execFileAsync = promisify(execFile);
|
|
1624
1625
|
var GIT_TIMEOUT_MS = 6e4;
|
|
1625
|
-
var ALLOWED_URL_SCHEMES = /^(https?:\/\/|ssh:\/\/|git:\/\/|file
|
|
1626
|
+
var ALLOWED_URL_SCHEMES = /^(https?:\/\/|ssh:\/\/|git:\/\/|file:\/\/\/).+$|^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9.-]+:[a-zA-Z0-9_.+/~-]+$/;
|
|
1626
1627
|
var INSECURE_URL_SCHEMES = /^(git:\/\/|http:\/\/)/;
|
|
1627
|
-
function findControlCharacter(value) {
|
|
1628
|
-
for (let i = 0; i < value.length; i++) {
|
|
1629
|
-
const code = value.charCodeAt(i);
|
|
1630
|
-
if (code >= 0 && code <= 31 || code === 127) {
|
|
1631
|
-
return { position: i, hex: `0x${code.toString(16).padStart(2, "0")}` };
|
|
1632
|
-
}
|
|
1633
|
-
}
|
|
1634
|
-
return null;
|
|
1635
|
-
}
|
|
1636
1628
|
var GitClientError = class extends Error {
|
|
1637
1629
|
constructor(message, cause) {
|
|
1638
1630
|
super(message, { cause });
|
|
@@ -1688,6 +1680,7 @@ async function resolveDefaultRef(url) {
|
|
|
1688
1680
|
const ref = stdout.match(/^ref: refs\/heads\/(.+)\tHEAD$/m)?.[1];
|
|
1689
1681
|
const sha = stdout.match(/^([0-9a-f]{40})\tHEAD$/m)?.[1];
|
|
1690
1682
|
if (!ref || !sha) throw new GitClientError(`Could not parse default branch from: ${url}`);
|
|
1683
|
+
validateRef(ref);
|
|
1691
1684
|
return { ref, sha };
|
|
1692
1685
|
} catch (error) {
|
|
1693
1686
|
if (error instanceof GitClientError) throw error;
|
|
@@ -1714,6 +1707,17 @@ async function fetchSkillFiles(params) {
|
|
|
1714
1707
|
const { url, ref, skillsPath } = params;
|
|
1715
1708
|
validateGitUrl(url);
|
|
1716
1709
|
validateRef(ref);
|
|
1710
|
+
if (skillsPath.split(/[/\\]/).includes("..") || isAbsolute(skillsPath)) {
|
|
1711
|
+
throw new GitClientError(
|
|
1712
|
+
`Invalid skillsPath "${skillsPath}": must be a relative path without ".."`
|
|
1713
|
+
);
|
|
1714
|
+
}
|
|
1715
|
+
const ctrl = findControlCharacter(skillsPath);
|
|
1716
|
+
if (ctrl) {
|
|
1717
|
+
throw new GitClientError(
|
|
1718
|
+
`skillsPath contains control character ${ctrl.hex} at position ${ctrl.position}`
|
|
1719
|
+
);
|
|
1720
|
+
}
|
|
1717
1721
|
await checkGitAvailable();
|
|
1718
1722
|
const tmpDir = await createTempDirectory("rulesync-git-");
|
|
1719
1723
|
try {
|
|
@@ -1748,7 +1752,9 @@ async function fetchSkillFiles(params) {
|
|
|
1748
1752
|
}
|
|
1749
1753
|
}
|
|
1750
1754
|
var MAX_WALK_DEPTH = 20;
|
|
1751
|
-
|
|
1755
|
+
var MAX_TOTAL_FILES = 1e4;
|
|
1756
|
+
var MAX_TOTAL_SIZE = 100 * 1024 * 1024;
|
|
1757
|
+
async function walkDirectory(dir, baseDir, depth = 0, ctx = { totalFiles: 0, totalSize: 0 }) {
|
|
1752
1758
|
if (depth > MAX_WALK_DEPTH) {
|
|
1753
1759
|
throw new GitClientError(
|
|
1754
1760
|
`Directory tree exceeds max depth of ${MAX_WALK_DEPTH}: "${dir}". Aborting to prevent resource exhaustion.`
|
|
@@ -1763,7 +1769,7 @@ async function walkDirectory(dir, baseDir, depth = 0) {
|
|
|
1763
1769
|
continue;
|
|
1764
1770
|
}
|
|
1765
1771
|
if (await directoryExists(fullPath)) {
|
|
1766
|
-
results.push(...await walkDirectory(fullPath, baseDir, depth + 1));
|
|
1772
|
+
results.push(...await walkDirectory(fullPath, baseDir, depth + 1, ctx));
|
|
1767
1773
|
} else {
|
|
1768
1774
|
const size = await getFileSize(fullPath);
|
|
1769
1775
|
if (size > MAX_FILE_SIZE) {
|
|
@@ -1772,8 +1778,20 @@ async function walkDirectory(dir, baseDir, depth = 0) {
|
|
|
1772
1778
|
);
|
|
1773
1779
|
continue;
|
|
1774
1780
|
}
|
|
1781
|
+
ctx.totalFiles++;
|
|
1782
|
+
ctx.totalSize += size;
|
|
1783
|
+
if (ctx.totalFiles >= MAX_TOTAL_FILES) {
|
|
1784
|
+
throw new GitClientError(
|
|
1785
|
+
`Repository exceeds max file count of ${MAX_TOTAL_FILES}. Aborting to prevent resource exhaustion.`
|
|
1786
|
+
);
|
|
1787
|
+
}
|
|
1788
|
+
if (ctx.totalSize >= MAX_TOTAL_SIZE) {
|
|
1789
|
+
throw new GitClientError(
|
|
1790
|
+
`Repository exceeds max total size of ${MAX_TOTAL_SIZE / 1024 / 1024}MB. Aborting to prevent resource exhaustion.`
|
|
1791
|
+
);
|
|
1792
|
+
}
|
|
1775
1793
|
const content = await readFileContent(fullPath);
|
|
1776
|
-
results.push({ relativePath:
|
|
1794
|
+
results.push({ relativePath: relative(baseDir, fullPath), content, size });
|
|
1777
1795
|
}
|
|
1778
1796
|
}
|
|
1779
1797
|
return results;
|
|
@@ -1782,14 +1800,14 @@ async function walkDirectory(dir, baseDir, depth = 0) {
|
|
|
1782
1800
|
// src/lib/sources-lock.ts
|
|
1783
1801
|
import { createHash } from "crypto";
|
|
1784
1802
|
import { join as join5 } from "path";
|
|
1785
|
-
import { optional, z as z4 } from "zod/mini";
|
|
1803
|
+
import { optional, refine, z as z4 } from "zod/mini";
|
|
1786
1804
|
var LOCKFILE_VERSION = 1;
|
|
1787
1805
|
var LockedSkillSchema = z4.object({
|
|
1788
1806
|
integrity: z4.string()
|
|
1789
1807
|
});
|
|
1790
1808
|
var LockedSourceSchema = z4.object({
|
|
1791
1809
|
requestedRef: optional(z4.string()),
|
|
1792
|
-
resolvedRef: z4.string(),
|
|
1810
|
+
resolvedRef: z4.string().check(refine((v) => /^[0-9a-f]{40}$/.test(v), "resolvedRef must be a 40-character hex SHA")),
|
|
1793
1811
|
resolvedAt: optional(z4.string()),
|
|
1794
1812
|
skills: z4.record(z4.string(), LockedSkillSchema)
|
|
1795
1813
|
});
|
|
@@ -1992,6 +2010,8 @@ async function resolveAndFetchSources(params) {
|
|
|
1992
2010
|
logger.error(`Failed to fetch source "${sourceEntry.source}": ${formatError(error)}`);
|
|
1993
2011
|
if (error instanceof GitHubClientError) {
|
|
1994
2012
|
logGitHubAuthHints(error);
|
|
2013
|
+
} else if (error instanceof GitClientError) {
|
|
2014
|
+
logGitClientHints(error);
|
|
1995
2015
|
}
|
|
1996
2016
|
}
|
|
1997
2017
|
}
|
|
@@ -2012,6 +2032,13 @@ async function resolveAndFetchSources(params) {
|
|
|
2012
2032
|
}
|
|
2013
2033
|
return { fetchedSkillCount: totalSkillCount, sourcesProcessed: sources.length };
|
|
2014
2034
|
}
|
|
2035
|
+
function logGitClientHints(error) {
|
|
2036
|
+
if (error.message.includes("not installed")) {
|
|
2037
|
+
logger.info("Hint: Install git and ensure it is available on your PATH.");
|
|
2038
|
+
} else {
|
|
2039
|
+
logger.info("Hint: Check your git credentials (SSH keys, credential helper, or access token).");
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
2015
2042
|
async function checkLockedSkillsExist(curatedDir, skillNames) {
|
|
2016
2043
|
if (skillNames.length === 0) return true;
|
|
2017
2044
|
for (const name of skillNames) {
|
|
@@ -2021,9 +2048,88 @@ async function checkLockedSkillsExist(curatedDir, skillNames) {
|
|
|
2021
2048
|
}
|
|
2022
2049
|
return true;
|
|
2023
2050
|
}
|
|
2051
|
+
async function cleanPreviousCuratedSkills(curatedDir, lockedSkillNames) {
|
|
2052
|
+
const resolvedCuratedDir = resolve(curatedDir);
|
|
2053
|
+
for (const prevSkill of lockedSkillNames) {
|
|
2054
|
+
const prevDir = join6(curatedDir, prevSkill);
|
|
2055
|
+
if (!resolve(prevDir).startsWith(resolvedCuratedDir + sep)) {
|
|
2056
|
+
logger.warn(
|
|
2057
|
+
`Skipping removal of "${prevSkill}": resolved path is outside the curated directory.`
|
|
2058
|
+
);
|
|
2059
|
+
continue;
|
|
2060
|
+
}
|
|
2061
|
+
if (await directoryExists(prevDir)) {
|
|
2062
|
+
await removeDirectory(prevDir);
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
2065
|
+
}
|
|
2066
|
+
function shouldSkipSkill(params) {
|
|
2067
|
+
const { skillName, sourceKey, localSkillNames, alreadyFetchedSkillNames } = params;
|
|
2068
|
+
if (skillName.includes("..") || skillName.includes("/") || skillName.includes("\\")) {
|
|
2069
|
+
logger.warn(
|
|
2070
|
+
`Skipping skill with invalid name "${skillName}" from ${sourceKey}: contains path traversal characters.`
|
|
2071
|
+
);
|
|
2072
|
+
return true;
|
|
2073
|
+
}
|
|
2074
|
+
if (localSkillNames.has(skillName)) {
|
|
2075
|
+
logger.debug(
|
|
2076
|
+
`Skipping remote skill "${skillName}" from ${sourceKey}: local skill takes precedence.`
|
|
2077
|
+
);
|
|
2078
|
+
return true;
|
|
2079
|
+
}
|
|
2080
|
+
if (alreadyFetchedSkillNames.has(skillName)) {
|
|
2081
|
+
logger.warn(
|
|
2082
|
+
`Skipping duplicate skill "${skillName}" from ${sourceKey}: already fetched from another source.`
|
|
2083
|
+
);
|
|
2084
|
+
return true;
|
|
2085
|
+
}
|
|
2086
|
+
return false;
|
|
2087
|
+
}
|
|
2088
|
+
async function writeSkillAndComputeIntegrity(params) {
|
|
2089
|
+
const { skillName, files, curatedDir, locked, resolvedSha, sourceKey } = params;
|
|
2090
|
+
const written = [];
|
|
2091
|
+
for (const file of files) {
|
|
2092
|
+
checkPathTraversal({
|
|
2093
|
+
relativePath: file.relativePath,
|
|
2094
|
+
intendedRootDir: join6(curatedDir, skillName)
|
|
2095
|
+
});
|
|
2096
|
+
await writeFileContent(join6(curatedDir, skillName, file.relativePath), file.content);
|
|
2097
|
+
written.push({ path: file.relativePath, content: file.content });
|
|
2098
|
+
}
|
|
2099
|
+
const integrity = computeSkillIntegrity(written);
|
|
2100
|
+
const lockedSkillEntry = locked?.skills[skillName];
|
|
2101
|
+
if (lockedSkillEntry?.integrity && lockedSkillEntry.integrity !== integrity && resolvedSha === locked?.resolvedRef) {
|
|
2102
|
+
logger.warn(
|
|
2103
|
+
`Integrity mismatch for skill "${skillName}" from ${sourceKey}: expected "${lockedSkillEntry.integrity}", got "${integrity}". Content may have been tampered with.`
|
|
2104
|
+
);
|
|
2105
|
+
}
|
|
2106
|
+
return { integrity };
|
|
2107
|
+
}
|
|
2108
|
+
function buildLockUpdate(params) {
|
|
2109
|
+
const { lock, sourceKey, fetchedSkills, locked, requestedRef, resolvedSha } = params;
|
|
2110
|
+
const fetchedNames = Object.keys(fetchedSkills);
|
|
2111
|
+
const mergedSkills = { ...fetchedSkills };
|
|
2112
|
+
if (locked) {
|
|
2113
|
+
for (const [skillName, skillEntry] of Object.entries(locked.skills)) {
|
|
2114
|
+
if (!(skillName in mergedSkills)) {
|
|
2115
|
+
mergedSkills[skillName] = skillEntry;
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
const updatedLock = setLockedSource(lock, sourceKey, {
|
|
2120
|
+
requestedRef,
|
|
2121
|
+
resolvedRef: resolvedSha,
|
|
2122
|
+
resolvedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2123
|
+
skills: mergedSkills
|
|
2124
|
+
});
|
|
2125
|
+
logger.info(
|
|
2126
|
+
`Fetched ${fetchedNames.length} skill(s) from ${sourceKey}: ${fetchedNames.join(", ") || "(none)"}`
|
|
2127
|
+
);
|
|
2128
|
+
return { updatedLock, fetchedNames };
|
|
2129
|
+
}
|
|
2024
2130
|
async function fetchSource(params) {
|
|
2025
2131
|
const { sourceEntry, client, baseDir, localSkillNames, alreadyFetchedSkillNames, updateSources } = params;
|
|
2026
|
-
|
|
2132
|
+
const { lock } = params;
|
|
2027
2133
|
const parsed = parseSource(sourceEntry.source);
|
|
2028
2134
|
if (parsed.provider === "gitlab") {
|
|
2029
2135
|
logger.warn(`GitLab sources are not yet supported. Skipping "${sourceEntry.source}".`);
|
|
@@ -2076,37 +2182,15 @@ async function fetchSource(params) {
|
|
|
2076
2182
|
const semaphore = new Semaphore2(FETCH_CONCURRENCY_LIMIT);
|
|
2077
2183
|
const fetchedSkills = {};
|
|
2078
2184
|
if (locked) {
|
|
2079
|
-
|
|
2080
|
-
for (const prevSkill of lockedSkillNames) {
|
|
2081
|
-
const prevDir = join6(curatedDir, prevSkill);
|
|
2082
|
-
if (!resolve(prevDir).startsWith(resolvedCuratedDir + sep)) {
|
|
2083
|
-
logger.warn(
|
|
2084
|
-
`Skipping removal of "${prevSkill}": resolved path is outside the curated directory.`
|
|
2085
|
-
);
|
|
2086
|
-
continue;
|
|
2087
|
-
}
|
|
2088
|
-
if (await directoryExists(prevDir)) {
|
|
2089
|
-
await removeDirectory(prevDir);
|
|
2090
|
-
}
|
|
2091
|
-
}
|
|
2185
|
+
await cleanPreviousCuratedSkills(curatedDir, lockedSkillNames);
|
|
2092
2186
|
}
|
|
2093
2187
|
for (const skillDir of filteredDirs) {
|
|
2094
|
-
if (
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
}
|
|
2100
|
-
if (localSkillNames.has(skillDir.name)) {
|
|
2101
|
-
logger.debug(
|
|
2102
|
-
`Skipping remote skill "${skillDir.name}" from ${sourceKey}: local skill takes precedence.`
|
|
2103
|
-
);
|
|
2104
|
-
continue;
|
|
2105
|
-
}
|
|
2106
|
-
if (alreadyFetchedSkillNames.has(skillDir.name)) {
|
|
2107
|
-
logger.warn(
|
|
2108
|
-
`Skipping duplicate skill "${skillDir.name}" from ${sourceKey}: already fetched from another source.`
|
|
2109
|
-
);
|
|
2188
|
+
if (shouldSkipSkill({
|
|
2189
|
+
skillName: skillDir.name,
|
|
2190
|
+
sourceKey,
|
|
2191
|
+
localSkillNames,
|
|
2192
|
+
alreadyFetchedSkillNames
|
|
2193
|
+
})) {
|
|
2110
2194
|
continue;
|
|
2111
2195
|
}
|
|
2112
2196
|
const allFiles = await listDirectoryRecursive({
|
|
@@ -2129,55 +2213,39 @@ async function fetchSource(params) {
|
|
|
2129
2213
|
const skillFiles = [];
|
|
2130
2214
|
for (const file of files) {
|
|
2131
2215
|
const relativeToSkill = file.path.substring(skillDir.path.length + 1);
|
|
2132
|
-
const localFilePath = join6(curatedDir, skillDir.name, relativeToSkill);
|
|
2133
|
-
checkPathTraversal({
|
|
2134
|
-
relativePath: relativeToSkill,
|
|
2135
|
-
intendedRootDir: join6(curatedDir, skillDir.name)
|
|
2136
|
-
});
|
|
2137
2216
|
const content = await withSemaphore(
|
|
2138
2217
|
semaphore,
|
|
2139
2218
|
() => client.getFileContent(parsed.owner, parsed.repo, file.path, ref)
|
|
2140
2219
|
);
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
}
|
|
2151
|
-
fetchedSkills[skillDir.name] = { integrity };
|
|
2220
|
+
skillFiles.push({ relativePath: relativeToSkill, content });
|
|
2221
|
+
}
|
|
2222
|
+
fetchedSkills[skillDir.name] = await writeSkillAndComputeIntegrity({
|
|
2223
|
+
skillName: skillDir.name,
|
|
2224
|
+
files: skillFiles,
|
|
2225
|
+
curatedDir,
|
|
2226
|
+
locked,
|
|
2227
|
+
resolvedSha,
|
|
2228
|
+
sourceKey
|
|
2229
|
+
});
|
|
2152
2230
|
logger.debug(`Fetched skill "${skillDir.name}" from ${sourceKey}`);
|
|
2153
2231
|
}
|
|
2154
|
-
const
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
mergedSkills[skillName] = skillEntry;
|
|
2160
|
-
}
|
|
2161
|
-
}
|
|
2162
|
-
}
|
|
2163
|
-
lock = setLockedSource(lock, sourceKey, {
|
|
2232
|
+
const result = buildLockUpdate({
|
|
2233
|
+
lock,
|
|
2234
|
+
sourceKey,
|
|
2235
|
+
fetchedSkills,
|
|
2236
|
+
locked,
|
|
2164
2237
|
requestedRef,
|
|
2165
|
-
|
|
2166
|
-
resolvedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2167
|
-
skills: mergedSkills
|
|
2238
|
+
resolvedSha
|
|
2168
2239
|
});
|
|
2169
|
-
logger.info(
|
|
2170
|
-
`Fetched ${fetchedNames.length} skill(s) from ${sourceKey}: ${fetchedNames.join(", ") || "(none)"}`
|
|
2171
|
-
);
|
|
2172
2240
|
return {
|
|
2173
|
-
skillCount: fetchedNames.length,
|
|
2174
|
-
fetchedSkillNames: fetchedNames,
|
|
2175
|
-
updatedLock:
|
|
2241
|
+
skillCount: result.fetchedNames.length,
|
|
2242
|
+
fetchedSkillNames: result.fetchedNames,
|
|
2243
|
+
updatedLock: result.updatedLock
|
|
2176
2244
|
};
|
|
2177
2245
|
}
|
|
2178
2246
|
async function fetchSourceViaGit(params) {
|
|
2179
2247
|
const { sourceEntry, baseDir, localSkillNames, alreadyFetchedSkillNames, updateSources, frozen } = params;
|
|
2180
|
-
|
|
2248
|
+
const { lock } = params;
|
|
2181
2249
|
const url = sourceEntry.source;
|
|
2182
2250
|
const locked = getLockedSource(lock, url);
|
|
2183
2251
|
const lockedSkillNames = locked ? getLockedSkillNames(locked) : [];
|
|
@@ -2233,68 +2301,35 @@ async function fetchSourceViaGit(params) {
|
|
|
2233
2301
|
const allNames = [...skillFileMap.keys()];
|
|
2234
2302
|
const filteredNames = isWildcard ? allNames : allNames.filter((n) => skillFilter.includes(n));
|
|
2235
2303
|
if (locked) {
|
|
2236
|
-
|
|
2237
|
-
for (const prev of lockedSkillNames) {
|
|
2238
|
-
const dir = join6(curatedDir, prev);
|
|
2239
|
-
if (resolve(dir).startsWith(base + sep) && await directoryExists(dir)) {
|
|
2240
|
-
await removeDirectory(dir);
|
|
2241
|
-
}
|
|
2242
|
-
}
|
|
2304
|
+
await cleanPreviousCuratedSkills(curatedDir, lockedSkillNames);
|
|
2243
2305
|
}
|
|
2244
2306
|
const fetchedSkills = {};
|
|
2245
2307
|
for (const skillName of filteredNames) {
|
|
2246
|
-
if (
|
|
2247
|
-
logger.warn(
|
|
2248
|
-
`Skipping skill with invalid name "${skillName}" from ${url}: contains path traversal characters.`
|
|
2249
|
-
);
|
|
2250
|
-
continue;
|
|
2251
|
-
}
|
|
2252
|
-
if (localSkillNames.has(skillName)) {
|
|
2253
|
-
logger.debug(
|
|
2254
|
-
`Skipping remote skill "${skillName}" from ${url}: local skill takes precedence.`
|
|
2255
|
-
);
|
|
2256
|
-
continue;
|
|
2257
|
-
}
|
|
2258
|
-
if (alreadyFetchedSkillNames.has(skillName)) {
|
|
2259
|
-
logger.warn(
|
|
2260
|
-
`Skipping duplicate skill "${skillName}" from ${url}: already fetched from another source.`
|
|
2261
|
-
);
|
|
2308
|
+
if (shouldSkipSkill({ skillName, sourceKey: url, localSkillNames, alreadyFetchedSkillNames })) {
|
|
2262
2309
|
continue;
|
|
2263
2310
|
}
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
written.push({ path: file.relativePath, content: file.content });
|
|
2273
|
-
}
|
|
2274
|
-
const integrity = computeSkillIntegrity(written);
|
|
2275
|
-
const lockedSkillEntry = locked?.skills[skillName];
|
|
2276
|
-
if (lockedSkillEntry?.integrity && lockedSkillEntry.integrity !== integrity && resolvedSha === locked?.resolvedRef) {
|
|
2277
|
-
logger.warn(`Integrity mismatch for skill "${skillName}" from ${url}.`);
|
|
2278
|
-
}
|
|
2279
|
-
fetchedSkills[skillName] = { integrity };
|
|
2280
|
-
}
|
|
2281
|
-
const fetchedNames = Object.keys(fetchedSkills);
|
|
2282
|
-
const mergedSkills = { ...fetchedSkills };
|
|
2283
|
-
if (locked) {
|
|
2284
|
-
for (const [k, v] of Object.entries(locked.skills)) {
|
|
2285
|
-
if (!(k in mergedSkills)) mergedSkills[k] = v;
|
|
2286
|
-
}
|
|
2311
|
+
fetchedSkills[skillName] = await writeSkillAndComputeIntegrity({
|
|
2312
|
+
skillName,
|
|
2313
|
+
files: skillFileMap.get(skillName) ?? [],
|
|
2314
|
+
curatedDir,
|
|
2315
|
+
locked,
|
|
2316
|
+
resolvedSha,
|
|
2317
|
+
sourceKey: url
|
|
2318
|
+
});
|
|
2287
2319
|
}
|
|
2288
|
-
|
|
2320
|
+
const result = buildLockUpdate({
|
|
2321
|
+
lock,
|
|
2322
|
+
sourceKey: url,
|
|
2323
|
+
fetchedSkills,
|
|
2324
|
+
locked,
|
|
2289
2325
|
requestedRef,
|
|
2290
|
-
|
|
2291
|
-
resolvedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2292
|
-
skills: mergedSkills
|
|
2326
|
+
resolvedSha
|
|
2293
2327
|
});
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2328
|
+
return {
|
|
2329
|
+
skillCount: result.fetchedNames.length,
|
|
2330
|
+
fetchedSkillNames: result.fetchedNames,
|
|
2331
|
+
updatedLock: result.updatedLock
|
|
2332
|
+
};
|
|
2298
2333
|
}
|
|
2299
2334
|
|
|
2300
2335
|
// src/cli/commands/install.ts
|
|
@@ -4110,7 +4145,7 @@ async function updateCommand(currentVersion, options) {
|
|
|
4110
4145
|
}
|
|
4111
4146
|
|
|
4112
4147
|
// src/cli/index.ts
|
|
4113
|
-
var getVersion = () => "7.
|
|
4148
|
+
var getVersion = () => "7.16.0";
|
|
4114
4149
|
var main = async () => {
|
|
4115
4150
|
const program = new Command();
|
|
4116
4151
|
const version = getVersion();
|