rulesync 3.28.1 → 3.29.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 +27 -0
- package/dist/index.cjs +306 -40
- package/dist/index.js +307 -41
- package/package.json +9 -8
package/README.md
CHANGED
|
@@ -213,11 +213,28 @@ npx rulesync gitignore
|
|
|
213
213
|
|
|
214
214
|
You can configure Rulesync by creating a `rulesync.jsonc` file in the root of your project.
|
|
215
215
|
|
|
216
|
+
### JSON Schema Support
|
|
217
|
+
|
|
218
|
+
Rulesync provides a JSON Schema for editor validation and autocompletion. Add the `$schema` property to your `rulesync.jsonc`:
|
|
219
|
+
|
|
220
|
+
```jsonc
|
|
221
|
+
// rulesync.jsonc
|
|
222
|
+
{
|
|
223
|
+
"$schema": "https://raw.githubusercontent.com/dyoshikawa/rulesync/refs/heads/main/config-schema.json",
|
|
224
|
+
"targets": ["claudecode"],
|
|
225
|
+
"features": ["rules"]
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Configuration Options
|
|
230
|
+
|
|
216
231
|
Example:
|
|
217
232
|
|
|
218
233
|
```jsonc
|
|
219
234
|
// rulesync.jsonc
|
|
220
235
|
{
|
|
236
|
+
"$schema": "https://raw.githubusercontent.com/dyoshikawa/rulesync/refs/heads/main/config-schema.json",
|
|
237
|
+
|
|
221
238
|
// List of tools to generate configurations for. You can specify "*" to generate all tools.
|
|
222
239
|
"targets": ["cursor", "claudecode", "geminicli", "opencode", "codexcli"],
|
|
223
240
|
|
|
@@ -670,6 +687,16 @@ The Rulesync MCP server provides the following tools:
|
|
|
670
687
|
|
|
671
688
|
</details>
|
|
672
689
|
|
|
690
|
+
<details>
|
|
691
|
+
<summary>Skills Management</summary>
|
|
692
|
+
|
|
693
|
+
- `listSkills` - List all skill directories
|
|
694
|
+
- `getSkill` - Get a specific skill (SKILL.md and other files)
|
|
695
|
+
- `putSkill` - Create or update a skill directory
|
|
696
|
+
- `deleteSkill` - Delete a skill directory
|
|
697
|
+
|
|
698
|
+
</details>
|
|
699
|
+
|
|
673
700
|
<details>
|
|
674
701
|
<summary>Ignore Files Management</summary>
|
|
675
702
|
|
package/dist/index.cjs
CHANGED
|
@@ -290,6 +290,10 @@ var ConfigParamsSchema = import_mini3.z.object({
|
|
|
290
290
|
experimentalSimulateSubagents: (0, import_mini3.optional)(import_mini3.z.boolean())
|
|
291
291
|
});
|
|
292
292
|
var PartialConfigParamsSchema = import_mini3.z.partial(ConfigParamsSchema);
|
|
293
|
+
var ConfigFileSchema = import_mini3.z.object({
|
|
294
|
+
$schema: (0, import_mini3.optional)(import_mini3.z.string()),
|
|
295
|
+
...import_mini3.z.partial(ConfigParamsSchema).shape
|
|
296
|
+
});
|
|
293
297
|
var RequiredConfigParamsSchema = import_mini3.z.required(ConfigParamsSchema);
|
|
294
298
|
var Config = class {
|
|
295
299
|
baseDirs;
|
|
@@ -419,7 +423,9 @@ var ConfigResolver = class {
|
|
|
419
423
|
try {
|
|
420
424
|
const fileContent = await readFileContent(validatedConfigPath);
|
|
421
425
|
const jsonData = (0, import_jsonc_parser.parse)(fileContent);
|
|
422
|
-
|
|
426
|
+
const parsed = ConfigFileSchema.parse(jsonData);
|
|
427
|
+
const { $schema: _schema, ...configParams2 } = parsed;
|
|
428
|
+
configByFile = configParams2;
|
|
423
429
|
} catch (error) {
|
|
424
430
|
logger.error(`Failed to load config file: ${formatError(error)}`);
|
|
425
431
|
throw error;
|
|
@@ -665,6 +671,13 @@ var AiFile = class {
|
|
|
665
671
|
setFileContent(newFileContent) {
|
|
666
672
|
this.fileContent = newFileContent;
|
|
667
673
|
}
|
|
674
|
+
/**
|
|
675
|
+
* Returns whether this file can be deleted by rulesync.
|
|
676
|
+
* Override in subclasses that should not be deleted (e.g., user-managed config files).
|
|
677
|
+
*/
|
|
678
|
+
isDeletable() {
|
|
679
|
+
return true;
|
|
680
|
+
}
|
|
668
681
|
};
|
|
669
682
|
|
|
670
683
|
// src/features/commands/tool-command.ts
|
|
@@ -2324,6 +2337,13 @@ var ClaudecodeIgnore = class _ClaudecodeIgnore extends ToolIgnore {
|
|
|
2324
2337
|
relativeFilePath: "settings.local.json"
|
|
2325
2338
|
};
|
|
2326
2339
|
}
|
|
2340
|
+
/**
|
|
2341
|
+
* ClaudecodeIgnore uses settings.local.json which is a user-managed config file.
|
|
2342
|
+
* It should not be deleted by rulesync.
|
|
2343
|
+
*/
|
|
2344
|
+
isDeletable() {
|
|
2345
|
+
return false;
|
|
2346
|
+
}
|
|
2327
2347
|
toRulesyncIgnore() {
|
|
2328
2348
|
const rulesyncPatterns = this.patterns.map((pattern) => {
|
|
2329
2349
|
if (pattern.startsWith("Read(") && pattern.endsWith(")")) {
|
|
@@ -2814,7 +2834,7 @@ var IgnoreProcessor = class extends FeatureProcessor {
|
|
|
2814
2834
|
try {
|
|
2815
2835
|
const toolIgnores = await this.loadToolIgnores();
|
|
2816
2836
|
if (forDeletion) {
|
|
2817
|
-
return toolIgnores.filter((toolFile) =>
|
|
2837
|
+
return toolIgnores.filter((toolFile) => toolFile.isDeletable());
|
|
2818
2838
|
}
|
|
2819
2839
|
return toolIgnores;
|
|
2820
2840
|
} catch (error) {
|
|
@@ -3316,6 +3336,14 @@ var ClaudecodeMcp = class _ClaudecodeMcp extends ToolMcp {
|
|
|
3316
3336
|
getJson() {
|
|
3317
3337
|
return this.json;
|
|
3318
3338
|
}
|
|
3339
|
+
/**
|
|
3340
|
+
* In global mode, ~/.claude/.claude.json should not be deleted
|
|
3341
|
+
* as it may contain other user settings.
|
|
3342
|
+
* In local mode, .mcp.json can be safely deleted.
|
|
3343
|
+
*/
|
|
3344
|
+
isDeletable() {
|
|
3345
|
+
return !this.global;
|
|
3346
|
+
}
|
|
3319
3347
|
static getSettablePaths({ global } = {}) {
|
|
3320
3348
|
if (global) {
|
|
3321
3349
|
return {
|
|
@@ -3898,6 +3926,12 @@ var OpencodeMcp = class _OpencodeMcp extends ToolMcp {
|
|
|
3898
3926
|
getJson() {
|
|
3899
3927
|
return this.json;
|
|
3900
3928
|
}
|
|
3929
|
+
/**
|
|
3930
|
+
* opencode.json may contain other settings, so it should not be deleted.
|
|
3931
|
+
*/
|
|
3932
|
+
isDeletable() {
|
|
3933
|
+
return false;
|
|
3934
|
+
}
|
|
3901
3935
|
static getSettablePaths({ global } = {}) {
|
|
3902
3936
|
if (global) {
|
|
3903
3937
|
return {
|
|
@@ -4182,11 +4216,7 @@ var McpProcessor = class extends FeatureProcessor {
|
|
|
4182
4216
|
})();
|
|
4183
4217
|
logger.info(`Successfully loaded ${toolMcps.length} ${this.toolTarget} MCP files`);
|
|
4184
4218
|
if (forDeletion) {
|
|
4185
|
-
|
|
4186
|
-
if (this.global) {
|
|
4187
|
-
filteredMcps = filteredMcps.filter((toolFile) => !(toolFile instanceof ClaudecodeMcp));
|
|
4188
|
-
}
|
|
4189
|
-
return filteredMcps;
|
|
4219
|
+
return toolMcps.filter((toolFile) => toolFile.isDeletable());
|
|
4190
4220
|
}
|
|
4191
4221
|
return toolMcps;
|
|
4192
4222
|
} catch (error) {
|
|
@@ -4567,20 +4597,13 @@ var SimulatedSkill = class extends ToolSkill {
|
|
|
4567
4597
|
name: rulesyncFrontmatter.name,
|
|
4568
4598
|
description: rulesyncFrontmatter.description
|
|
4569
4599
|
};
|
|
4570
|
-
const otherFiles = rulesyncSkill.getOtherFiles();
|
|
4571
|
-
if (otherFiles.length > 0) {
|
|
4572
|
-
logger.warn(
|
|
4573
|
-
`Skill "${rulesyncFrontmatter.name}" has ${otherFiles.length} additional file(s) that will be ignored for simulated skill generation.`
|
|
4574
|
-
);
|
|
4575
|
-
}
|
|
4576
4600
|
return {
|
|
4577
4601
|
baseDir: rulesyncSkill.getBaseDir(),
|
|
4578
4602
|
relativeDirPath: this.getSettablePaths().relativeDirPath,
|
|
4579
4603
|
dirName: rulesyncSkill.getDirName(),
|
|
4580
4604
|
frontmatter: simulatedFrontmatter,
|
|
4581
4605
|
body: rulesyncSkill.getBody(),
|
|
4582
|
-
otherFiles:
|
|
4583
|
-
// Simulated skills ignore otherFiles
|
|
4606
|
+
otherFiles: rulesyncSkill.getOtherFiles(),
|
|
4584
4607
|
validate
|
|
4585
4608
|
};
|
|
4586
4609
|
}
|
|
@@ -4602,14 +4625,19 @@ var SimulatedSkill = class extends ToolSkill {
|
|
|
4602
4625
|
if (!result.success) {
|
|
4603
4626
|
throw new Error(`Invalid frontmatter in ${skillFilePath}: ${formatError(result.error)}`);
|
|
4604
4627
|
}
|
|
4628
|
+
const otherFiles = await this.collectOtherFiles(
|
|
4629
|
+
baseDir,
|
|
4630
|
+
actualRelativeDirPath,
|
|
4631
|
+
dirName,
|
|
4632
|
+
SKILL_FILE_NAME
|
|
4633
|
+
);
|
|
4605
4634
|
return {
|
|
4606
4635
|
baseDir,
|
|
4607
4636
|
relativeDirPath: actualRelativeDirPath,
|
|
4608
4637
|
dirName,
|
|
4609
4638
|
frontmatter: result.data,
|
|
4610
4639
|
body: content.trim(),
|
|
4611
|
-
otherFiles
|
|
4612
|
-
// Simulated skills ignore otherFiles
|
|
4640
|
+
otherFiles,
|
|
4613
4641
|
validate: true
|
|
4614
4642
|
};
|
|
4615
4643
|
}
|
|
@@ -10024,13 +10052,247 @@ var ruleTools = {
|
|
|
10024
10052
|
}
|
|
10025
10053
|
};
|
|
10026
10054
|
|
|
10027
|
-
// src/mcp/
|
|
10055
|
+
// src/mcp/skills.ts
|
|
10028
10056
|
var import_node_path88 = require("path");
|
|
10029
10057
|
var import_mini35 = require("zod/mini");
|
|
10058
|
+
var maxSkillSizeBytes = 1024 * 1024;
|
|
10059
|
+
var maxSkillsCount = 1e3;
|
|
10060
|
+
function aiDirFileToMcpSkillFile(file) {
|
|
10061
|
+
return {
|
|
10062
|
+
name: file.relativeFilePathToDirPath,
|
|
10063
|
+
body: file.fileBuffer.toString("utf-8")
|
|
10064
|
+
};
|
|
10065
|
+
}
|
|
10066
|
+
function mcpSkillFileToAiDirFile(file) {
|
|
10067
|
+
return {
|
|
10068
|
+
relativeFilePathToDirPath: file.name,
|
|
10069
|
+
fileBuffer: Buffer.from(file.body, "utf-8")
|
|
10070
|
+
};
|
|
10071
|
+
}
|
|
10072
|
+
function extractDirName(relativeDirPathFromCwd) {
|
|
10073
|
+
const dirName = (0, import_node_path88.basename)(relativeDirPathFromCwd);
|
|
10074
|
+
if (!dirName) {
|
|
10075
|
+
throw new Error(`Invalid path: ${relativeDirPathFromCwd}`);
|
|
10076
|
+
}
|
|
10077
|
+
return dirName;
|
|
10078
|
+
}
|
|
10079
|
+
async function listSkills() {
|
|
10080
|
+
const skillsDir = (0, import_node_path88.join)(process.cwd(), RULESYNC_SKILLS_RELATIVE_DIR_PATH);
|
|
10081
|
+
try {
|
|
10082
|
+
const skillDirPaths = await findFilesByGlobs((0, import_node_path88.join)(skillsDir, "*"), { type: "dir" });
|
|
10083
|
+
const skills = await Promise.all(
|
|
10084
|
+
skillDirPaths.map(async (dirPath) => {
|
|
10085
|
+
const dirName = (0, import_node_path88.basename)(dirPath);
|
|
10086
|
+
if (!dirName) return null;
|
|
10087
|
+
try {
|
|
10088
|
+
const skill = await RulesyncSkill.fromDir({
|
|
10089
|
+
dirName
|
|
10090
|
+
});
|
|
10091
|
+
const frontmatter = skill.getFrontmatter();
|
|
10092
|
+
return {
|
|
10093
|
+
relativeDirPathFromCwd: (0, import_node_path88.join)(RULESYNC_SKILLS_RELATIVE_DIR_PATH, dirName),
|
|
10094
|
+
frontmatter
|
|
10095
|
+
};
|
|
10096
|
+
} catch (error) {
|
|
10097
|
+
logger.error(`Failed to read skill directory ${dirName}: ${formatError(error)}`);
|
|
10098
|
+
return null;
|
|
10099
|
+
}
|
|
10100
|
+
})
|
|
10101
|
+
);
|
|
10102
|
+
return skills.filter((skill) => skill !== null);
|
|
10103
|
+
} catch (error) {
|
|
10104
|
+
logger.error(`Failed to read skills directory: ${formatError(error)}`);
|
|
10105
|
+
return [];
|
|
10106
|
+
}
|
|
10107
|
+
}
|
|
10108
|
+
async function getSkill({ relativeDirPathFromCwd }) {
|
|
10109
|
+
checkPathTraversal({
|
|
10110
|
+
relativePath: relativeDirPathFromCwd,
|
|
10111
|
+
intendedRootDir: process.cwd()
|
|
10112
|
+
});
|
|
10113
|
+
const dirName = extractDirName(relativeDirPathFromCwd);
|
|
10114
|
+
try {
|
|
10115
|
+
const skill = await RulesyncSkill.fromDir({
|
|
10116
|
+
dirName
|
|
10117
|
+
});
|
|
10118
|
+
return {
|
|
10119
|
+
relativeDirPathFromCwd: (0, import_node_path88.join)(RULESYNC_SKILLS_RELATIVE_DIR_PATH, dirName),
|
|
10120
|
+
frontmatter: skill.getFrontmatter(),
|
|
10121
|
+
body: skill.getBody(),
|
|
10122
|
+
otherFiles: skill.getOtherFiles().map(aiDirFileToMcpSkillFile)
|
|
10123
|
+
};
|
|
10124
|
+
} catch (error) {
|
|
10125
|
+
throw new Error(
|
|
10126
|
+
`Failed to read skill directory ${relativeDirPathFromCwd}: ${formatError(error)}`,
|
|
10127
|
+
{
|
|
10128
|
+
cause: error
|
|
10129
|
+
}
|
|
10130
|
+
);
|
|
10131
|
+
}
|
|
10132
|
+
}
|
|
10133
|
+
async function putSkill({
|
|
10134
|
+
relativeDirPathFromCwd,
|
|
10135
|
+
frontmatter,
|
|
10136
|
+
body,
|
|
10137
|
+
otherFiles = []
|
|
10138
|
+
}) {
|
|
10139
|
+
checkPathTraversal({
|
|
10140
|
+
relativePath: relativeDirPathFromCwd,
|
|
10141
|
+
intendedRootDir: process.cwd()
|
|
10142
|
+
});
|
|
10143
|
+
const dirName = extractDirName(relativeDirPathFromCwd);
|
|
10144
|
+
const estimatedSize = JSON.stringify(frontmatter).length + body.length + otherFiles.reduce((acc, file) => acc + file.name.length + file.body.length, 0);
|
|
10145
|
+
if (estimatedSize > maxSkillSizeBytes) {
|
|
10146
|
+
throw new Error(
|
|
10147
|
+
`Skill size ${estimatedSize} bytes exceeds maximum ${maxSkillSizeBytes} bytes (1MB)`
|
|
10148
|
+
);
|
|
10149
|
+
}
|
|
10150
|
+
try {
|
|
10151
|
+
const existingSkills = await listSkills();
|
|
10152
|
+
const isUpdate = existingSkills.some(
|
|
10153
|
+
(skill2) => skill2.relativeDirPathFromCwd === (0, import_node_path88.join)(RULESYNC_SKILLS_RELATIVE_DIR_PATH, dirName)
|
|
10154
|
+
);
|
|
10155
|
+
if (!isUpdate && existingSkills.length >= maxSkillsCount) {
|
|
10156
|
+
throw new Error(`Maximum number of skills (${maxSkillsCount}) reached`);
|
|
10157
|
+
}
|
|
10158
|
+
const aiDirFiles = otherFiles.map(mcpSkillFileToAiDirFile);
|
|
10159
|
+
const skill = new RulesyncSkill({
|
|
10160
|
+
baseDir: process.cwd(),
|
|
10161
|
+
relativeDirPath: RULESYNC_SKILLS_RELATIVE_DIR_PATH,
|
|
10162
|
+
dirName,
|
|
10163
|
+
frontmatter,
|
|
10164
|
+
body,
|
|
10165
|
+
otherFiles: aiDirFiles,
|
|
10166
|
+
validate: true
|
|
10167
|
+
});
|
|
10168
|
+
const skillDirPath = (0, import_node_path88.join)(process.cwd(), RULESYNC_SKILLS_RELATIVE_DIR_PATH, dirName);
|
|
10169
|
+
await ensureDir(skillDirPath);
|
|
10170
|
+
const skillFilePath = (0, import_node_path88.join)(skillDirPath, SKILL_FILE_NAME);
|
|
10171
|
+
const skillFileContent = stringifyFrontmatter(body, frontmatter);
|
|
10172
|
+
await writeFileContent(skillFilePath, skillFileContent);
|
|
10173
|
+
for (const file of otherFiles) {
|
|
10174
|
+
checkPathTraversal({
|
|
10175
|
+
relativePath: file.name,
|
|
10176
|
+
intendedRootDir: skillDirPath
|
|
10177
|
+
});
|
|
10178
|
+
const filePath = (0, import_node_path88.join)(skillDirPath, file.name);
|
|
10179
|
+
const fileDir = (0, import_node_path88.join)(skillDirPath, (0, import_node_path88.dirname)(file.name));
|
|
10180
|
+
if (fileDir !== skillDirPath) {
|
|
10181
|
+
await ensureDir(fileDir);
|
|
10182
|
+
}
|
|
10183
|
+
await writeFileContent(filePath, file.body);
|
|
10184
|
+
}
|
|
10185
|
+
return {
|
|
10186
|
+
relativeDirPathFromCwd: (0, import_node_path88.join)(RULESYNC_SKILLS_RELATIVE_DIR_PATH, dirName),
|
|
10187
|
+
frontmatter: skill.getFrontmatter(),
|
|
10188
|
+
body: skill.getBody(),
|
|
10189
|
+
otherFiles: skill.getOtherFiles().map(aiDirFileToMcpSkillFile)
|
|
10190
|
+
};
|
|
10191
|
+
} catch (error) {
|
|
10192
|
+
throw new Error(
|
|
10193
|
+
`Failed to write skill directory ${relativeDirPathFromCwd}: ${formatError(error)}`,
|
|
10194
|
+
{
|
|
10195
|
+
cause: error
|
|
10196
|
+
}
|
|
10197
|
+
);
|
|
10198
|
+
}
|
|
10199
|
+
}
|
|
10200
|
+
async function deleteSkill({
|
|
10201
|
+
relativeDirPathFromCwd
|
|
10202
|
+
}) {
|
|
10203
|
+
checkPathTraversal({
|
|
10204
|
+
relativePath: relativeDirPathFromCwd,
|
|
10205
|
+
intendedRootDir: process.cwd()
|
|
10206
|
+
});
|
|
10207
|
+
const dirName = extractDirName(relativeDirPathFromCwd);
|
|
10208
|
+
const skillDirPath = (0, import_node_path88.join)(process.cwd(), RULESYNC_SKILLS_RELATIVE_DIR_PATH, dirName);
|
|
10209
|
+
try {
|
|
10210
|
+
if (await directoryExists(skillDirPath)) {
|
|
10211
|
+
await removeDirectory(skillDirPath);
|
|
10212
|
+
}
|
|
10213
|
+
return {
|
|
10214
|
+
relativeDirPathFromCwd: (0, import_node_path88.join)(RULESYNC_SKILLS_RELATIVE_DIR_PATH, dirName)
|
|
10215
|
+
};
|
|
10216
|
+
} catch (error) {
|
|
10217
|
+
throw new Error(
|
|
10218
|
+
`Failed to delete skill directory ${relativeDirPathFromCwd}: ${formatError(error)}`,
|
|
10219
|
+
{
|
|
10220
|
+
cause: error
|
|
10221
|
+
}
|
|
10222
|
+
);
|
|
10223
|
+
}
|
|
10224
|
+
}
|
|
10225
|
+
var McpSkillFileSchema = import_mini35.z.object({
|
|
10226
|
+
name: import_mini35.z.string(),
|
|
10227
|
+
body: import_mini35.z.string()
|
|
10228
|
+
});
|
|
10229
|
+
var skillToolSchemas = {
|
|
10230
|
+
listSkills: import_mini35.z.object({}),
|
|
10231
|
+
getSkill: import_mini35.z.object({
|
|
10232
|
+
relativeDirPathFromCwd: import_mini35.z.string()
|
|
10233
|
+
}),
|
|
10234
|
+
putSkill: import_mini35.z.object({
|
|
10235
|
+
relativeDirPathFromCwd: import_mini35.z.string(),
|
|
10236
|
+
frontmatter: RulesyncSkillFrontmatterSchema,
|
|
10237
|
+
body: import_mini35.z.string(),
|
|
10238
|
+
otherFiles: import_mini35.z.optional(import_mini35.z.array(McpSkillFileSchema))
|
|
10239
|
+
}),
|
|
10240
|
+
deleteSkill: import_mini35.z.object({
|
|
10241
|
+
relativeDirPathFromCwd: import_mini35.z.string()
|
|
10242
|
+
})
|
|
10243
|
+
};
|
|
10244
|
+
var skillTools = {
|
|
10245
|
+
listSkills: {
|
|
10246
|
+
name: "listSkills",
|
|
10247
|
+
description: `List all skills from ${(0, import_node_path88.join)(RULESYNC_SKILLS_RELATIVE_DIR_PATH, "*", SKILL_FILE_NAME)} with their frontmatter.`,
|
|
10248
|
+
parameters: skillToolSchemas.listSkills,
|
|
10249
|
+
execute: async () => {
|
|
10250
|
+
const skills = await listSkills();
|
|
10251
|
+
const output = { skills };
|
|
10252
|
+
return JSON.stringify(output, null, 2);
|
|
10253
|
+
}
|
|
10254
|
+
},
|
|
10255
|
+
getSkill: {
|
|
10256
|
+
name: "getSkill",
|
|
10257
|
+
description: "Get detailed information about a specific skill including SKILL.md content and other files. relativeDirPathFromCwd parameter is required.",
|
|
10258
|
+
parameters: skillToolSchemas.getSkill,
|
|
10259
|
+
execute: async (args) => {
|
|
10260
|
+
const result = await getSkill({ relativeDirPathFromCwd: args.relativeDirPathFromCwd });
|
|
10261
|
+
return JSON.stringify(result, null, 2);
|
|
10262
|
+
}
|
|
10263
|
+
},
|
|
10264
|
+
putSkill: {
|
|
10265
|
+
name: "putSkill",
|
|
10266
|
+
description: "Create or update a skill (upsert operation). relativeDirPathFromCwd, frontmatter, and body parameters are required. otherFiles is optional.",
|
|
10267
|
+
parameters: skillToolSchemas.putSkill,
|
|
10268
|
+
execute: async (args) => {
|
|
10269
|
+
const result = await putSkill({
|
|
10270
|
+
relativeDirPathFromCwd: args.relativeDirPathFromCwd,
|
|
10271
|
+
frontmatter: args.frontmatter,
|
|
10272
|
+
body: args.body,
|
|
10273
|
+
otherFiles: args.otherFiles
|
|
10274
|
+
});
|
|
10275
|
+
return JSON.stringify(result, null, 2);
|
|
10276
|
+
}
|
|
10277
|
+
},
|
|
10278
|
+
deleteSkill: {
|
|
10279
|
+
name: "deleteSkill",
|
|
10280
|
+
description: "Delete a skill directory and all its contents. relativeDirPathFromCwd parameter is required.",
|
|
10281
|
+
parameters: skillToolSchemas.deleteSkill,
|
|
10282
|
+
execute: async (args) => {
|
|
10283
|
+
const result = await deleteSkill({ relativeDirPathFromCwd: args.relativeDirPathFromCwd });
|
|
10284
|
+
return JSON.stringify(result, null, 2);
|
|
10285
|
+
}
|
|
10286
|
+
}
|
|
10287
|
+
};
|
|
10288
|
+
|
|
10289
|
+
// src/mcp/subagents.ts
|
|
10290
|
+
var import_node_path89 = require("path");
|
|
10291
|
+
var import_mini36 = require("zod/mini");
|
|
10030
10292
|
var maxSubagentSizeBytes = 1024 * 1024;
|
|
10031
10293
|
var maxSubagentsCount = 1e3;
|
|
10032
10294
|
async function listSubagents() {
|
|
10033
|
-
const subagentsDir = (0,
|
|
10295
|
+
const subagentsDir = (0, import_node_path89.join)(process.cwd(), RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH);
|
|
10034
10296
|
try {
|
|
10035
10297
|
const files = await listDirectoryFiles(subagentsDir);
|
|
10036
10298
|
const mdFiles = files.filter((file) => file.endsWith(".md"));
|
|
@@ -10043,7 +10305,7 @@ async function listSubagents() {
|
|
|
10043
10305
|
});
|
|
10044
10306
|
const frontmatter = subagent.getFrontmatter();
|
|
10045
10307
|
return {
|
|
10046
|
-
relativePathFromCwd: (0,
|
|
10308
|
+
relativePathFromCwd: (0, import_node_path89.join)(RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH, file),
|
|
10047
10309
|
frontmatter
|
|
10048
10310
|
};
|
|
10049
10311
|
} catch (error) {
|
|
@@ -10065,14 +10327,14 @@ async function getSubagent({ relativePathFromCwd }) {
|
|
|
10065
10327
|
relativePath: relativePathFromCwd,
|
|
10066
10328
|
intendedRootDir: process.cwd()
|
|
10067
10329
|
});
|
|
10068
|
-
const filename = (0,
|
|
10330
|
+
const filename = (0, import_node_path89.basename)(relativePathFromCwd);
|
|
10069
10331
|
try {
|
|
10070
10332
|
const subagent = await RulesyncSubagent.fromFile({
|
|
10071
10333
|
relativeFilePath: filename,
|
|
10072
10334
|
validate: true
|
|
10073
10335
|
});
|
|
10074
10336
|
return {
|
|
10075
|
-
relativePathFromCwd: (0,
|
|
10337
|
+
relativePathFromCwd: (0, import_node_path89.join)(RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH, filename),
|
|
10076
10338
|
frontmatter: subagent.getFrontmatter(),
|
|
10077
10339
|
body: subagent.getBody()
|
|
10078
10340
|
};
|
|
@@ -10091,7 +10353,7 @@ async function putSubagent({
|
|
|
10091
10353
|
relativePath: relativePathFromCwd,
|
|
10092
10354
|
intendedRootDir: process.cwd()
|
|
10093
10355
|
});
|
|
10094
|
-
const filename = (0,
|
|
10356
|
+
const filename = (0, import_node_path89.basename)(relativePathFromCwd);
|
|
10095
10357
|
const estimatedSize = JSON.stringify(frontmatter).length + body.length;
|
|
10096
10358
|
if (estimatedSize > maxSubagentSizeBytes) {
|
|
10097
10359
|
throw new Error(
|
|
@@ -10101,7 +10363,7 @@ async function putSubagent({
|
|
|
10101
10363
|
try {
|
|
10102
10364
|
const existingSubagents = await listSubagents();
|
|
10103
10365
|
const isUpdate = existingSubagents.some(
|
|
10104
|
-
(subagent2) => subagent2.relativePathFromCwd === (0,
|
|
10366
|
+
(subagent2) => subagent2.relativePathFromCwd === (0, import_node_path89.join)(RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH, filename)
|
|
10105
10367
|
);
|
|
10106
10368
|
if (!isUpdate && existingSubagents.length >= maxSubagentsCount) {
|
|
10107
10369
|
throw new Error(`Maximum number of subagents (${maxSubagentsCount}) reached`);
|
|
@@ -10114,11 +10376,11 @@ async function putSubagent({
|
|
|
10114
10376
|
body,
|
|
10115
10377
|
validate: true
|
|
10116
10378
|
});
|
|
10117
|
-
const subagentsDir = (0,
|
|
10379
|
+
const subagentsDir = (0, import_node_path89.join)(process.cwd(), RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH);
|
|
10118
10380
|
await ensureDir(subagentsDir);
|
|
10119
10381
|
await writeFileContent(subagent.getFilePath(), subagent.getFileContent());
|
|
10120
10382
|
return {
|
|
10121
|
-
relativePathFromCwd: (0,
|
|
10383
|
+
relativePathFromCwd: (0, import_node_path89.join)(RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH, filename),
|
|
10122
10384
|
frontmatter: subagent.getFrontmatter(),
|
|
10123
10385
|
body: subagent.getBody()
|
|
10124
10386
|
};
|
|
@@ -10133,12 +10395,12 @@ async function deleteSubagent({ relativePathFromCwd }) {
|
|
|
10133
10395
|
relativePath: relativePathFromCwd,
|
|
10134
10396
|
intendedRootDir: process.cwd()
|
|
10135
10397
|
});
|
|
10136
|
-
const filename = (0,
|
|
10137
|
-
const fullPath = (0,
|
|
10398
|
+
const filename = (0, import_node_path89.basename)(relativePathFromCwd);
|
|
10399
|
+
const fullPath = (0, import_node_path89.join)(process.cwd(), RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH, filename);
|
|
10138
10400
|
try {
|
|
10139
10401
|
await removeFile(fullPath);
|
|
10140
10402
|
return {
|
|
10141
|
-
relativePathFromCwd: (0,
|
|
10403
|
+
relativePathFromCwd: (0, import_node_path89.join)(RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH, filename)
|
|
10142
10404
|
};
|
|
10143
10405
|
} catch (error) {
|
|
10144
10406
|
throw new Error(
|
|
@@ -10150,23 +10412,23 @@ async function deleteSubagent({ relativePathFromCwd }) {
|
|
|
10150
10412
|
}
|
|
10151
10413
|
}
|
|
10152
10414
|
var subagentToolSchemas = {
|
|
10153
|
-
listSubagents:
|
|
10154
|
-
getSubagent:
|
|
10155
|
-
relativePathFromCwd:
|
|
10415
|
+
listSubagents: import_mini36.z.object({}),
|
|
10416
|
+
getSubagent: import_mini36.z.object({
|
|
10417
|
+
relativePathFromCwd: import_mini36.z.string()
|
|
10156
10418
|
}),
|
|
10157
|
-
putSubagent:
|
|
10158
|
-
relativePathFromCwd:
|
|
10419
|
+
putSubagent: import_mini36.z.object({
|
|
10420
|
+
relativePathFromCwd: import_mini36.z.string(),
|
|
10159
10421
|
frontmatter: RulesyncSubagentFrontmatterSchema,
|
|
10160
|
-
body:
|
|
10422
|
+
body: import_mini36.z.string()
|
|
10161
10423
|
}),
|
|
10162
|
-
deleteSubagent:
|
|
10163
|
-
relativePathFromCwd:
|
|
10424
|
+
deleteSubagent: import_mini36.z.object({
|
|
10425
|
+
relativePathFromCwd: import_mini36.z.string()
|
|
10164
10426
|
})
|
|
10165
10427
|
};
|
|
10166
10428
|
var subagentTools = {
|
|
10167
10429
|
listSubagents: {
|
|
10168
10430
|
name: "listSubagents",
|
|
10169
|
-
description: `List all subagents from ${(0,
|
|
10431
|
+
description: `List all subagents from ${(0, import_node_path89.join)(RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH, "*.md")} with their frontmatter.`,
|
|
10170
10432
|
parameters: subagentToolSchemas.listSubagents,
|
|
10171
10433
|
execute: async () => {
|
|
10172
10434
|
const subagents = await listSubagents();
|
|
@@ -10213,7 +10475,7 @@ async function mcpCommand({ version }) {
|
|
|
10213
10475
|
name: "Rulesync MCP Server",
|
|
10214
10476
|
// eslint-disable-next-line no-type-assertion/no-type-assertion
|
|
10215
10477
|
version,
|
|
10216
|
-
instructions: "This server handles Rulesync files including rules, commands, MCP, ignore files, and
|
|
10478
|
+
instructions: "This server handles Rulesync files including rules, commands, MCP, ignore files, subagents and skills for any AI agents. It should be used when you need those files."
|
|
10217
10479
|
});
|
|
10218
10480
|
server.addTool(ruleTools.listRules);
|
|
10219
10481
|
server.addTool(ruleTools.getRule);
|
|
@@ -10227,6 +10489,10 @@ async function mcpCommand({ version }) {
|
|
|
10227
10489
|
server.addTool(subagentTools.getSubagent);
|
|
10228
10490
|
server.addTool(subagentTools.putSubagent);
|
|
10229
10491
|
server.addTool(subagentTools.deleteSubagent);
|
|
10492
|
+
server.addTool(skillTools.listSkills);
|
|
10493
|
+
server.addTool(skillTools.getSkill);
|
|
10494
|
+
server.addTool(skillTools.putSkill);
|
|
10495
|
+
server.addTool(skillTools.deleteSkill);
|
|
10230
10496
|
server.addTool(ignoreTools.getIgnoreFile);
|
|
10231
10497
|
server.addTool(ignoreTools.putIgnoreFile);
|
|
10232
10498
|
server.addTool(ignoreTools.deleteIgnoreFile);
|
|
@@ -10240,7 +10506,7 @@ async function mcpCommand({ version }) {
|
|
|
10240
10506
|
}
|
|
10241
10507
|
|
|
10242
10508
|
// src/cli/index.ts
|
|
10243
|
-
var getVersion = () => "3.
|
|
10509
|
+
var getVersion = () => "3.29.0";
|
|
10244
10510
|
var main = async () => {
|
|
10245
10511
|
const program = new import_commander.Command();
|
|
10246
10512
|
const version = getVersion();
|
package/dist/index.js
CHANGED
|
@@ -267,6 +267,10 @@ var ConfigParamsSchema = z3.object({
|
|
|
267
267
|
experimentalSimulateSubagents: optional(z3.boolean())
|
|
268
268
|
});
|
|
269
269
|
var PartialConfigParamsSchema = z3.partial(ConfigParamsSchema);
|
|
270
|
+
var ConfigFileSchema = z3.object({
|
|
271
|
+
$schema: optional(z3.string()),
|
|
272
|
+
...z3.partial(ConfigParamsSchema).shape
|
|
273
|
+
});
|
|
270
274
|
var RequiredConfigParamsSchema = z3.required(ConfigParamsSchema);
|
|
271
275
|
var Config = class {
|
|
272
276
|
baseDirs;
|
|
@@ -396,7 +400,9 @@ var ConfigResolver = class {
|
|
|
396
400
|
try {
|
|
397
401
|
const fileContent = await readFileContent(validatedConfigPath);
|
|
398
402
|
const jsonData = parseJsonc(fileContent);
|
|
399
|
-
|
|
403
|
+
const parsed = ConfigFileSchema.parse(jsonData);
|
|
404
|
+
const { $schema: _schema, ...configParams2 } = parsed;
|
|
405
|
+
configByFile = configParams2;
|
|
400
406
|
} catch (error) {
|
|
401
407
|
logger.error(`Failed to load config file: ${formatError(error)}`);
|
|
402
408
|
throw error;
|
|
@@ -642,6 +648,13 @@ var AiFile = class {
|
|
|
642
648
|
setFileContent(newFileContent) {
|
|
643
649
|
this.fileContent = newFileContent;
|
|
644
650
|
}
|
|
651
|
+
/**
|
|
652
|
+
* Returns whether this file can be deleted by rulesync.
|
|
653
|
+
* Override in subclasses that should not be deleted (e.g., user-managed config files).
|
|
654
|
+
*/
|
|
655
|
+
isDeletable() {
|
|
656
|
+
return true;
|
|
657
|
+
}
|
|
645
658
|
};
|
|
646
659
|
|
|
647
660
|
// src/features/commands/tool-command.ts
|
|
@@ -2301,6 +2314,13 @@ var ClaudecodeIgnore = class _ClaudecodeIgnore extends ToolIgnore {
|
|
|
2301
2314
|
relativeFilePath: "settings.local.json"
|
|
2302
2315
|
};
|
|
2303
2316
|
}
|
|
2317
|
+
/**
|
|
2318
|
+
* ClaudecodeIgnore uses settings.local.json which is a user-managed config file.
|
|
2319
|
+
* It should not be deleted by rulesync.
|
|
2320
|
+
*/
|
|
2321
|
+
isDeletable() {
|
|
2322
|
+
return false;
|
|
2323
|
+
}
|
|
2304
2324
|
toRulesyncIgnore() {
|
|
2305
2325
|
const rulesyncPatterns = this.patterns.map((pattern) => {
|
|
2306
2326
|
if (pattern.startsWith("Read(") && pattern.endsWith(")")) {
|
|
@@ -2791,7 +2811,7 @@ var IgnoreProcessor = class extends FeatureProcessor {
|
|
|
2791
2811
|
try {
|
|
2792
2812
|
const toolIgnores = await this.loadToolIgnores();
|
|
2793
2813
|
if (forDeletion) {
|
|
2794
|
-
return toolIgnores.filter((toolFile) =>
|
|
2814
|
+
return toolIgnores.filter((toolFile) => toolFile.isDeletable());
|
|
2795
2815
|
}
|
|
2796
2816
|
return toolIgnores;
|
|
2797
2817
|
} catch (error) {
|
|
@@ -3293,6 +3313,14 @@ var ClaudecodeMcp = class _ClaudecodeMcp extends ToolMcp {
|
|
|
3293
3313
|
getJson() {
|
|
3294
3314
|
return this.json;
|
|
3295
3315
|
}
|
|
3316
|
+
/**
|
|
3317
|
+
* In global mode, ~/.claude/.claude.json should not be deleted
|
|
3318
|
+
* as it may contain other user settings.
|
|
3319
|
+
* In local mode, .mcp.json can be safely deleted.
|
|
3320
|
+
*/
|
|
3321
|
+
isDeletable() {
|
|
3322
|
+
return !this.global;
|
|
3323
|
+
}
|
|
3296
3324
|
static getSettablePaths({ global } = {}) {
|
|
3297
3325
|
if (global) {
|
|
3298
3326
|
return {
|
|
@@ -3875,6 +3903,12 @@ var OpencodeMcp = class _OpencodeMcp extends ToolMcp {
|
|
|
3875
3903
|
getJson() {
|
|
3876
3904
|
return this.json;
|
|
3877
3905
|
}
|
|
3906
|
+
/**
|
|
3907
|
+
* opencode.json may contain other settings, so it should not be deleted.
|
|
3908
|
+
*/
|
|
3909
|
+
isDeletable() {
|
|
3910
|
+
return false;
|
|
3911
|
+
}
|
|
3878
3912
|
static getSettablePaths({ global } = {}) {
|
|
3879
3913
|
if (global) {
|
|
3880
3914
|
return {
|
|
@@ -4159,11 +4193,7 @@ var McpProcessor = class extends FeatureProcessor {
|
|
|
4159
4193
|
})();
|
|
4160
4194
|
logger.info(`Successfully loaded ${toolMcps.length} ${this.toolTarget} MCP files`);
|
|
4161
4195
|
if (forDeletion) {
|
|
4162
|
-
|
|
4163
|
-
if (this.global) {
|
|
4164
|
-
filteredMcps = filteredMcps.filter((toolFile) => !(toolFile instanceof ClaudecodeMcp));
|
|
4165
|
-
}
|
|
4166
|
-
return filteredMcps;
|
|
4196
|
+
return toolMcps.filter((toolFile) => toolFile.isDeletable());
|
|
4167
4197
|
}
|
|
4168
4198
|
return toolMcps;
|
|
4169
4199
|
} catch (error) {
|
|
@@ -4544,20 +4574,13 @@ var SimulatedSkill = class extends ToolSkill {
|
|
|
4544
4574
|
name: rulesyncFrontmatter.name,
|
|
4545
4575
|
description: rulesyncFrontmatter.description
|
|
4546
4576
|
};
|
|
4547
|
-
const otherFiles = rulesyncSkill.getOtherFiles();
|
|
4548
|
-
if (otherFiles.length > 0) {
|
|
4549
|
-
logger.warn(
|
|
4550
|
-
`Skill "${rulesyncFrontmatter.name}" has ${otherFiles.length} additional file(s) that will be ignored for simulated skill generation.`
|
|
4551
|
-
);
|
|
4552
|
-
}
|
|
4553
4577
|
return {
|
|
4554
4578
|
baseDir: rulesyncSkill.getBaseDir(),
|
|
4555
4579
|
relativeDirPath: this.getSettablePaths().relativeDirPath,
|
|
4556
4580
|
dirName: rulesyncSkill.getDirName(),
|
|
4557
4581
|
frontmatter: simulatedFrontmatter,
|
|
4558
4582
|
body: rulesyncSkill.getBody(),
|
|
4559
|
-
otherFiles:
|
|
4560
|
-
// Simulated skills ignore otherFiles
|
|
4583
|
+
otherFiles: rulesyncSkill.getOtherFiles(),
|
|
4561
4584
|
validate
|
|
4562
4585
|
};
|
|
4563
4586
|
}
|
|
@@ -4579,14 +4602,19 @@ var SimulatedSkill = class extends ToolSkill {
|
|
|
4579
4602
|
if (!result.success) {
|
|
4580
4603
|
throw new Error(`Invalid frontmatter in ${skillFilePath}: ${formatError(result.error)}`);
|
|
4581
4604
|
}
|
|
4605
|
+
const otherFiles = await this.collectOtherFiles(
|
|
4606
|
+
baseDir,
|
|
4607
|
+
actualRelativeDirPath,
|
|
4608
|
+
dirName,
|
|
4609
|
+
SKILL_FILE_NAME
|
|
4610
|
+
);
|
|
4582
4611
|
return {
|
|
4583
4612
|
baseDir,
|
|
4584
4613
|
relativeDirPath: actualRelativeDirPath,
|
|
4585
4614
|
dirName,
|
|
4586
4615
|
frontmatter: result.data,
|
|
4587
4616
|
body: content.trim(),
|
|
4588
|
-
otherFiles
|
|
4589
|
-
// Simulated skills ignore otherFiles
|
|
4617
|
+
otherFiles,
|
|
4590
4618
|
validate: true
|
|
4591
4619
|
};
|
|
4592
4620
|
}
|
|
@@ -10001,13 +10029,247 @@ var ruleTools = {
|
|
|
10001
10029
|
}
|
|
10002
10030
|
};
|
|
10003
10031
|
|
|
10004
|
-
// src/mcp/
|
|
10005
|
-
import { basename as basename23, join as join86 } from "path";
|
|
10032
|
+
// src/mcp/skills.ts
|
|
10033
|
+
import { basename as basename23, dirname as dirname2, join as join86 } from "path";
|
|
10006
10034
|
import { z as z35 } from "zod/mini";
|
|
10035
|
+
var maxSkillSizeBytes = 1024 * 1024;
|
|
10036
|
+
var maxSkillsCount = 1e3;
|
|
10037
|
+
function aiDirFileToMcpSkillFile(file) {
|
|
10038
|
+
return {
|
|
10039
|
+
name: file.relativeFilePathToDirPath,
|
|
10040
|
+
body: file.fileBuffer.toString("utf-8")
|
|
10041
|
+
};
|
|
10042
|
+
}
|
|
10043
|
+
function mcpSkillFileToAiDirFile(file) {
|
|
10044
|
+
return {
|
|
10045
|
+
relativeFilePathToDirPath: file.name,
|
|
10046
|
+
fileBuffer: Buffer.from(file.body, "utf-8")
|
|
10047
|
+
};
|
|
10048
|
+
}
|
|
10049
|
+
function extractDirName(relativeDirPathFromCwd) {
|
|
10050
|
+
const dirName = basename23(relativeDirPathFromCwd);
|
|
10051
|
+
if (!dirName) {
|
|
10052
|
+
throw new Error(`Invalid path: ${relativeDirPathFromCwd}`);
|
|
10053
|
+
}
|
|
10054
|
+
return dirName;
|
|
10055
|
+
}
|
|
10056
|
+
async function listSkills() {
|
|
10057
|
+
const skillsDir = join86(process.cwd(), RULESYNC_SKILLS_RELATIVE_DIR_PATH);
|
|
10058
|
+
try {
|
|
10059
|
+
const skillDirPaths = await findFilesByGlobs(join86(skillsDir, "*"), { type: "dir" });
|
|
10060
|
+
const skills = await Promise.all(
|
|
10061
|
+
skillDirPaths.map(async (dirPath) => {
|
|
10062
|
+
const dirName = basename23(dirPath);
|
|
10063
|
+
if (!dirName) return null;
|
|
10064
|
+
try {
|
|
10065
|
+
const skill = await RulesyncSkill.fromDir({
|
|
10066
|
+
dirName
|
|
10067
|
+
});
|
|
10068
|
+
const frontmatter = skill.getFrontmatter();
|
|
10069
|
+
return {
|
|
10070
|
+
relativeDirPathFromCwd: join86(RULESYNC_SKILLS_RELATIVE_DIR_PATH, dirName),
|
|
10071
|
+
frontmatter
|
|
10072
|
+
};
|
|
10073
|
+
} catch (error) {
|
|
10074
|
+
logger.error(`Failed to read skill directory ${dirName}: ${formatError(error)}`);
|
|
10075
|
+
return null;
|
|
10076
|
+
}
|
|
10077
|
+
})
|
|
10078
|
+
);
|
|
10079
|
+
return skills.filter((skill) => skill !== null);
|
|
10080
|
+
} catch (error) {
|
|
10081
|
+
logger.error(`Failed to read skills directory: ${formatError(error)}`);
|
|
10082
|
+
return [];
|
|
10083
|
+
}
|
|
10084
|
+
}
|
|
10085
|
+
async function getSkill({ relativeDirPathFromCwd }) {
|
|
10086
|
+
checkPathTraversal({
|
|
10087
|
+
relativePath: relativeDirPathFromCwd,
|
|
10088
|
+
intendedRootDir: process.cwd()
|
|
10089
|
+
});
|
|
10090
|
+
const dirName = extractDirName(relativeDirPathFromCwd);
|
|
10091
|
+
try {
|
|
10092
|
+
const skill = await RulesyncSkill.fromDir({
|
|
10093
|
+
dirName
|
|
10094
|
+
});
|
|
10095
|
+
return {
|
|
10096
|
+
relativeDirPathFromCwd: join86(RULESYNC_SKILLS_RELATIVE_DIR_PATH, dirName),
|
|
10097
|
+
frontmatter: skill.getFrontmatter(),
|
|
10098
|
+
body: skill.getBody(),
|
|
10099
|
+
otherFiles: skill.getOtherFiles().map(aiDirFileToMcpSkillFile)
|
|
10100
|
+
};
|
|
10101
|
+
} catch (error) {
|
|
10102
|
+
throw new Error(
|
|
10103
|
+
`Failed to read skill directory ${relativeDirPathFromCwd}: ${formatError(error)}`,
|
|
10104
|
+
{
|
|
10105
|
+
cause: error
|
|
10106
|
+
}
|
|
10107
|
+
);
|
|
10108
|
+
}
|
|
10109
|
+
}
|
|
10110
|
+
async function putSkill({
|
|
10111
|
+
relativeDirPathFromCwd,
|
|
10112
|
+
frontmatter,
|
|
10113
|
+
body,
|
|
10114
|
+
otherFiles = []
|
|
10115
|
+
}) {
|
|
10116
|
+
checkPathTraversal({
|
|
10117
|
+
relativePath: relativeDirPathFromCwd,
|
|
10118
|
+
intendedRootDir: process.cwd()
|
|
10119
|
+
});
|
|
10120
|
+
const dirName = extractDirName(relativeDirPathFromCwd);
|
|
10121
|
+
const estimatedSize = JSON.stringify(frontmatter).length + body.length + otherFiles.reduce((acc, file) => acc + file.name.length + file.body.length, 0);
|
|
10122
|
+
if (estimatedSize > maxSkillSizeBytes) {
|
|
10123
|
+
throw new Error(
|
|
10124
|
+
`Skill size ${estimatedSize} bytes exceeds maximum ${maxSkillSizeBytes} bytes (1MB)`
|
|
10125
|
+
);
|
|
10126
|
+
}
|
|
10127
|
+
try {
|
|
10128
|
+
const existingSkills = await listSkills();
|
|
10129
|
+
const isUpdate = existingSkills.some(
|
|
10130
|
+
(skill2) => skill2.relativeDirPathFromCwd === join86(RULESYNC_SKILLS_RELATIVE_DIR_PATH, dirName)
|
|
10131
|
+
);
|
|
10132
|
+
if (!isUpdate && existingSkills.length >= maxSkillsCount) {
|
|
10133
|
+
throw new Error(`Maximum number of skills (${maxSkillsCount}) reached`);
|
|
10134
|
+
}
|
|
10135
|
+
const aiDirFiles = otherFiles.map(mcpSkillFileToAiDirFile);
|
|
10136
|
+
const skill = new RulesyncSkill({
|
|
10137
|
+
baseDir: process.cwd(),
|
|
10138
|
+
relativeDirPath: RULESYNC_SKILLS_RELATIVE_DIR_PATH,
|
|
10139
|
+
dirName,
|
|
10140
|
+
frontmatter,
|
|
10141
|
+
body,
|
|
10142
|
+
otherFiles: aiDirFiles,
|
|
10143
|
+
validate: true
|
|
10144
|
+
});
|
|
10145
|
+
const skillDirPath = join86(process.cwd(), RULESYNC_SKILLS_RELATIVE_DIR_PATH, dirName);
|
|
10146
|
+
await ensureDir(skillDirPath);
|
|
10147
|
+
const skillFilePath = join86(skillDirPath, SKILL_FILE_NAME);
|
|
10148
|
+
const skillFileContent = stringifyFrontmatter(body, frontmatter);
|
|
10149
|
+
await writeFileContent(skillFilePath, skillFileContent);
|
|
10150
|
+
for (const file of otherFiles) {
|
|
10151
|
+
checkPathTraversal({
|
|
10152
|
+
relativePath: file.name,
|
|
10153
|
+
intendedRootDir: skillDirPath
|
|
10154
|
+
});
|
|
10155
|
+
const filePath = join86(skillDirPath, file.name);
|
|
10156
|
+
const fileDir = join86(skillDirPath, dirname2(file.name));
|
|
10157
|
+
if (fileDir !== skillDirPath) {
|
|
10158
|
+
await ensureDir(fileDir);
|
|
10159
|
+
}
|
|
10160
|
+
await writeFileContent(filePath, file.body);
|
|
10161
|
+
}
|
|
10162
|
+
return {
|
|
10163
|
+
relativeDirPathFromCwd: join86(RULESYNC_SKILLS_RELATIVE_DIR_PATH, dirName),
|
|
10164
|
+
frontmatter: skill.getFrontmatter(),
|
|
10165
|
+
body: skill.getBody(),
|
|
10166
|
+
otherFiles: skill.getOtherFiles().map(aiDirFileToMcpSkillFile)
|
|
10167
|
+
};
|
|
10168
|
+
} catch (error) {
|
|
10169
|
+
throw new Error(
|
|
10170
|
+
`Failed to write skill directory ${relativeDirPathFromCwd}: ${formatError(error)}`,
|
|
10171
|
+
{
|
|
10172
|
+
cause: error
|
|
10173
|
+
}
|
|
10174
|
+
);
|
|
10175
|
+
}
|
|
10176
|
+
}
|
|
10177
|
+
async function deleteSkill({
|
|
10178
|
+
relativeDirPathFromCwd
|
|
10179
|
+
}) {
|
|
10180
|
+
checkPathTraversal({
|
|
10181
|
+
relativePath: relativeDirPathFromCwd,
|
|
10182
|
+
intendedRootDir: process.cwd()
|
|
10183
|
+
});
|
|
10184
|
+
const dirName = extractDirName(relativeDirPathFromCwd);
|
|
10185
|
+
const skillDirPath = join86(process.cwd(), RULESYNC_SKILLS_RELATIVE_DIR_PATH, dirName);
|
|
10186
|
+
try {
|
|
10187
|
+
if (await directoryExists(skillDirPath)) {
|
|
10188
|
+
await removeDirectory(skillDirPath);
|
|
10189
|
+
}
|
|
10190
|
+
return {
|
|
10191
|
+
relativeDirPathFromCwd: join86(RULESYNC_SKILLS_RELATIVE_DIR_PATH, dirName)
|
|
10192
|
+
};
|
|
10193
|
+
} catch (error) {
|
|
10194
|
+
throw new Error(
|
|
10195
|
+
`Failed to delete skill directory ${relativeDirPathFromCwd}: ${formatError(error)}`,
|
|
10196
|
+
{
|
|
10197
|
+
cause: error
|
|
10198
|
+
}
|
|
10199
|
+
);
|
|
10200
|
+
}
|
|
10201
|
+
}
|
|
10202
|
+
var McpSkillFileSchema = z35.object({
|
|
10203
|
+
name: z35.string(),
|
|
10204
|
+
body: z35.string()
|
|
10205
|
+
});
|
|
10206
|
+
var skillToolSchemas = {
|
|
10207
|
+
listSkills: z35.object({}),
|
|
10208
|
+
getSkill: z35.object({
|
|
10209
|
+
relativeDirPathFromCwd: z35.string()
|
|
10210
|
+
}),
|
|
10211
|
+
putSkill: z35.object({
|
|
10212
|
+
relativeDirPathFromCwd: z35.string(),
|
|
10213
|
+
frontmatter: RulesyncSkillFrontmatterSchema,
|
|
10214
|
+
body: z35.string(),
|
|
10215
|
+
otherFiles: z35.optional(z35.array(McpSkillFileSchema))
|
|
10216
|
+
}),
|
|
10217
|
+
deleteSkill: z35.object({
|
|
10218
|
+
relativeDirPathFromCwd: z35.string()
|
|
10219
|
+
})
|
|
10220
|
+
};
|
|
10221
|
+
var skillTools = {
|
|
10222
|
+
listSkills: {
|
|
10223
|
+
name: "listSkills",
|
|
10224
|
+
description: `List all skills from ${join86(RULESYNC_SKILLS_RELATIVE_DIR_PATH, "*", SKILL_FILE_NAME)} with their frontmatter.`,
|
|
10225
|
+
parameters: skillToolSchemas.listSkills,
|
|
10226
|
+
execute: async () => {
|
|
10227
|
+
const skills = await listSkills();
|
|
10228
|
+
const output = { skills };
|
|
10229
|
+
return JSON.stringify(output, null, 2);
|
|
10230
|
+
}
|
|
10231
|
+
},
|
|
10232
|
+
getSkill: {
|
|
10233
|
+
name: "getSkill",
|
|
10234
|
+
description: "Get detailed information about a specific skill including SKILL.md content and other files. relativeDirPathFromCwd parameter is required.",
|
|
10235
|
+
parameters: skillToolSchemas.getSkill,
|
|
10236
|
+
execute: async (args) => {
|
|
10237
|
+
const result = await getSkill({ relativeDirPathFromCwd: args.relativeDirPathFromCwd });
|
|
10238
|
+
return JSON.stringify(result, null, 2);
|
|
10239
|
+
}
|
|
10240
|
+
},
|
|
10241
|
+
putSkill: {
|
|
10242
|
+
name: "putSkill",
|
|
10243
|
+
description: "Create or update a skill (upsert operation). relativeDirPathFromCwd, frontmatter, and body parameters are required. otherFiles is optional.",
|
|
10244
|
+
parameters: skillToolSchemas.putSkill,
|
|
10245
|
+
execute: async (args) => {
|
|
10246
|
+
const result = await putSkill({
|
|
10247
|
+
relativeDirPathFromCwd: args.relativeDirPathFromCwd,
|
|
10248
|
+
frontmatter: args.frontmatter,
|
|
10249
|
+
body: args.body,
|
|
10250
|
+
otherFiles: args.otherFiles
|
|
10251
|
+
});
|
|
10252
|
+
return JSON.stringify(result, null, 2);
|
|
10253
|
+
}
|
|
10254
|
+
},
|
|
10255
|
+
deleteSkill: {
|
|
10256
|
+
name: "deleteSkill",
|
|
10257
|
+
description: "Delete a skill directory and all its contents. relativeDirPathFromCwd parameter is required.",
|
|
10258
|
+
parameters: skillToolSchemas.deleteSkill,
|
|
10259
|
+
execute: async (args) => {
|
|
10260
|
+
const result = await deleteSkill({ relativeDirPathFromCwd: args.relativeDirPathFromCwd });
|
|
10261
|
+
return JSON.stringify(result, null, 2);
|
|
10262
|
+
}
|
|
10263
|
+
}
|
|
10264
|
+
};
|
|
10265
|
+
|
|
10266
|
+
// src/mcp/subagents.ts
|
|
10267
|
+
import { basename as basename24, join as join87 } from "path";
|
|
10268
|
+
import { z as z36 } from "zod/mini";
|
|
10007
10269
|
var maxSubagentSizeBytes = 1024 * 1024;
|
|
10008
10270
|
var maxSubagentsCount = 1e3;
|
|
10009
10271
|
async function listSubagents() {
|
|
10010
|
-
const subagentsDir =
|
|
10272
|
+
const subagentsDir = join87(process.cwd(), RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH);
|
|
10011
10273
|
try {
|
|
10012
10274
|
const files = await listDirectoryFiles(subagentsDir);
|
|
10013
10275
|
const mdFiles = files.filter((file) => file.endsWith(".md"));
|
|
@@ -10020,7 +10282,7 @@ async function listSubagents() {
|
|
|
10020
10282
|
});
|
|
10021
10283
|
const frontmatter = subagent.getFrontmatter();
|
|
10022
10284
|
return {
|
|
10023
|
-
relativePathFromCwd:
|
|
10285
|
+
relativePathFromCwd: join87(RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH, file),
|
|
10024
10286
|
frontmatter
|
|
10025
10287
|
};
|
|
10026
10288
|
} catch (error) {
|
|
@@ -10042,14 +10304,14 @@ async function getSubagent({ relativePathFromCwd }) {
|
|
|
10042
10304
|
relativePath: relativePathFromCwd,
|
|
10043
10305
|
intendedRootDir: process.cwd()
|
|
10044
10306
|
});
|
|
10045
|
-
const filename =
|
|
10307
|
+
const filename = basename24(relativePathFromCwd);
|
|
10046
10308
|
try {
|
|
10047
10309
|
const subagent = await RulesyncSubagent.fromFile({
|
|
10048
10310
|
relativeFilePath: filename,
|
|
10049
10311
|
validate: true
|
|
10050
10312
|
});
|
|
10051
10313
|
return {
|
|
10052
|
-
relativePathFromCwd:
|
|
10314
|
+
relativePathFromCwd: join87(RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH, filename),
|
|
10053
10315
|
frontmatter: subagent.getFrontmatter(),
|
|
10054
10316
|
body: subagent.getBody()
|
|
10055
10317
|
};
|
|
@@ -10068,7 +10330,7 @@ async function putSubagent({
|
|
|
10068
10330
|
relativePath: relativePathFromCwd,
|
|
10069
10331
|
intendedRootDir: process.cwd()
|
|
10070
10332
|
});
|
|
10071
|
-
const filename =
|
|
10333
|
+
const filename = basename24(relativePathFromCwd);
|
|
10072
10334
|
const estimatedSize = JSON.stringify(frontmatter).length + body.length;
|
|
10073
10335
|
if (estimatedSize > maxSubagentSizeBytes) {
|
|
10074
10336
|
throw new Error(
|
|
@@ -10078,7 +10340,7 @@ async function putSubagent({
|
|
|
10078
10340
|
try {
|
|
10079
10341
|
const existingSubagents = await listSubagents();
|
|
10080
10342
|
const isUpdate = existingSubagents.some(
|
|
10081
|
-
(subagent2) => subagent2.relativePathFromCwd ===
|
|
10343
|
+
(subagent2) => subagent2.relativePathFromCwd === join87(RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH, filename)
|
|
10082
10344
|
);
|
|
10083
10345
|
if (!isUpdate && existingSubagents.length >= maxSubagentsCount) {
|
|
10084
10346
|
throw new Error(`Maximum number of subagents (${maxSubagentsCount}) reached`);
|
|
@@ -10091,11 +10353,11 @@ async function putSubagent({
|
|
|
10091
10353
|
body,
|
|
10092
10354
|
validate: true
|
|
10093
10355
|
});
|
|
10094
|
-
const subagentsDir =
|
|
10356
|
+
const subagentsDir = join87(process.cwd(), RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH);
|
|
10095
10357
|
await ensureDir(subagentsDir);
|
|
10096
10358
|
await writeFileContent(subagent.getFilePath(), subagent.getFileContent());
|
|
10097
10359
|
return {
|
|
10098
|
-
relativePathFromCwd:
|
|
10360
|
+
relativePathFromCwd: join87(RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH, filename),
|
|
10099
10361
|
frontmatter: subagent.getFrontmatter(),
|
|
10100
10362
|
body: subagent.getBody()
|
|
10101
10363
|
};
|
|
@@ -10110,12 +10372,12 @@ async function deleteSubagent({ relativePathFromCwd }) {
|
|
|
10110
10372
|
relativePath: relativePathFromCwd,
|
|
10111
10373
|
intendedRootDir: process.cwd()
|
|
10112
10374
|
});
|
|
10113
|
-
const filename =
|
|
10114
|
-
const fullPath =
|
|
10375
|
+
const filename = basename24(relativePathFromCwd);
|
|
10376
|
+
const fullPath = join87(process.cwd(), RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH, filename);
|
|
10115
10377
|
try {
|
|
10116
10378
|
await removeFile(fullPath);
|
|
10117
10379
|
return {
|
|
10118
|
-
relativePathFromCwd:
|
|
10380
|
+
relativePathFromCwd: join87(RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH, filename)
|
|
10119
10381
|
};
|
|
10120
10382
|
} catch (error) {
|
|
10121
10383
|
throw new Error(
|
|
@@ -10127,23 +10389,23 @@ async function deleteSubagent({ relativePathFromCwd }) {
|
|
|
10127
10389
|
}
|
|
10128
10390
|
}
|
|
10129
10391
|
var subagentToolSchemas = {
|
|
10130
|
-
listSubagents:
|
|
10131
|
-
getSubagent:
|
|
10132
|
-
relativePathFromCwd:
|
|
10392
|
+
listSubagents: z36.object({}),
|
|
10393
|
+
getSubagent: z36.object({
|
|
10394
|
+
relativePathFromCwd: z36.string()
|
|
10133
10395
|
}),
|
|
10134
|
-
putSubagent:
|
|
10135
|
-
relativePathFromCwd:
|
|
10396
|
+
putSubagent: z36.object({
|
|
10397
|
+
relativePathFromCwd: z36.string(),
|
|
10136
10398
|
frontmatter: RulesyncSubagentFrontmatterSchema,
|
|
10137
|
-
body:
|
|
10399
|
+
body: z36.string()
|
|
10138
10400
|
}),
|
|
10139
|
-
deleteSubagent:
|
|
10140
|
-
relativePathFromCwd:
|
|
10401
|
+
deleteSubagent: z36.object({
|
|
10402
|
+
relativePathFromCwd: z36.string()
|
|
10141
10403
|
})
|
|
10142
10404
|
};
|
|
10143
10405
|
var subagentTools = {
|
|
10144
10406
|
listSubagents: {
|
|
10145
10407
|
name: "listSubagents",
|
|
10146
|
-
description: `List all subagents from ${
|
|
10408
|
+
description: `List all subagents from ${join87(RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH, "*.md")} with their frontmatter.`,
|
|
10147
10409
|
parameters: subagentToolSchemas.listSubagents,
|
|
10148
10410
|
execute: async () => {
|
|
10149
10411
|
const subagents = await listSubagents();
|
|
@@ -10190,7 +10452,7 @@ async function mcpCommand({ version }) {
|
|
|
10190
10452
|
name: "Rulesync MCP Server",
|
|
10191
10453
|
// eslint-disable-next-line no-type-assertion/no-type-assertion
|
|
10192
10454
|
version,
|
|
10193
|
-
instructions: "This server handles Rulesync files including rules, commands, MCP, ignore files, and
|
|
10455
|
+
instructions: "This server handles Rulesync files including rules, commands, MCP, ignore files, subagents and skills for any AI agents. It should be used when you need those files."
|
|
10194
10456
|
});
|
|
10195
10457
|
server.addTool(ruleTools.listRules);
|
|
10196
10458
|
server.addTool(ruleTools.getRule);
|
|
@@ -10204,6 +10466,10 @@ async function mcpCommand({ version }) {
|
|
|
10204
10466
|
server.addTool(subagentTools.getSubagent);
|
|
10205
10467
|
server.addTool(subagentTools.putSubagent);
|
|
10206
10468
|
server.addTool(subagentTools.deleteSubagent);
|
|
10469
|
+
server.addTool(skillTools.listSkills);
|
|
10470
|
+
server.addTool(skillTools.getSkill);
|
|
10471
|
+
server.addTool(skillTools.putSkill);
|
|
10472
|
+
server.addTool(skillTools.deleteSkill);
|
|
10207
10473
|
server.addTool(ignoreTools.getIgnoreFile);
|
|
10208
10474
|
server.addTool(ignoreTools.putIgnoreFile);
|
|
10209
10475
|
server.addTool(ignoreTools.deleteIgnoreFile);
|
|
@@ -10217,7 +10483,7 @@ async function mcpCommand({ version }) {
|
|
|
10217
10483
|
}
|
|
10218
10484
|
|
|
10219
10485
|
// src/cli/index.ts
|
|
10220
|
-
var getVersion = () => "3.
|
|
10486
|
+
var getVersion = () => "3.29.0";
|
|
10221
10487
|
var main = async () => {
|
|
10222
10488
|
const program = new Command();
|
|
10223
10489
|
const version = getVersion();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rulesync",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.29.0",
|
|
4
4
|
"description": "Unified AI rules management CLI tool that generates configuration files for various AI development tools",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -49,15 +49,15 @@
|
|
|
49
49
|
"zod": "4.1.13"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
|
-
"@anthropic-ai/claude-agent-sdk": "0.1.
|
|
53
|
-
"@biomejs/biome": "2.3.
|
|
52
|
+
"@anthropic-ai/claude-agent-sdk": "0.1.55",
|
|
53
|
+
"@biomejs/biome": "2.3.8",
|
|
54
54
|
"@eslint/js": "9.39.1",
|
|
55
55
|
"@secretlint/secretlint-rule-preset-recommend": "11.2.5",
|
|
56
56
|
"@tsconfig/node24": "24.0.3",
|
|
57
57
|
"@types/js-yaml": "4.0.9",
|
|
58
58
|
"@types/node": "24.10.1",
|
|
59
|
-
"@typescript/native-preview": "7.0.0-dev.
|
|
60
|
-
"@vitest/coverage-v8": "4.0.
|
|
59
|
+
"@typescript/native-preview": "7.0.0-dev.20251130.1",
|
|
60
|
+
"@vitest/coverage-v8": "4.0.14",
|
|
61
61
|
"cspell": "9.3.2",
|
|
62
62
|
"eslint": "9.39.1",
|
|
63
63
|
"eslint-plugin-import": "2.32.0",
|
|
@@ -70,12 +70,12 @@
|
|
|
70
70
|
"oxlint": "1.30.0",
|
|
71
71
|
"secretlint": "11.2.5",
|
|
72
72
|
"simple-git-hooks": "2.13.1",
|
|
73
|
-
"sort-package-json": "3.
|
|
73
|
+
"sort-package-json": "3.5.0",
|
|
74
74
|
"tsup": "8.5.1",
|
|
75
|
-
"tsx": "4.
|
|
75
|
+
"tsx": "4.21.0",
|
|
76
76
|
"typescript": "5.9.3",
|
|
77
77
|
"typescript-eslint": "8.48.0",
|
|
78
|
-
"vitest": "4.0.
|
|
78
|
+
"vitest": "4.0.14"
|
|
79
79
|
},
|
|
80
80
|
"engines": {
|
|
81
81
|
"node": ">=22.0.0"
|
|
@@ -98,6 +98,7 @@
|
|
|
98
98
|
"eslint:fix": "eslint . --fix --max-warnings 0 --cache",
|
|
99
99
|
"fix": "pnpm run bcheck:fix && pnpm run oxlint:fix && pnpm run eslint:fix",
|
|
100
100
|
"generate": "pnpm run dev generate",
|
|
101
|
+
"generate:schema": "tsx scripts/generate-json-schema.ts",
|
|
101
102
|
"knip": "knip",
|
|
102
103
|
"oxlint": "oxlint . --max-warnings 0",
|
|
103
104
|
"oxlint:fix": "oxlint . --fix --max-warnings 0",
|