rulesync 7.13.0 → 7.15.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 +5 -5
- package/dist/{chunk-JGRHJWG5.js → chunk-L5AQUWUM.js} +871 -434
- package/dist/cli/index.cjs +1451 -715
- package/dist/cli/index.js +380 -77
- package/dist/index.cjs +1044 -627
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -50,8 +50,10 @@ import {
|
|
|
50
50
|
findFilesByGlobs,
|
|
51
51
|
formatError,
|
|
52
52
|
generate,
|
|
53
|
+
getFileSize,
|
|
53
54
|
getLocalSkillDirNames,
|
|
54
55
|
importFromTool,
|
|
56
|
+
isSymlink,
|
|
55
57
|
listDirectoryFiles,
|
|
56
58
|
logger,
|
|
57
59
|
readFileContent,
|
|
@@ -60,7 +62,7 @@ import {
|
|
|
60
62
|
removeTempDirectory,
|
|
61
63
|
stringifyFrontmatter,
|
|
62
64
|
writeFileContent
|
|
63
|
-
} from "../chunk-
|
|
65
|
+
} from "../chunk-L5AQUWUM.js";
|
|
64
66
|
|
|
65
67
|
// src/cli/index.ts
|
|
66
68
|
import { Command } from "commander";
|
|
@@ -1205,6 +1207,8 @@ var RULESYNC_IGNORE_ENTRIES = [
|
|
|
1205
1207
|
// Junie
|
|
1206
1208
|
"**/.junie/guidelines.md",
|
|
1207
1209
|
"**/.junie/mcp.json",
|
|
1210
|
+
"**/.junie/skills/",
|
|
1211
|
+
"**/.junie/agents/",
|
|
1208
1212
|
// Kilo Code
|
|
1209
1213
|
"**/.kilocode/rules/",
|
|
1210
1214
|
"**/.kilocode/skills/",
|
|
@@ -1609,12 +1613,175 @@ async function initCommand() {
|
|
|
1609
1613
|
}
|
|
1610
1614
|
|
|
1611
1615
|
// src/lib/sources.ts
|
|
1612
|
-
import { join as
|
|
1616
|
+
import { join as join6, resolve, sep } from "path";
|
|
1613
1617
|
import { Semaphore as Semaphore2 } from "es-toolkit/promise";
|
|
1614
1618
|
|
|
1619
|
+
// src/lib/git-client.ts
|
|
1620
|
+
import { execFile } from "child_process";
|
|
1621
|
+
import { join as join4 } from "path";
|
|
1622
|
+
import { promisify } from "util";
|
|
1623
|
+
var execFileAsync = promisify(execFile);
|
|
1624
|
+
var GIT_TIMEOUT_MS = 6e4;
|
|
1625
|
+
var ALLOWED_URL_SCHEMES = /^(https?:\/\/|ssh:\/\/|git:\/\/|file:\/\/\/|[a-zA-Z0-9_.+-]+@[a-zA-Z0-9.-]+:[a-zA-Z0-9_.+/~-]+)/;
|
|
1626
|
+
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
|
+
var GitClientError = class extends Error {
|
|
1637
|
+
constructor(message, cause) {
|
|
1638
|
+
super(message, { cause });
|
|
1639
|
+
this.name = "GitClientError";
|
|
1640
|
+
}
|
|
1641
|
+
};
|
|
1642
|
+
function validateGitUrl(url) {
|
|
1643
|
+
const ctrl = findControlCharacter(url);
|
|
1644
|
+
if (ctrl) {
|
|
1645
|
+
throw new GitClientError(
|
|
1646
|
+
`Git URL contains control character ${ctrl.hex} at position ${ctrl.position}`
|
|
1647
|
+
);
|
|
1648
|
+
}
|
|
1649
|
+
if (!ALLOWED_URL_SCHEMES.test(url)) {
|
|
1650
|
+
throw new GitClientError(
|
|
1651
|
+
`Unsupported or unsafe git URL: "${url}". Use https, ssh, git, or file schemes.`
|
|
1652
|
+
);
|
|
1653
|
+
}
|
|
1654
|
+
if (INSECURE_URL_SCHEMES.test(url)) {
|
|
1655
|
+
logger.warn(
|
|
1656
|
+
`URL "${url}" uses an unencrypted protocol. Consider using https:// or ssh:// instead.`
|
|
1657
|
+
);
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
function validateRef(ref) {
|
|
1661
|
+
if (ref.startsWith("-")) {
|
|
1662
|
+
throw new GitClientError(`Ref must not start with "-": "${ref}"`);
|
|
1663
|
+
}
|
|
1664
|
+
const ctrl = findControlCharacter(ref);
|
|
1665
|
+
if (ctrl) {
|
|
1666
|
+
throw new GitClientError(
|
|
1667
|
+
`Ref contains control character ${ctrl.hex} at position ${ctrl.position}`
|
|
1668
|
+
);
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
var gitChecked = false;
|
|
1672
|
+
async function checkGitAvailable() {
|
|
1673
|
+
if (gitChecked) return;
|
|
1674
|
+
try {
|
|
1675
|
+
await execFileAsync("git", ["--version"], { timeout: GIT_TIMEOUT_MS });
|
|
1676
|
+
gitChecked = true;
|
|
1677
|
+
} catch {
|
|
1678
|
+
throw new GitClientError("git is not installed or not found in PATH");
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
async function resolveDefaultRef(url) {
|
|
1682
|
+
validateGitUrl(url);
|
|
1683
|
+
await checkGitAvailable();
|
|
1684
|
+
try {
|
|
1685
|
+
const { stdout } = await execFileAsync("git", ["ls-remote", "--symref", "--", url, "HEAD"], {
|
|
1686
|
+
timeout: GIT_TIMEOUT_MS
|
|
1687
|
+
});
|
|
1688
|
+
const ref = stdout.match(/^ref: refs\/heads\/(.+)\tHEAD$/m)?.[1];
|
|
1689
|
+
const sha = stdout.match(/^([0-9a-f]{40})\tHEAD$/m)?.[1];
|
|
1690
|
+
if (!ref || !sha) throw new GitClientError(`Could not parse default branch from: ${url}`);
|
|
1691
|
+
return { ref, sha };
|
|
1692
|
+
} catch (error) {
|
|
1693
|
+
if (error instanceof GitClientError) throw error;
|
|
1694
|
+
throw new GitClientError(`Failed to resolve default ref for ${url}`, error);
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
async function resolveRefToSha(url, ref) {
|
|
1698
|
+
validateGitUrl(url);
|
|
1699
|
+
validateRef(ref);
|
|
1700
|
+
await checkGitAvailable();
|
|
1701
|
+
try {
|
|
1702
|
+
const { stdout } = await execFileAsync("git", ["ls-remote", "--", url, ref], {
|
|
1703
|
+
timeout: GIT_TIMEOUT_MS
|
|
1704
|
+
});
|
|
1705
|
+
const sha = stdout.match(/^([0-9a-f]{40})\t/m)?.[1];
|
|
1706
|
+
if (!sha) throw new GitClientError(`Ref "${ref}" not found in ${url}`);
|
|
1707
|
+
return sha;
|
|
1708
|
+
} catch (error) {
|
|
1709
|
+
if (error instanceof GitClientError) throw error;
|
|
1710
|
+
throw new GitClientError(`Failed to resolve ref "${ref}" for ${url}`, error);
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
async function fetchSkillFiles(params) {
|
|
1714
|
+
const { url, ref, skillsPath } = params;
|
|
1715
|
+
validateGitUrl(url);
|
|
1716
|
+
validateRef(ref);
|
|
1717
|
+
await checkGitAvailable();
|
|
1718
|
+
const tmpDir = await createTempDirectory("rulesync-git-");
|
|
1719
|
+
try {
|
|
1720
|
+
await execFileAsync(
|
|
1721
|
+
"git",
|
|
1722
|
+
[
|
|
1723
|
+
"clone",
|
|
1724
|
+
"--depth",
|
|
1725
|
+
"1",
|
|
1726
|
+
"--branch",
|
|
1727
|
+
ref,
|
|
1728
|
+
"--no-checkout",
|
|
1729
|
+
"--filter=blob:none",
|
|
1730
|
+
"--",
|
|
1731
|
+
url,
|
|
1732
|
+
tmpDir
|
|
1733
|
+
],
|
|
1734
|
+
{ timeout: GIT_TIMEOUT_MS }
|
|
1735
|
+
);
|
|
1736
|
+
await execFileAsync("git", ["-C", tmpDir, "sparse-checkout", "set", "--", skillsPath], {
|
|
1737
|
+
timeout: GIT_TIMEOUT_MS
|
|
1738
|
+
});
|
|
1739
|
+
await execFileAsync("git", ["-C", tmpDir, "checkout"], { timeout: GIT_TIMEOUT_MS });
|
|
1740
|
+
const skillsDir = join4(tmpDir, skillsPath);
|
|
1741
|
+
if (!await directoryExists(skillsDir)) return [];
|
|
1742
|
+
return await walkDirectory(skillsDir, skillsDir);
|
|
1743
|
+
} catch (error) {
|
|
1744
|
+
if (error instanceof GitClientError) throw error;
|
|
1745
|
+
throw new GitClientError(`Failed to fetch skill files from ${url}`, error);
|
|
1746
|
+
} finally {
|
|
1747
|
+
await removeTempDirectory(tmpDir);
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
var MAX_WALK_DEPTH = 20;
|
|
1751
|
+
async function walkDirectory(dir, baseDir, depth = 0) {
|
|
1752
|
+
if (depth > MAX_WALK_DEPTH) {
|
|
1753
|
+
throw new GitClientError(
|
|
1754
|
+
`Directory tree exceeds max depth of ${MAX_WALK_DEPTH}: "${dir}". Aborting to prevent resource exhaustion.`
|
|
1755
|
+
);
|
|
1756
|
+
}
|
|
1757
|
+
const results = [];
|
|
1758
|
+
for (const name of await listDirectoryFiles(dir)) {
|
|
1759
|
+
if (name === ".git") continue;
|
|
1760
|
+
const fullPath = join4(dir, name);
|
|
1761
|
+
if (await isSymlink(fullPath)) {
|
|
1762
|
+
logger.warn(`Skipping symlink "${fullPath}".`);
|
|
1763
|
+
continue;
|
|
1764
|
+
}
|
|
1765
|
+
if (await directoryExists(fullPath)) {
|
|
1766
|
+
results.push(...await walkDirectory(fullPath, baseDir, depth + 1));
|
|
1767
|
+
} else {
|
|
1768
|
+
const size = await getFileSize(fullPath);
|
|
1769
|
+
if (size > MAX_FILE_SIZE) {
|
|
1770
|
+
logger.warn(
|
|
1771
|
+
`Skipping file "${fullPath}" (${(size / 1024 / 1024).toFixed(2)}MB exceeds ${MAX_FILE_SIZE / 1024 / 1024}MB limit).`
|
|
1772
|
+
);
|
|
1773
|
+
continue;
|
|
1774
|
+
}
|
|
1775
|
+
const content = await readFileContent(fullPath);
|
|
1776
|
+
results.push({ relativePath: fullPath.substring(baseDir.length + 1), content, size });
|
|
1777
|
+
}
|
|
1778
|
+
}
|
|
1779
|
+
return results;
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1615
1782
|
// src/lib/sources-lock.ts
|
|
1616
1783
|
import { createHash } from "crypto";
|
|
1617
|
-
import { join as
|
|
1784
|
+
import { join as join5 } from "path";
|
|
1618
1785
|
import { optional, z as z4 } from "zod/mini";
|
|
1619
1786
|
var LOCKFILE_VERSION = 1;
|
|
1620
1787
|
var LockedSkillSchema = z4.object({
|
|
@@ -1658,7 +1825,7 @@ function createEmptyLock() {
|
|
|
1658
1825
|
return { lockfileVersion: LOCKFILE_VERSION, sources: {} };
|
|
1659
1826
|
}
|
|
1660
1827
|
async function readLockFile(params) {
|
|
1661
|
-
const lockPath =
|
|
1828
|
+
const lockPath = join5(params.baseDir, RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH);
|
|
1662
1829
|
if (!await fileExists(lockPath)) {
|
|
1663
1830
|
logger.debug("No sources lockfile found, starting fresh.");
|
|
1664
1831
|
return createEmptyLock();
|
|
@@ -1686,7 +1853,7 @@ async function readLockFile(params) {
|
|
|
1686
1853
|
}
|
|
1687
1854
|
}
|
|
1688
1855
|
async function writeLockFile(params) {
|
|
1689
|
-
const lockPath =
|
|
1856
|
+
const lockPath = join5(params.baseDir, RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH);
|
|
1690
1857
|
const content = JSON.stringify(params.lock, null, 2) + "\n";
|
|
1691
1858
|
await writeFileContent(lockPath, content);
|
|
1692
1859
|
logger.debug(`Wrote sources lockfile to ${lockPath}`);
|
|
@@ -1792,15 +1959,30 @@ async function resolveAndFetchSources(params) {
|
|
|
1792
1959
|
const allFetchedSkillNames = /* @__PURE__ */ new Set();
|
|
1793
1960
|
for (const sourceEntry of sources) {
|
|
1794
1961
|
try {
|
|
1795
|
-
const
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1962
|
+
const transport = sourceEntry.transport ?? "github";
|
|
1963
|
+
let result;
|
|
1964
|
+
if (transport === "git") {
|
|
1965
|
+
result = await fetchSourceViaGit({
|
|
1966
|
+
sourceEntry,
|
|
1967
|
+
baseDir,
|
|
1968
|
+
lock,
|
|
1969
|
+
localSkillNames,
|
|
1970
|
+
alreadyFetchedSkillNames: allFetchedSkillNames,
|
|
1971
|
+
updateSources: options.updateSources ?? false,
|
|
1972
|
+
frozen: options.frozen ?? false
|
|
1973
|
+
});
|
|
1974
|
+
} else {
|
|
1975
|
+
result = await fetchSource({
|
|
1976
|
+
sourceEntry,
|
|
1977
|
+
client,
|
|
1978
|
+
baseDir,
|
|
1979
|
+
lock,
|
|
1980
|
+
localSkillNames,
|
|
1981
|
+
alreadyFetchedSkillNames: allFetchedSkillNames,
|
|
1982
|
+
updateSources: options.updateSources ?? false
|
|
1983
|
+
});
|
|
1984
|
+
}
|
|
1985
|
+
const { skillCount, fetchedSkillNames, updatedLock } = result;
|
|
1804
1986
|
lock = updatedLock;
|
|
1805
1987
|
totalSkillCount += skillCount;
|
|
1806
1988
|
for (const name of fetchedSkillNames) {
|
|
@@ -1833,7 +2015,7 @@ async function resolveAndFetchSources(params) {
|
|
|
1833
2015
|
async function checkLockedSkillsExist(curatedDir, skillNames) {
|
|
1834
2016
|
if (skillNames.length === 0) return true;
|
|
1835
2017
|
for (const name of skillNames) {
|
|
1836
|
-
if (!await directoryExists(
|
|
2018
|
+
if (!await directoryExists(join6(curatedDir, name))) {
|
|
1837
2019
|
return false;
|
|
1838
2020
|
}
|
|
1839
2021
|
}
|
|
@@ -1864,7 +2046,7 @@ async function fetchSource(params) {
|
|
|
1864
2046
|
ref = resolvedSha;
|
|
1865
2047
|
logger.debug(`Resolved ${sourceKey} ref "${requestedRef}" to SHA: ${resolvedSha}`);
|
|
1866
2048
|
}
|
|
1867
|
-
const curatedDir =
|
|
2049
|
+
const curatedDir = join6(baseDir, RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH);
|
|
1868
2050
|
if (locked && resolvedSha === locked.resolvedRef && !updateSources) {
|
|
1869
2051
|
const allExist = await checkLockedSkillsExist(curatedDir, lockedSkillNames);
|
|
1870
2052
|
if (allExist) {
|
|
@@ -1896,7 +2078,7 @@ async function fetchSource(params) {
|
|
|
1896
2078
|
if (locked) {
|
|
1897
2079
|
const resolvedCuratedDir = resolve(curatedDir);
|
|
1898
2080
|
for (const prevSkill of lockedSkillNames) {
|
|
1899
|
-
const prevDir =
|
|
2081
|
+
const prevDir = join6(curatedDir, prevSkill);
|
|
1900
2082
|
if (!resolve(prevDir).startsWith(resolvedCuratedDir + sep)) {
|
|
1901
2083
|
logger.warn(
|
|
1902
2084
|
`Skipping removal of "${prevSkill}": resolved path is outside the curated directory.`
|
|
@@ -1947,10 +2129,10 @@ async function fetchSource(params) {
|
|
|
1947
2129
|
const skillFiles = [];
|
|
1948
2130
|
for (const file of files) {
|
|
1949
2131
|
const relativeToSkill = file.path.substring(skillDir.path.length + 1);
|
|
1950
|
-
const localFilePath =
|
|
2132
|
+
const localFilePath = join6(curatedDir, skillDir.name, relativeToSkill);
|
|
1951
2133
|
checkPathTraversal({
|
|
1952
2134
|
relativePath: relativeToSkill,
|
|
1953
|
-
intendedRootDir:
|
|
2135
|
+
intendedRootDir: join6(curatedDir, skillDir.name)
|
|
1954
2136
|
});
|
|
1955
2137
|
const content = await withSemaphore(
|
|
1956
2138
|
semaphore,
|
|
@@ -1993,6 +2175,127 @@ async function fetchSource(params) {
|
|
|
1993
2175
|
updatedLock: lock
|
|
1994
2176
|
};
|
|
1995
2177
|
}
|
|
2178
|
+
async function fetchSourceViaGit(params) {
|
|
2179
|
+
const { sourceEntry, baseDir, localSkillNames, alreadyFetchedSkillNames, updateSources, frozen } = params;
|
|
2180
|
+
let { lock } = params;
|
|
2181
|
+
const url = sourceEntry.source;
|
|
2182
|
+
const locked = getLockedSource(lock, url);
|
|
2183
|
+
const lockedSkillNames = locked ? getLockedSkillNames(locked) : [];
|
|
2184
|
+
let resolvedSha;
|
|
2185
|
+
let requestedRef;
|
|
2186
|
+
if (locked && !updateSources) {
|
|
2187
|
+
resolvedSha = locked.resolvedRef;
|
|
2188
|
+
requestedRef = locked.requestedRef;
|
|
2189
|
+
if (requestedRef) {
|
|
2190
|
+
validateRef(requestedRef);
|
|
2191
|
+
}
|
|
2192
|
+
} else if (sourceEntry.ref) {
|
|
2193
|
+
requestedRef = sourceEntry.ref;
|
|
2194
|
+
resolvedSha = await resolveRefToSha(url, requestedRef);
|
|
2195
|
+
} else {
|
|
2196
|
+
const def = await resolveDefaultRef(url);
|
|
2197
|
+
requestedRef = def.ref;
|
|
2198
|
+
resolvedSha = def.sha;
|
|
2199
|
+
}
|
|
2200
|
+
const curatedDir = join6(baseDir, RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH);
|
|
2201
|
+
if (locked && resolvedSha === locked.resolvedRef && !updateSources) {
|
|
2202
|
+
if (await checkLockedSkillsExist(curatedDir, lockedSkillNames)) {
|
|
2203
|
+
return { skillCount: 0, fetchedSkillNames: lockedSkillNames, updatedLock: lock };
|
|
2204
|
+
}
|
|
2205
|
+
}
|
|
2206
|
+
if (!requestedRef) {
|
|
2207
|
+
if (frozen) {
|
|
2208
|
+
throw new Error(
|
|
2209
|
+
`Frozen install failed: lockfile entry for "${url}" is missing requestedRef. Run 'rulesync install' to update the lockfile.`
|
|
2210
|
+
);
|
|
2211
|
+
}
|
|
2212
|
+
const def = await resolveDefaultRef(url);
|
|
2213
|
+
requestedRef = def.ref;
|
|
2214
|
+
resolvedSha = def.sha;
|
|
2215
|
+
}
|
|
2216
|
+
const skillFilter = sourceEntry.skills ?? ["*"];
|
|
2217
|
+
const isWildcard = skillFilter.length === 1 && skillFilter[0] === "*";
|
|
2218
|
+
const remoteFiles = await fetchSkillFiles({
|
|
2219
|
+
url,
|
|
2220
|
+
ref: requestedRef,
|
|
2221
|
+
skillsPath: sourceEntry.path ?? "skills"
|
|
2222
|
+
});
|
|
2223
|
+
const skillFileMap = /* @__PURE__ */ new Map();
|
|
2224
|
+
for (const file of remoteFiles) {
|
|
2225
|
+
const idx = file.relativePath.indexOf("/");
|
|
2226
|
+
if (idx === -1) continue;
|
|
2227
|
+
const name = file.relativePath.substring(0, idx);
|
|
2228
|
+
const inner = file.relativePath.substring(idx + 1);
|
|
2229
|
+
const arr = skillFileMap.get(name) ?? [];
|
|
2230
|
+
arr.push({ relativePath: inner, content: file.content });
|
|
2231
|
+
skillFileMap.set(name, arr);
|
|
2232
|
+
}
|
|
2233
|
+
const allNames = [...skillFileMap.keys()];
|
|
2234
|
+
const filteredNames = isWildcard ? allNames : allNames.filter((n) => skillFilter.includes(n));
|
|
2235
|
+
if (locked) {
|
|
2236
|
+
const base = resolve(curatedDir);
|
|
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
|
+
}
|
|
2243
|
+
}
|
|
2244
|
+
const fetchedSkills = {};
|
|
2245
|
+
for (const skillName of filteredNames) {
|
|
2246
|
+
if (skillName.includes("..") || skillName.includes("/") || skillName.includes("\\")) {
|
|
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
|
+
);
|
|
2262
|
+
continue;
|
|
2263
|
+
}
|
|
2264
|
+
const files = skillFileMap.get(skillName) ?? [];
|
|
2265
|
+
const written = [];
|
|
2266
|
+
for (const file of files) {
|
|
2267
|
+
checkPathTraversal({
|
|
2268
|
+
relativePath: file.relativePath,
|
|
2269
|
+
intendedRootDir: join6(curatedDir, skillName)
|
|
2270
|
+
});
|
|
2271
|
+
await writeFileContent(join6(curatedDir, skillName, file.relativePath), file.content);
|
|
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
|
+
}
|
|
2287
|
+
}
|
|
2288
|
+
lock = setLockedSource(lock, url, {
|
|
2289
|
+
requestedRef,
|
|
2290
|
+
resolvedRef: resolvedSha,
|
|
2291
|
+
resolvedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2292
|
+
skills: mergedSkills
|
|
2293
|
+
});
|
|
2294
|
+
logger.info(
|
|
2295
|
+
`Fetched ${fetchedNames.length} skill(s) from ${url}: ${fetchedNames.join(", ") || "(none)"}`
|
|
2296
|
+
);
|
|
2297
|
+
return { skillCount: fetchedNames.length, fetchedSkillNames: fetchedNames, updatedLock: lock };
|
|
2298
|
+
}
|
|
1996
2299
|
|
|
1997
2300
|
// src/cli/commands/install.ts
|
|
1998
2301
|
async function installCommand(options) {
|
|
@@ -2036,12 +2339,12 @@ import { FastMCP } from "fastmcp";
|
|
|
2036
2339
|
import { z as z13 } from "zod/mini";
|
|
2037
2340
|
|
|
2038
2341
|
// src/mcp/commands.ts
|
|
2039
|
-
import { basename, join as
|
|
2342
|
+
import { basename, join as join7 } from "path";
|
|
2040
2343
|
import { z as z5 } from "zod/mini";
|
|
2041
2344
|
var maxCommandSizeBytes = 1024 * 1024;
|
|
2042
2345
|
var maxCommandsCount = 1e3;
|
|
2043
2346
|
async function listCommands() {
|
|
2044
|
-
const commandsDir =
|
|
2347
|
+
const commandsDir = join7(process.cwd(), RULESYNC_COMMANDS_RELATIVE_DIR_PATH);
|
|
2045
2348
|
try {
|
|
2046
2349
|
const files = await listDirectoryFiles(commandsDir);
|
|
2047
2350
|
const mdFiles = files.filter((file) => file.endsWith(".md"));
|
|
@@ -2057,7 +2360,7 @@ async function listCommands() {
|
|
|
2057
2360
|
});
|
|
2058
2361
|
const frontmatter = command.getFrontmatter();
|
|
2059
2362
|
return {
|
|
2060
|
-
relativePathFromCwd:
|
|
2363
|
+
relativePathFromCwd: join7(RULESYNC_COMMANDS_RELATIVE_DIR_PATH, file),
|
|
2061
2364
|
frontmatter
|
|
2062
2365
|
};
|
|
2063
2366
|
} catch (error) {
|
|
@@ -2085,7 +2388,7 @@ async function getCommand({ relativePathFromCwd }) {
|
|
|
2085
2388
|
relativeFilePath: filename
|
|
2086
2389
|
});
|
|
2087
2390
|
return {
|
|
2088
|
-
relativePathFromCwd:
|
|
2391
|
+
relativePathFromCwd: join7(RULESYNC_COMMANDS_RELATIVE_DIR_PATH, filename),
|
|
2089
2392
|
frontmatter: command.getFrontmatter(),
|
|
2090
2393
|
body: command.getBody()
|
|
2091
2394
|
};
|
|
@@ -2114,7 +2417,7 @@ async function putCommand({
|
|
|
2114
2417
|
try {
|
|
2115
2418
|
const existingCommands = await listCommands();
|
|
2116
2419
|
const isUpdate = existingCommands.some(
|
|
2117
|
-
(command2) => command2.relativePathFromCwd ===
|
|
2420
|
+
(command2) => command2.relativePathFromCwd === join7(RULESYNC_COMMANDS_RELATIVE_DIR_PATH, filename)
|
|
2118
2421
|
);
|
|
2119
2422
|
if (!isUpdate && existingCommands.length >= maxCommandsCount) {
|
|
2120
2423
|
throw new Error(
|
|
@@ -2131,11 +2434,11 @@ async function putCommand({
|
|
|
2131
2434
|
fileContent,
|
|
2132
2435
|
validate: true
|
|
2133
2436
|
});
|
|
2134
|
-
const commandsDir =
|
|
2437
|
+
const commandsDir = join7(process.cwd(), RULESYNC_COMMANDS_RELATIVE_DIR_PATH);
|
|
2135
2438
|
await ensureDir(commandsDir);
|
|
2136
2439
|
await writeFileContent(command.getFilePath(), command.getFileContent());
|
|
2137
2440
|
return {
|
|
2138
|
-
relativePathFromCwd:
|
|
2441
|
+
relativePathFromCwd: join7(RULESYNC_COMMANDS_RELATIVE_DIR_PATH, filename),
|
|
2139
2442
|
frontmatter: command.getFrontmatter(),
|
|
2140
2443
|
body: command.getBody()
|
|
2141
2444
|
};
|
|
@@ -2151,11 +2454,11 @@ async function deleteCommand({ relativePathFromCwd }) {
|
|
|
2151
2454
|
intendedRootDir: process.cwd()
|
|
2152
2455
|
});
|
|
2153
2456
|
const filename = basename(relativePathFromCwd);
|
|
2154
|
-
const fullPath =
|
|
2457
|
+
const fullPath = join7(process.cwd(), RULESYNC_COMMANDS_RELATIVE_DIR_PATH, filename);
|
|
2155
2458
|
try {
|
|
2156
2459
|
await removeFile(fullPath);
|
|
2157
2460
|
return {
|
|
2158
|
-
relativePathFromCwd:
|
|
2461
|
+
relativePathFromCwd: join7(RULESYNC_COMMANDS_RELATIVE_DIR_PATH, filename)
|
|
2159
2462
|
};
|
|
2160
2463
|
} catch (error) {
|
|
2161
2464
|
throw new Error(`Failed to delete command file ${relativePathFromCwd}: ${formatError(error)}`, {
|
|
@@ -2180,7 +2483,7 @@ var commandToolSchemas = {
|
|
|
2180
2483
|
var commandTools = {
|
|
2181
2484
|
listCommands: {
|
|
2182
2485
|
name: "listCommands",
|
|
2183
|
-
description: `List all commands from ${
|
|
2486
|
+
description: `List all commands from ${join7(RULESYNC_COMMANDS_RELATIVE_DIR_PATH, "*.md")} with their frontmatter.`,
|
|
2184
2487
|
parameters: commandToolSchemas.listCommands,
|
|
2185
2488
|
execute: async () => {
|
|
2186
2489
|
const commands = await listCommands();
|
|
@@ -2307,11 +2610,11 @@ var generateTools = {
|
|
|
2307
2610
|
};
|
|
2308
2611
|
|
|
2309
2612
|
// src/mcp/ignore.ts
|
|
2310
|
-
import { join as
|
|
2613
|
+
import { join as join8 } from "path";
|
|
2311
2614
|
import { z as z7 } from "zod/mini";
|
|
2312
2615
|
var maxIgnoreFileSizeBytes = 100 * 1024;
|
|
2313
2616
|
async function getIgnoreFile() {
|
|
2314
|
-
const ignoreFilePath =
|
|
2617
|
+
const ignoreFilePath = join8(process.cwd(), RULESYNC_AIIGNORE_RELATIVE_FILE_PATH);
|
|
2315
2618
|
try {
|
|
2316
2619
|
const content = await readFileContent(ignoreFilePath);
|
|
2317
2620
|
return {
|
|
@@ -2328,7 +2631,7 @@ async function getIgnoreFile() {
|
|
|
2328
2631
|
}
|
|
2329
2632
|
}
|
|
2330
2633
|
async function putIgnoreFile({ content }) {
|
|
2331
|
-
const ignoreFilePath =
|
|
2634
|
+
const ignoreFilePath = join8(process.cwd(), RULESYNC_AIIGNORE_RELATIVE_FILE_PATH);
|
|
2332
2635
|
const contentSizeBytes = Buffer.byteLength(content, "utf8");
|
|
2333
2636
|
if (contentSizeBytes > maxIgnoreFileSizeBytes) {
|
|
2334
2637
|
throw new Error(
|
|
@@ -2352,8 +2655,8 @@ async function putIgnoreFile({ content }) {
|
|
|
2352
2655
|
}
|
|
2353
2656
|
}
|
|
2354
2657
|
async function deleteIgnoreFile() {
|
|
2355
|
-
const aiignorePath =
|
|
2356
|
-
const legacyIgnorePath =
|
|
2658
|
+
const aiignorePath = join8(process.cwd(), RULESYNC_AIIGNORE_RELATIVE_FILE_PATH);
|
|
2659
|
+
const legacyIgnorePath = join8(process.cwd(), RULESYNC_IGNORE_RELATIVE_FILE_PATH);
|
|
2357
2660
|
try {
|
|
2358
2661
|
await Promise.all([removeFile(aiignorePath), removeFile(legacyIgnorePath)]);
|
|
2359
2662
|
return {
|
|
@@ -2481,7 +2784,7 @@ var importTools = {
|
|
|
2481
2784
|
};
|
|
2482
2785
|
|
|
2483
2786
|
// src/mcp/mcp.ts
|
|
2484
|
-
import { join as
|
|
2787
|
+
import { join as join9 } from "path";
|
|
2485
2788
|
import { z as z9 } from "zod/mini";
|
|
2486
2789
|
var maxMcpSizeBytes = 1024 * 1024;
|
|
2487
2790
|
async function getMcpFile() {
|
|
@@ -2489,7 +2792,7 @@ async function getMcpFile() {
|
|
|
2489
2792
|
const rulesyncMcp = await RulesyncMcp.fromFile({
|
|
2490
2793
|
validate: true
|
|
2491
2794
|
});
|
|
2492
|
-
const relativePathFromCwd =
|
|
2795
|
+
const relativePathFromCwd = join9(
|
|
2493
2796
|
rulesyncMcp.getRelativeDirPath(),
|
|
2494
2797
|
rulesyncMcp.getRelativeFilePath()
|
|
2495
2798
|
);
|
|
@@ -2527,7 +2830,7 @@ async function putMcpFile({ content }) {
|
|
|
2527
2830
|
const paths = RulesyncMcp.getSettablePaths();
|
|
2528
2831
|
const relativeDirPath = paths.recommended.relativeDirPath;
|
|
2529
2832
|
const relativeFilePath = paths.recommended.relativeFilePath;
|
|
2530
|
-
const fullPath =
|
|
2833
|
+
const fullPath = join9(baseDir, relativeDirPath, relativeFilePath);
|
|
2531
2834
|
const rulesyncMcp = new RulesyncMcp({
|
|
2532
2835
|
baseDir,
|
|
2533
2836
|
relativeDirPath,
|
|
@@ -2535,9 +2838,9 @@ async function putMcpFile({ content }) {
|
|
|
2535
2838
|
fileContent: content,
|
|
2536
2839
|
validate: true
|
|
2537
2840
|
});
|
|
2538
|
-
await ensureDir(
|
|
2841
|
+
await ensureDir(join9(baseDir, relativeDirPath));
|
|
2539
2842
|
await writeFileContent(fullPath, content);
|
|
2540
|
-
const relativePathFromCwd =
|
|
2843
|
+
const relativePathFromCwd = join9(relativeDirPath, relativeFilePath);
|
|
2541
2844
|
return {
|
|
2542
2845
|
relativePathFromCwd,
|
|
2543
2846
|
content: rulesyncMcp.getFileContent()
|
|
@@ -2555,15 +2858,15 @@ async function deleteMcpFile() {
|
|
|
2555
2858
|
try {
|
|
2556
2859
|
const baseDir = process.cwd();
|
|
2557
2860
|
const paths = RulesyncMcp.getSettablePaths();
|
|
2558
|
-
const recommendedPath =
|
|
2861
|
+
const recommendedPath = join9(
|
|
2559
2862
|
baseDir,
|
|
2560
2863
|
paths.recommended.relativeDirPath,
|
|
2561
2864
|
paths.recommended.relativeFilePath
|
|
2562
2865
|
);
|
|
2563
|
-
const legacyPath =
|
|
2866
|
+
const legacyPath = join9(baseDir, paths.legacy.relativeDirPath, paths.legacy.relativeFilePath);
|
|
2564
2867
|
await removeFile(recommendedPath);
|
|
2565
2868
|
await removeFile(legacyPath);
|
|
2566
|
-
const relativePathFromCwd =
|
|
2869
|
+
const relativePathFromCwd = join9(
|
|
2567
2870
|
paths.recommended.relativeDirPath,
|
|
2568
2871
|
paths.recommended.relativeFilePath
|
|
2569
2872
|
);
|
|
@@ -2617,12 +2920,12 @@ var mcpTools = {
|
|
|
2617
2920
|
};
|
|
2618
2921
|
|
|
2619
2922
|
// src/mcp/rules.ts
|
|
2620
|
-
import { basename as basename2, join as
|
|
2923
|
+
import { basename as basename2, join as join10 } from "path";
|
|
2621
2924
|
import { z as z10 } from "zod/mini";
|
|
2622
2925
|
var maxRuleSizeBytes = 1024 * 1024;
|
|
2623
2926
|
var maxRulesCount = 1e3;
|
|
2624
2927
|
async function listRules() {
|
|
2625
|
-
const rulesDir =
|
|
2928
|
+
const rulesDir = join10(process.cwd(), RULESYNC_RULES_RELATIVE_DIR_PATH);
|
|
2626
2929
|
try {
|
|
2627
2930
|
const files = await listDirectoryFiles(rulesDir);
|
|
2628
2931
|
const mdFiles = files.filter((file) => file.endsWith(".md"));
|
|
@@ -2635,7 +2938,7 @@ async function listRules() {
|
|
|
2635
2938
|
});
|
|
2636
2939
|
const frontmatter = rule.getFrontmatter();
|
|
2637
2940
|
return {
|
|
2638
|
-
relativePathFromCwd:
|
|
2941
|
+
relativePathFromCwd: join10(RULESYNC_RULES_RELATIVE_DIR_PATH, file),
|
|
2639
2942
|
frontmatter
|
|
2640
2943
|
};
|
|
2641
2944
|
} catch (error) {
|
|
@@ -2664,7 +2967,7 @@ async function getRule({ relativePathFromCwd }) {
|
|
|
2664
2967
|
validate: true
|
|
2665
2968
|
});
|
|
2666
2969
|
return {
|
|
2667
|
-
relativePathFromCwd:
|
|
2970
|
+
relativePathFromCwd: join10(RULESYNC_RULES_RELATIVE_DIR_PATH, filename),
|
|
2668
2971
|
frontmatter: rule.getFrontmatter(),
|
|
2669
2972
|
body: rule.getBody()
|
|
2670
2973
|
};
|
|
@@ -2693,7 +2996,7 @@ async function putRule({
|
|
|
2693
2996
|
try {
|
|
2694
2997
|
const existingRules = await listRules();
|
|
2695
2998
|
const isUpdate = existingRules.some(
|
|
2696
|
-
(rule2) => rule2.relativePathFromCwd ===
|
|
2999
|
+
(rule2) => rule2.relativePathFromCwd === join10(RULESYNC_RULES_RELATIVE_DIR_PATH, filename)
|
|
2697
3000
|
);
|
|
2698
3001
|
if (!isUpdate && existingRules.length >= maxRulesCount) {
|
|
2699
3002
|
throw new Error(
|
|
@@ -2708,11 +3011,11 @@ async function putRule({
|
|
|
2708
3011
|
body,
|
|
2709
3012
|
validate: true
|
|
2710
3013
|
});
|
|
2711
|
-
const rulesDir =
|
|
3014
|
+
const rulesDir = join10(process.cwd(), RULESYNC_RULES_RELATIVE_DIR_PATH);
|
|
2712
3015
|
await ensureDir(rulesDir);
|
|
2713
3016
|
await writeFileContent(rule.getFilePath(), rule.getFileContent());
|
|
2714
3017
|
return {
|
|
2715
|
-
relativePathFromCwd:
|
|
3018
|
+
relativePathFromCwd: join10(RULESYNC_RULES_RELATIVE_DIR_PATH, filename),
|
|
2716
3019
|
frontmatter: rule.getFrontmatter(),
|
|
2717
3020
|
body: rule.getBody()
|
|
2718
3021
|
};
|
|
@@ -2728,11 +3031,11 @@ async function deleteRule({ relativePathFromCwd }) {
|
|
|
2728
3031
|
intendedRootDir: process.cwd()
|
|
2729
3032
|
});
|
|
2730
3033
|
const filename = basename2(relativePathFromCwd);
|
|
2731
|
-
const fullPath =
|
|
3034
|
+
const fullPath = join10(process.cwd(), RULESYNC_RULES_RELATIVE_DIR_PATH, filename);
|
|
2732
3035
|
try {
|
|
2733
3036
|
await removeFile(fullPath);
|
|
2734
3037
|
return {
|
|
2735
|
-
relativePathFromCwd:
|
|
3038
|
+
relativePathFromCwd: join10(RULESYNC_RULES_RELATIVE_DIR_PATH, filename)
|
|
2736
3039
|
};
|
|
2737
3040
|
} catch (error) {
|
|
2738
3041
|
throw new Error(`Failed to delete rule file ${relativePathFromCwd}: ${formatError(error)}`, {
|
|
@@ -2757,7 +3060,7 @@ var ruleToolSchemas = {
|
|
|
2757
3060
|
var ruleTools = {
|
|
2758
3061
|
listRules: {
|
|
2759
3062
|
name: "listRules",
|
|
2760
|
-
description: `List all rules from ${
|
|
3063
|
+
description: `List all rules from ${join10(RULESYNC_RULES_RELATIVE_DIR_PATH, "*.md")} with their frontmatter.`,
|
|
2761
3064
|
parameters: ruleToolSchemas.listRules,
|
|
2762
3065
|
execute: async () => {
|
|
2763
3066
|
const rules = await listRules();
|
|
@@ -2799,7 +3102,7 @@ var ruleTools = {
|
|
|
2799
3102
|
};
|
|
2800
3103
|
|
|
2801
3104
|
// src/mcp/skills.ts
|
|
2802
|
-
import { basename as basename3, dirname, join as
|
|
3105
|
+
import { basename as basename3, dirname, join as join11 } from "path";
|
|
2803
3106
|
import { z as z11 } from "zod/mini";
|
|
2804
3107
|
var maxSkillSizeBytes = 1024 * 1024;
|
|
2805
3108
|
var maxSkillsCount = 1e3;
|
|
@@ -2823,9 +3126,9 @@ function extractDirName(relativeDirPathFromCwd) {
|
|
|
2823
3126
|
return dirName;
|
|
2824
3127
|
}
|
|
2825
3128
|
async function listSkills() {
|
|
2826
|
-
const skillsDir =
|
|
3129
|
+
const skillsDir = join11(process.cwd(), RULESYNC_SKILLS_RELATIVE_DIR_PATH);
|
|
2827
3130
|
try {
|
|
2828
|
-
const skillDirPaths = await findFilesByGlobs(
|
|
3131
|
+
const skillDirPaths = await findFilesByGlobs(join11(skillsDir, "*"), { type: "dir" });
|
|
2829
3132
|
const skills = await Promise.all(
|
|
2830
3133
|
skillDirPaths.map(async (dirPath) => {
|
|
2831
3134
|
const dirName = basename3(dirPath);
|
|
@@ -2836,7 +3139,7 @@ async function listSkills() {
|
|
|
2836
3139
|
});
|
|
2837
3140
|
const frontmatter = skill.getFrontmatter();
|
|
2838
3141
|
return {
|
|
2839
|
-
relativeDirPathFromCwd:
|
|
3142
|
+
relativeDirPathFromCwd: join11(RULESYNC_SKILLS_RELATIVE_DIR_PATH, dirName),
|
|
2840
3143
|
frontmatter
|
|
2841
3144
|
};
|
|
2842
3145
|
} catch (error) {
|
|
@@ -2864,7 +3167,7 @@ async function getSkill({ relativeDirPathFromCwd }) {
|
|
|
2864
3167
|
dirName
|
|
2865
3168
|
});
|
|
2866
3169
|
return {
|
|
2867
|
-
relativeDirPathFromCwd:
|
|
3170
|
+
relativeDirPathFromCwd: join11(RULESYNC_SKILLS_RELATIVE_DIR_PATH, dirName),
|
|
2868
3171
|
frontmatter: skill.getFrontmatter(),
|
|
2869
3172
|
body: skill.getBody(),
|
|
2870
3173
|
otherFiles: skill.getOtherFiles().map(aiDirFileToMcpSkillFile)
|
|
@@ -2898,7 +3201,7 @@ async function putSkill({
|
|
|
2898
3201
|
try {
|
|
2899
3202
|
const existingSkills = await listSkills();
|
|
2900
3203
|
const isUpdate = existingSkills.some(
|
|
2901
|
-
(skill2) => skill2.relativeDirPathFromCwd ===
|
|
3204
|
+
(skill2) => skill2.relativeDirPathFromCwd === join11(RULESYNC_SKILLS_RELATIVE_DIR_PATH, dirName)
|
|
2902
3205
|
);
|
|
2903
3206
|
if (!isUpdate && existingSkills.length >= maxSkillsCount) {
|
|
2904
3207
|
throw new Error(
|
|
@@ -2915,9 +3218,9 @@ async function putSkill({
|
|
|
2915
3218
|
otherFiles: aiDirFiles,
|
|
2916
3219
|
validate: true
|
|
2917
3220
|
});
|
|
2918
|
-
const skillDirPath =
|
|
3221
|
+
const skillDirPath = join11(process.cwd(), RULESYNC_SKILLS_RELATIVE_DIR_PATH, dirName);
|
|
2919
3222
|
await ensureDir(skillDirPath);
|
|
2920
|
-
const skillFilePath =
|
|
3223
|
+
const skillFilePath = join11(skillDirPath, SKILL_FILE_NAME);
|
|
2921
3224
|
const skillFileContent = stringifyFrontmatter(body, frontmatter);
|
|
2922
3225
|
await writeFileContent(skillFilePath, skillFileContent);
|
|
2923
3226
|
for (const file of otherFiles) {
|
|
@@ -2925,15 +3228,15 @@ async function putSkill({
|
|
|
2925
3228
|
relativePath: file.name,
|
|
2926
3229
|
intendedRootDir: skillDirPath
|
|
2927
3230
|
});
|
|
2928
|
-
const filePath =
|
|
2929
|
-
const fileDir =
|
|
3231
|
+
const filePath = join11(skillDirPath, file.name);
|
|
3232
|
+
const fileDir = join11(skillDirPath, dirname(file.name));
|
|
2930
3233
|
if (fileDir !== skillDirPath) {
|
|
2931
3234
|
await ensureDir(fileDir);
|
|
2932
3235
|
}
|
|
2933
3236
|
await writeFileContent(filePath, file.body);
|
|
2934
3237
|
}
|
|
2935
3238
|
return {
|
|
2936
|
-
relativeDirPathFromCwd:
|
|
3239
|
+
relativeDirPathFromCwd: join11(RULESYNC_SKILLS_RELATIVE_DIR_PATH, dirName),
|
|
2937
3240
|
frontmatter: skill.getFrontmatter(),
|
|
2938
3241
|
body: skill.getBody(),
|
|
2939
3242
|
otherFiles: skill.getOtherFiles().map(aiDirFileToMcpSkillFile)
|
|
@@ -2955,13 +3258,13 @@ async function deleteSkill({
|
|
|
2955
3258
|
intendedRootDir: process.cwd()
|
|
2956
3259
|
});
|
|
2957
3260
|
const dirName = extractDirName(relativeDirPathFromCwd);
|
|
2958
|
-
const skillDirPath =
|
|
3261
|
+
const skillDirPath = join11(process.cwd(), RULESYNC_SKILLS_RELATIVE_DIR_PATH, dirName);
|
|
2959
3262
|
try {
|
|
2960
3263
|
if (await directoryExists(skillDirPath)) {
|
|
2961
3264
|
await removeDirectory(skillDirPath);
|
|
2962
3265
|
}
|
|
2963
3266
|
return {
|
|
2964
|
-
relativeDirPathFromCwd:
|
|
3267
|
+
relativeDirPathFromCwd: join11(RULESYNC_SKILLS_RELATIVE_DIR_PATH, dirName)
|
|
2965
3268
|
};
|
|
2966
3269
|
} catch (error) {
|
|
2967
3270
|
throw new Error(
|
|
@@ -2994,7 +3297,7 @@ var skillToolSchemas = {
|
|
|
2994
3297
|
var skillTools = {
|
|
2995
3298
|
listSkills: {
|
|
2996
3299
|
name: "listSkills",
|
|
2997
|
-
description: `List all skills from ${
|
|
3300
|
+
description: `List all skills from ${join11(RULESYNC_SKILLS_RELATIVE_DIR_PATH, "*", SKILL_FILE_NAME)} with their frontmatter.`,
|
|
2998
3301
|
parameters: skillToolSchemas.listSkills,
|
|
2999
3302
|
execute: async () => {
|
|
3000
3303
|
const skills = await listSkills();
|
|
@@ -3037,12 +3340,12 @@ var skillTools = {
|
|
|
3037
3340
|
};
|
|
3038
3341
|
|
|
3039
3342
|
// src/mcp/subagents.ts
|
|
3040
|
-
import { basename as basename4, join as
|
|
3343
|
+
import { basename as basename4, join as join12 } from "path";
|
|
3041
3344
|
import { z as z12 } from "zod/mini";
|
|
3042
3345
|
var maxSubagentSizeBytes = 1024 * 1024;
|
|
3043
3346
|
var maxSubagentsCount = 1e3;
|
|
3044
3347
|
async function listSubagents() {
|
|
3045
|
-
const subagentsDir =
|
|
3348
|
+
const subagentsDir = join12(process.cwd(), RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH);
|
|
3046
3349
|
try {
|
|
3047
3350
|
const files = await listDirectoryFiles(subagentsDir);
|
|
3048
3351
|
const mdFiles = files.filter((file) => file.endsWith(".md"));
|
|
@@ -3055,7 +3358,7 @@ async function listSubagents() {
|
|
|
3055
3358
|
});
|
|
3056
3359
|
const frontmatter = subagent.getFrontmatter();
|
|
3057
3360
|
return {
|
|
3058
|
-
relativePathFromCwd:
|
|
3361
|
+
relativePathFromCwd: join12(RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH, file),
|
|
3059
3362
|
frontmatter
|
|
3060
3363
|
};
|
|
3061
3364
|
} catch (error) {
|
|
@@ -3086,7 +3389,7 @@ async function getSubagent({ relativePathFromCwd }) {
|
|
|
3086
3389
|
validate: true
|
|
3087
3390
|
});
|
|
3088
3391
|
return {
|
|
3089
|
-
relativePathFromCwd:
|
|
3392
|
+
relativePathFromCwd: join12(RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH, filename),
|
|
3090
3393
|
frontmatter: subagent.getFrontmatter(),
|
|
3091
3394
|
body: subagent.getBody()
|
|
3092
3395
|
};
|
|
@@ -3115,7 +3418,7 @@ async function putSubagent({
|
|
|
3115
3418
|
try {
|
|
3116
3419
|
const existingSubagents = await listSubagents();
|
|
3117
3420
|
const isUpdate = existingSubagents.some(
|
|
3118
|
-
(subagent2) => subagent2.relativePathFromCwd ===
|
|
3421
|
+
(subagent2) => subagent2.relativePathFromCwd === join12(RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH, filename)
|
|
3119
3422
|
);
|
|
3120
3423
|
if (!isUpdate && existingSubagents.length >= maxSubagentsCount) {
|
|
3121
3424
|
throw new Error(
|
|
@@ -3130,11 +3433,11 @@ async function putSubagent({
|
|
|
3130
3433
|
body,
|
|
3131
3434
|
validate: true
|
|
3132
3435
|
});
|
|
3133
|
-
const subagentsDir =
|
|
3436
|
+
const subagentsDir = join12(process.cwd(), RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH);
|
|
3134
3437
|
await ensureDir(subagentsDir);
|
|
3135
3438
|
await writeFileContent(subagent.getFilePath(), subagent.getFileContent());
|
|
3136
3439
|
return {
|
|
3137
|
-
relativePathFromCwd:
|
|
3440
|
+
relativePathFromCwd: join12(RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH, filename),
|
|
3138
3441
|
frontmatter: subagent.getFrontmatter(),
|
|
3139
3442
|
body: subagent.getBody()
|
|
3140
3443
|
};
|
|
@@ -3150,11 +3453,11 @@ async function deleteSubagent({ relativePathFromCwd }) {
|
|
|
3150
3453
|
intendedRootDir: process.cwd()
|
|
3151
3454
|
});
|
|
3152
3455
|
const filename = basename4(relativePathFromCwd);
|
|
3153
|
-
const fullPath =
|
|
3456
|
+
const fullPath = join12(process.cwd(), RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH, filename);
|
|
3154
3457
|
try {
|
|
3155
3458
|
await removeFile(fullPath);
|
|
3156
3459
|
return {
|
|
3157
|
-
relativePathFromCwd:
|
|
3460
|
+
relativePathFromCwd: join12(RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH, filename)
|
|
3158
3461
|
};
|
|
3159
3462
|
} catch (error) {
|
|
3160
3463
|
throw new Error(
|
|
@@ -3182,7 +3485,7 @@ var subagentToolSchemas = {
|
|
|
3182
3485
|
var subagentTools = {
|
|
3183
3486
|
listSubagents: {
|
|
3184
3487
|
name: "listSubagents",
|
|
3185
|
-
description: `List all subagents from ${
|
|
3488
|
+
description: `List all subagents from ${join12(RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH, "*.md")} with their frontmatter.`,
|
|
3186
3489
|
parameters: subagentToolSchemas.listSubagents,
|
|
3187
3490
|
execute: async () => {
|
|
3188
3491
|
const subagents = await listSubagents();
|
|
@@ -3807,7 +4110,7 @@ async function updateCommand(currentVersion, options) {
|
|
|
3807
4110
|
}
|
|
3808
4111
|
|
|
3809
4112
|
// src/cli/index.ts
|
|
3810
|
-
var getVersion = () => "7.
|
|
4113
|
+
var getVersion = () => "7.15.0";
|
|
3811
4114
|
var main = async () => {
|
|
3812
4115
|
const program = new Command();
|
|
3813
4116
|
const version = getVersion();
|