rulesync 0.56.0 → 0.58.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.ja.md +173 -20
- package/README.md +67 -699
- package/dist/index.cjs +464 -213
- package/dist/index.js +464 -213
- package/package.json +19 -19
package/dist/index.js
CHANGED
|
@@ -129,128 +129,137 @@ var ClaudeSettingsSchema = z.looseObject({
|
|
|
129
129
|
)
|
|
130
130
|
});
|
|
131
131
|
|
|
132
|
-
// src/types/
|
|
132
|
+
// src/types/commands.ts
|
|
133
133
|
import { z as z2 } from "zod/mini";
|
|
134
|
-
var
|
|
135
|
-
|
|
136
|
-
outputPaths: z2.record(ToolTargetSchema, z2.string()),
|
|
137
|
-
watchEnabled: z2.boolean(),
|
|
138
|
-
defaultTargets: ToolTargetsSchema
|
|
134
|
+
var CommandFrontmatterSchema = z2.object({
|
|
135
|
+
description: z2.optional(z2.string())
|
|
139
136
|
});
|
|
140
137
|
|
|
141
|
-
// src/types/config
|
|
138
|
+
// src/types/config.ts
|
|
142
139
|
import { z as z3 } from "zod/mini";
|
|
143
|
-
var
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
140
|
+
var ConfigSchema = z3.object({
|
|
141
|
+
aiRulesDir: z3.string(),
|
|
142
|
+
outputPaths: z3.record(ToolTargetSchema, z3.string()),
|
|
143
|
+
watchEnabled: z3.boolean(),
|
|
144
|
+
defaultTargets: ToolTargetsSchema,
|
|
145
|
+
claudecodeCommands: z3.optional(z3.string()),
|
|
146
|
+
geminicliCommands: z3.optional(z3.string())
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// src/types/config-options.ts
|
|
150
|
+
import { z as z4 } from "zod/mini";
|
|
151
|
+
var OutputPathsSchema = z4.object({
|
|
152
|
+
augmentcode: z4.optional(z4.string()),
|
|
153
|
+
"augmentcode-legacy": z4.optional(z4.string()),
|
|
154
|
+
copilot: z4.optional(z4.string()),
|
|
155
|
+
cursor: z4.optional(z4.string()),
|
|
156
|
+
cline: z4.optional(z4.string()),
|
|
157
|
+
claudecode: z4.optional(z4.string()),
|
|
158
|
+
codexcli: z4.optional(z4.string()),
|
|
159
|
+
roo: z4.optional(z4.string()),
|
|
160
|
+
geminicli: z4.optional(z4.string()),
|
|
161
|
+
kiro: z4.optional(z4.string()),
|
|
162
|
+
junie: z4.optional(z4.string()),
|
|
163
|
+
windsurf: z4.optional(z4.string())
|
|
156
164
|
});
|
|
157
|
-
var ConfigOptionsSchema =
|
|
158
|
-
aiRulesDir:
|
|
159
|
-
outputPaths:
|
|
160
|
-
watchEnabled:
|
|
161
|
-
defaultTargets:
|
|
162
|
-
targets:
|
|
163
|
-
exclude:
|
|
164
|
-
verbose:
|
|
165
|
-
delete:
|
|
166
|
-
baseDir:
|
|
167
|
-
watch:
|
|
168
|
-
|
|
169
|
-
enabled:
|
|
170
|
-
interval:
|
|
171
|
-
ignore:
|
|
165
|
+
var ConfigOptionsSchema = z4.object({
|
|
166
|
+
aiRulesDir: z4.optional(z4.string()),
|
|
167
|
+
outputPaths: z4.optional(OutputPathsSchema),
|
|
168
|
+
watchEnabled: z4.optional(z4.boolean()),
|
|
169
|
+
defaultTargets: z4.optional(ToolTargetsSchema),
|
|
170
|
+
targets: z4.optional(z4.array(ToolTargetSchema)),
|
|
171
|
+
exclude: z4.optional(z4.array(ToolTargetSchema)),
|
|
172
|
+
verbose: z4.optional(z4.boolean()),
|
|
173
|
+
delete: z4.optional(z4.boolean()),
|
|
174
|
+
baseDir: z4.optional(z4.union([z4.string(), z4.array(z4.string())])),
|
|
175
|
+
watch: z4.optional(
|
|
176
|
+
z4.object({
|
|
177
|
+
enabled: z4.optional(z4.boolean()),
|
|
178
|
+
interval: z4.optional(z4.number()),
|
|
179
|
+
ignore: z4.optional(z4.array(z4.string()))
|
|
172
180
|
})
|
|
173
181
|
)
|
|
174
182
|
});
|
|
175
|
-
var MergedConfigSchema =
|
|
176
|
-
aiRulesDir:
|
|
177
|
-
outputPaths:
|
|
178
|
-
watchEnabled:
|
|
183
|
+
var MergedConfigSchema = z4.object({
|
|
184
|
+
aiRulesDir: z4.string(),
|
|
185
|
+
outputPaths: z4.record(ToolTargetSchema, z4.string()),
|
|
186
|
+
watchEnabled: z4.boolean(),
|
|
179
187
|
defaultTargets: ToolTargetsSchema,
|
|
180
|
-
targets:
|
|
181
|
-
exclude:
|
|
182
|
-
verbose:
|
|
183
|
-
delete:
|
|
184
|
-
baseDir:
|
|
185
|
-
configPath:
|
|
186
|
-
watch:
|
|
187
|
-
|
|
188
|
-
enabled:
|
|
189
|
-
interval:
|
|
190
|
-
ignore:
|
|
188
|
+
targets: z4.optional(z4.array(ToolTargetSchema)),
|
|
189
|
+
exclude: z4.optional(z4.array(ToolTargetSchema)),
|
|
190
|
+
verbose: z4.optional(z4.boolean()),
|
|
191
|
+
delete: z4.optional(z4.boolean()),
|
|
192
|
+
baseDir: z4.optional(z4.union([z4.string(), z4.array(z4.string())])),
|
|
193
|
+
configPath: z4.optional(z4.string()),
|
|
194
|
+
watch: z4.optional(
|
|
195
|
+
z4.object({
|
|
196
|
+
enabled: z4.optional(z4.boolean()),
|
|
197
|
+
interval: z4.optional(z4.number()),
|
|
198
|
+
ignore: z4.optional(z4.array(z4.string()))
|
|
191
199
|
})
|
|
192
200
|
)
|
|
193
201
|
});
|
|
194
202
|
|
|
195
203
|
// src/types/mcp.ts
|
|
196
|
-
import { z as
|
|
197
|
-
var McpTransportTypeSchema =
|
|
198
|
-
var McpServerBaseSchema =
|
|
199
|
-
command:
|
|
200
|
-
args:
|
|
201
|
-
url:
|
|
202
|
-
httpUrl:
|
|
203
|
-
env:
|
|
204
|
-
disabled:
|
|
205
|
-
networkTimeout:
|
|
206
|
-
timeout:
|
|
207
|
-
trust:
|
|
208
|
-
cwd:
|
|
209
|
-
transport:
|
|
210
|
-
type:
|
|
211
|
-
alwaysAllow:
|
|
212
|
-
tools:
|
|
213
|
-
kiroAutoApprove:
|
|
214
|
-
kiroAutoBlock:
|
|
215
|
-
headers:
|
|
204
|
+
import { z as z5 } from "zod/mini";
|
|
205
|
+
var McpTransportTypeSchema = z5.enum(["stdio", "sse", "http"]);
|
|
206
|
+
var McpServerBaseSchema = z5.object({
|
|
207
|
+
command: z5.optional(z5.string()),
|
|
208
|
+
args: z5.optional(z5.array(z5.string())),
|
|
209
|
+
url: z5.optional(z5.string()),
|
|
210
|
+
httpUrl: z5.optional(z5.string()),
|
|
211
|
+
env: z5.optional(z5.record(z5.string(), z5.string())),
|
|
212
|
+
disabled: z5.optional(z5.boolean()),
|
|
213
|
+
networkTimeout: z5.optional(z5.number()),
|
|
214
|
+
timeout: z5.optional(z5.number()),
|
|
215
|
+
trust: z5.optional(z5.boolean()),
|
|
216
|
+
cwd: z5.optional(z5.string()),
|
|
217
|
+
transport: z5.optional(McpTransportTypeSchema),
|
|
218
|
+
type: z5.optional(z5.enum(["sse", "streamable-http"])),
|
|
219
|
+
alwaysAllow: z5.optional(z5.array(z5.string())),
|
|
220
|
+
tools: z5.optional(z5.array(z5.string())),
|
|
221
|
+
kiroAutoApprove: z5.optional(z5.array(z5.string())),
|
|
222
|
+
kiroAutoBlock: z5.optional(z5.array(z5.string())),
|
|
223
|
+
headers: z5.optional(z5.record(z5.string(), z5.string()))
|
|
216
224
|
});
|
|
217
|
-
var RulesyncMcpServerSchema =
|
|
218
|
-
targets:
|
|
225
|
+
var RulesyncMcpServerSchema = z5.extend(McpServerBaseSchema, {
|
|
226
|
+
targets: z5.optional(RulesyncTargetsSchema)
|
|
219
227
|
});
|
|
220
|
-
var McpConfigSchema =
|
|
221
|
-
mcpServers:
|
|
228
|
+
var McpConfigSchema = z5.object({
|
|
229
|
+
mcpServers: z5.record(z5.string(), McpServerBaseSchema)
|
|
222
230
|
});
|
|
223
|
-
var RulesyncMcpConfigSchema =
|
|
224
|
-
mcpServers:
|
|
231
|
+
var RulesyncMcpConfigSchema = z5.object({
|
|
232
|
+
mcpServers: z5.record(z5.string(), RulesyncMcpServerSchema)
|
|
225
233
|
});
|
|
226
234
|
|
|
227
235
|
// src/types/rules.ts
|
|
228
|
-
import { z as
|
|
229
|
-
var RuleFrontmatterSchema =
|
|
230
|
-
root:
|
|
231
|
-
targets: RulesyncTargetsSchema,
|
|
232
|
-
description:
|
|
233
|
-
globs:
|
|
234
|
-
cursorRuleType:
|
|
235
|
-
windsurfActivationMode:
|
|
236
|
-
windsurfOutputFormat:
|
|
237
|
-
tags:
|
|
236
|
+
import { z as z6 } from "zod/mini";
|
|
237
|
+
var RuleFrontmatterSchema = z6.object({
|
|
238
|
+
root: z6.optional(z6.boolean()),
|
|
239
|
+
targets: z6.optional(RulesyncTargetsSchema),
|
|
240
|
+
description: z6.optional(z6.string()),
|
|
241
|
+
globs: z6.optional(z6.array(z6.string())),
|
|
242
|
+
cursorRuleType: z6.optional(z6.enum(["always", "manual", "specificFiles", "intelligently"])),
|
|
243
|
+
windsurfActivationMode: z6.optional(z6.enum(["always", "manual", "model-decision", "glob"])),
|
|
244
|
+
windsurfOutputFormat: z6.optional(z6.enum(["single-file", "directory"])),
|
|
245
|
+
tags: z6.optional(z6.array(z6.string()))
|
|
238
246
|
});
|
|
239
|
-
var ParsedRuleSchema =
|
|
247
|
+
var ParsedRuleSchema = z6.object({
|
|
240
248
|
frontmatter: RuleFrontmatterSchema,
|
|
241
|
-
content:
|
|
242
|
-
filename:
|
|
243
|
-
filepath:
|
|
249
|
+
content: z6.string(),
|
|
250
|
+
filename: z6.string(),
|
|
251
|
+
filepath: z6.string(),
|
|
252
|
+
type: z6.optional(z6.enum(["rule", "command"]))
|
|
244
253
|
});
|
|
245
|
-
var GeneratedOutputSchema =
|
|
254
|
+
var GeneratedOutputSchema = z6.object({
|
|
246
255
|
tool: ToolTargetSchema,
|
|
247
|
-
filepath:
|
|
248
|
-
content:
|
|
256
|
+
filepath: z6.string(),
|
|
257
|
+
content: z6.string()
|
|
249
258
|
});
|
|
250
|
-
var GenerateOptionsSchema =
|
|
251
|
-
targetTools:
|
|
252
|
-
outputDir:
|
|
253
|
-
watch:
|
|
259
|
+
var GenerateOptionsSchema = z6.object({
|
|
260
|
+
targetTools: z6.optional(ToolTargetsSchema),
|
|
261
|
+
outputDir: z6.optional(z6.string()),
|
|
262
|
+
watch: z6.optional(z6.boolean())
|
|
254
263
|
});
|
|
255
264
|
|
|
256
265
|
// src/utils/config-loader.ts
|
|
@@ -542,28 +551,6 @@ async function removeClaudeGeneratedFiles() {
|
|
|
542
551
|
}
|
|
543
552
|
}
|
|
544
553
|
|
|
545
|
-
// src/utils/rules.ts
|
|
546
|
-
function isToolSpecificRule(rule, targetTool) {
|
|
547
|
-
const filename = rule.filename;
|
|
548
|
-
const toolPatterns = {
|
|
549
|
-
"augmentcode-legacy": /^specification-augmentcode-legacy-/i,
|
|
550
|
-
augmentcode: /^specification-augmentcode-/i,
|
|
551
|
-
copilot: /^specification-copilot-/i,
|
|
552
|
-
cursor: /^specification-cursor-/i,
|
|
553
|
-
cline: /^specification-cline-/i,
|
|
554
|
-
claudecode: /^specification-claudecode-/i,
|
|
555
|
-
roo: /^specification-roo-/i,
|
|
556
|
-
geminicli: /^specification-geminicli-/i,
|
|
557
|
-
kiro: /^specification-kiro-/i
|
|
558
|
-
};
|
|
559
|
-
for (const [tool, pattern] of Object.entries(toolPatterns)) {
|
|
560
|
-
if (pattern.test(filename)) {
|
|
561
|
-
return tool === targetTool;
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
return true;
|
|
565
|
-
}
|
|
566
|
-
|
|
567
554
|
// src/cli/commands/config.ts
|
|
568
555
|
async function configCommand(options = {}) {
|
|
569
556
|
if (options.init) {
|
|
@@ -775,10 +762,165 @@ export default config;
|
|
|
775
762
|
}
|
|
776
763
|
|
|
777
764
|
// src/cli/commands/generate.ts
|
|
778
|
-
import { join as
|
|
765
|
+
import { join as join14 } from "path";
|
|
779
766
|
|
|
780
|
-
// src/
|
|
767
|
+
// src/core/command-generator.ts
|
|
768
|
+
import { join as join5 } from "path";
|
|
769
|
+
|
|
770
|
+
// src/generators/commands/claudecode.ts
|
|
781
771
|
import { join as join3 } from "path";
|
|
772
|
+
var ClaudeCodeCommandGenerator = class {
|
|
773
|
+
generate(command, outputDir) {
|
|
774
|
+
const filepath = this.getOutputPath(command.filename, outputDir);
|
|
775
|
+
const frontmatter = ["---"];
|
|
776
|
+
if (command.frontmatter.description) {
|
|
777
|
+
frontmatter.push(`description: ${command.frontmatter.description}`);
|
|
778
|
+
}
|
|
779
|
+
frontmatter.push("---");
|
|
780
|
+
const content = `${frontmatter.join("\n")}
|
|
781
|
+
|
|
782
|
+
${command.content.trim()}
|
|
783
|
+
`;
|
|
784
|
+
return {
|
|
785
|
+
tool: "claudecode",
|
|
786
|
+
filepath,
|
|
787
|
+
content
|
|
788
|
+
};
|
|
789
|
+
}
|
|
790
|
+
getOutputPath(filename, baseDir) {
|
|
791
|
+
const flattenedName = filename.replace(/\//g, "-");
|
|
792
|
+
return join3(baseDir, ".claude", "commands", `${flattenedName}.md`);
|
|
793
|
+
}
|
|
794
|
+
};
|
|
795
|
+
|
|
796
|
+
// src/generators/commands/geminicli.ts
|
|
797
|
+
import { join as join4 } from "path";
|
|
798
|
+
var GeminiCliCommandGenerator = class {
|
|
799
|
+
generate(command, outputDir) {
|
|
800
|
+
const filepath = this.getOutputPath(command.filename, outputDir);
|
|
801
|
+
const convertedContent = this.convertSyntax(command.content);
|
|
802
|
+
const tomlLines = [];
|
|
803
|
+
if (command.frontmatter.description) {
|
|
804
|
+
tomlLines.push(`description = "${this.escapeTomlString(command.frontmatter.description)}"`);
|
|
805
|
+
tomlLines.push("");
|
|
806
|
+
}
|
|
807
|
+
tomlLines.push(`prompt = """${convertedContent}"""`);
|
|
808
|
+
const content = tomlLines.join("\n") + "\n";
|
|
809
|
+
return {
|
|
810
|
+
tool: "geminicli",
|
|
811
|
+
filepath,
|
|
812
|
+
content
|
|
813
|
+
};
|
|
814
|
+
}
|
|
815
|
+
getOutputPath(filename, baseDir) {
|
|
816
|
+
const tomlFilename = filename.replace(/\.md$/, ".toml");
|
|
817
|
+
const filenameWithExt = tomlFilename.endsWith(".toml") ? tomlFilename : `${tomlFilename}.toml`;
|
|
818
|
+
return join4(baseDir, ".gemini", "commands", filenameWithExt);
|
|
819
|
+
}
|
|
820
|
+
convertSyntax(content) {
|
|
821
|
+
let converted = content;
|
|
822
|
+
converted = converted.replace(/\$ARGUMENTS/g, "{{args}}");
|
|
823
|
+
converted = converted.replace(/!`([^`]+)`/g, "!{$1}");
|
|
824
|
+
const atSyntaxMatches = converted.match(/@[^\s]+/g);
|
|
825
|
+
if (atSyntaxMatches) {
|
|
826
|
+
console.warn(
|
|
827
|
+
`\u26A0\uFE0F Warning: @ syntax found (${atSyntaxMatches.join(", ")}). Gemini CLI does not support file content injection. Consider using shell commands or remove these references.`
|
|
828
|
+
);
|
|
829
|
+
}
|
|
830
|
+
return converted.trim();
|
|
831
|
+
}
|
|
832
|
+
escapeTomlString(str) {
|
|
833
|
+
return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
|
|
834
|
+
}
|
|
835
|
+
};
|
|
836
|
+
|
|
837
|
+
// src/generators/commands/index.ts
|
|
838
|
+
var commandGenerators = {
|
|
839
|
+
claudecode: new ClaudeCodeCommandGenerator(),
|
|
840
|
+
geminicli: new GeminiCliCommandGenerator()
|
|
841
|
+
};
|
|
842
|
+
function getCommandGenerator(tool) {
|
|
843
|
+
return commandGenerators[tool];
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
// src/core/command-parser.ts
|
|
847
|
+
import { basename } from "path";
|
|
848
|
+
import matter from "gray-matter";
|
|
849
|
+
async function parseCommandsFromDirectory(commandsDir) {
|
|
850
|
+
const commandFiles = await findFiles(commandsDir, ".md");
|
|
851
|
+
const commands = [];
|
|
852
|
+
const errors = [];
|
|
853
|
+
for (const filepath of commandFiles) {
|
|
854
|
+
try {
|
|
855
|
+
const command = await parseCommandFile(filepath);
|
|
856
|
+
commands.push(command);
|
|
857
|
+
} catch (error) {
|
|
858
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
859
|
+
errors.push(`Failed to parse command file ${filepath}: ${errorMessage}`);
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
if (errors.length > 0) {
|
|
863
|
+
console.warn(`\u26A0\uFE0F Command parsing errors:
|
|
864
|
+
${errors.join("\n")}`);
|
|
865
|
+
}
|
|
866
|
+
return commands;
|
|
867
|
+
}
|
|
868
|
+
async function parseCommandFile(filepath) {
|
|
869
|
+
const content = await readFileContent(filepath);
|
|
870
|
+
const parsed = matter(content);
|
|
871
|
+
try {
|
|
872
|
+
const validatedData = CommandFrontmatterSchema.parse(parsed.data);
|
|
873
|
+
const filename = basename(filepath, ".md");
|
|
874
|
+
return {
|
|
875
|
+
frontmatter: {
|
|
876
|
+
description: validatedData.description
|
|
877
|
+
},
|
|
878
|
+
content: parsed.content,
|
|
879
|
+
filename,
|
|
880
|
+
filepath
|
|
881
|
+
};
|
|
882
|
+
} catch (error) {
|
|
883
|
+
throw new Error(
|
|
884
|
+
`Invalid frontmatter in ${filepath}: ${error instanceof Error ? error.message : String(error)}`
|
|
885
|
+
);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
// src/core/command-generator.ts
|
|
890
|
+
async function generateCommands(projectRoot, baseDir, targets) {
|
|
891
|
+
const commandsDir = join5(projectRoot, ".rulesync", "commands");
|
|
892
|
+
if (!await fileExists(commandsDir)) {
|
|
893
|
+
return [];
|
|
894
|
+
}
|
|
895
|
+
const commands = await parseCommandsFromDirectory(commandsDir);
|
|
896
|
+
if (commands.length === 0) {
|
|
897
|
+
return [];
|
|
898
|
+
}
|
|
899
|
+
const outputs = [];
|
|
900
|
+
const outputDir = baseDir || projectRoot;
|
|
901
|
+
const supportedTargets = targets.filter((target) => ["claudecode", "geminicli"].includes(target));
|
|
902
|
+
for (const target of supportedTargets) {
|
|
903
|
+
const generator = getCommandGenerator(target);
|
|
904
|
+
if (!generator) {
|
|
905
|
+
continue;
|
|
906
|
+
}
|
|
907
|
+
for (const command of commands) {
|
|
908
|
+
try {
|
|
909
|
+
const output = generator.generate(command, outputDir);
|
|
910
|
+
outputs.push(output);
|
|
911
|
+
} catch (error) {
|
|
912
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
913
|
+
console.error(
|
|
914
|
+
`\u274C Failed to generate ${target} command for ${command.filename}: ${errorMessage}`
|
|
915
|
+
);
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
return outputs;
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
// src/generators/ignore/shared-factory.ts
|
|
923
|
+
import { join as join6 } from "path";
|
|
782
924
|
|
|
783
925
|
// src/generators/ignore/shared-helpers.ts
|
|
784
926
|
function extractIgnorePatternsFromRules(rules) {
|
|
@@ -896,7 +1038,7 @@ function generateIgnoreFile(rules, config, ignoreConfig, baseDir) {
|
|
|
896
1038
|
const outputs = [];
|
|
897
1039
|
const content = generateIgnoreContent(rules, ignoreConfig);
|
|
898
1040
|
const outputPath = baseDir || process.cwd();
|
|
899
|
-
const filepath =
|
|
1041
|
+
const filepath = join6(outputPath, ignoreConfig.filename);
|
|
900
1042
|
outputs.push({
|
|
901
1043
|
tool: ignoreConfig.tool,
|
|
902
1044
|
filepath,
|
|
@@ -1484,20 +1626,20 @@ function generateWindsurfIgnore(rules, config, baseDir) {
|
|
|
1484
1626
|
}
|
|
1485
1627
|
|
|
1486
1628
|
// src/generators/rules/augmentcode.ts
|
|
1487
|
-
import { join as
|
|
1629
|
+
import { join as join9 } from "path";
|
|
1488
1630
|
|
|
1489
1631
|
// src/generators/rules/shared-helpers.ts
|
|
1490
|
-
import { join as
|
|
1632
|
+
import { join as join8 } from "path";
|
|
1491
1633
|
|
|
1492
1634
|
// src/utils/ignore.ts
|
|
1493
|
-
import { join as
|
|
1635
|
+
import { join as join7 } from "path";
|
|
1494
1636
|
import micromatch from "micromatch";
|
|
1495
1637
|
var cachedIgnorePatterns = null;
|
|
1496
1638
|
async function loadIgnorePatterns(baseDir = process.cwd()) {
|
|
1497
1639
|
if (cachedIgnorePatterns) {
|
|
1498
1640
|
return cachedIgnorePatterns;
|
|
1499
1641
|
}
|
|
1500
|
-
const ignorePath =
|
|
1642
|
+
const ignorePath = join7(baseDir, ".rulesyncignore");
|
|
1501
1643
|
if (!await fileExists(ignorePath)) {
|
|
1502
1644
|
cachedIgnorePatterns = { patterns: [] };
|
|
1503
1645
|
return cachedIgnorePatterns;
|
|
@@ -1551,7 +1693,7 @@ function addOutput(outputs, tool, config, baseDir, relativePath, content) {
|
|
|
1551
1693
|
const outputDir = resolveOutputDir(config, tool, baseDir);
|
|
1552
1694
|
outputs.push({
|
|
1553
1695
|
tool,
|
|
1554
|
-
filepath:
|
|
1696
|
+
filepath: join8(outputDir, relativePath),
|
|
1555
1697
|
content
|
|
1556
1698
|
});
|
|
1557
1699
|
}
|
|
@@ -1560,7 +1702,7 @@ async function generateRulesConfig(rules, config, generatorConfig, baseDir) {
|
|
|
1560
1702
|
for (const rule of rules) {
|
|
1561
1703
|
const content = generatorConfig.generateContent(rule);
|
|
1562
1704
|
const outputDir = resolveOutputDir(config, generatorConfig.tool, baseDir);
|
|
1563
|
-
const filepath = generatorConfig.pathResolver ? generatorConfig.pathResolver(rule, outputDir) :
|
|
1705
|
+
const filepath = generatorConfig.pathResolver ? generatorConfig.pathResolver(rule, outputDir) : join8(outputDir, `${rule.filename}${generatorConfig.fileExtension}`);
|
|
1564
1706
|
outputs.push({
|
|
1565
1707
|
tool: generatorConfig.tool,
|
|
1566
1708
|
filepath,
|
|
@@ -1568,7 +1710,7 @@ async function generateRulesConfig(rules, config, generatorConfig, baseDir) {
|
|
|
1568
1710
|
});
|
|
1569
1711
|
}
|
|
1570
1712
|
const ignorePatterns = await loadIgnorePatterns(baseDir);
|
|
1571
|
-
if (ignorePatterns.patterns.length > 0) {
|
|
1713
|
+
if (ignorePatterns.patterns.length > 0 && generatorConfig.ignoreFileName) {
|
|
1572
1714
|
const ignorePath = resolvePath(generatorConfig.ignoreFileName, baseDir);
|
|
1573
1715
|
const ignoreContent = generateIgnoreFile2(ignorePatterns.patterns, generatorConfig.tool);
|
|
1574
1716
|
outputs.push({
|
|
@@ -1588,7 +1730,7 @@ async function generateComplexRules(rules, config, generatorConfig, baseDir) {
|
|
|
1588
1730
|
for (const rule of detailRules) {
|
|
1589
1731
|
const content = generatorConfig.generateDetailContent(rule);
|
|
1590
1732
|
const filepath = resolvePath(
|
|
1591
|
-
|
|
1733
|
+
join8(generatorConfig.detailSubDir, `${rule.filename}.md`),
|
|
1592
1734
|
baseDir
|
|
1593
1735
|
);
|
|
1594
1736
|
outputs.push({
|
|
@@ -1609,13 +1751,15 @@ async function generateComplexRules(rules, config, generatorConfig, baseDir) {
|
|
|
1609
1751
|
}
|
|
1610
1752
|
const ignorePatterns = await loadIgnorePatterns(baseDir);
|
|
1611
1753
|
if (ignorePatterns.patterns.length > 0) {
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1754
|
+
if (generatorConfig.ignoreFileName) {
|
|
1755
|
+
const ignorePath = resolvePath(generatorConfig.ignoreFileName, baseDir);
|
|
1756
|
+
const ignoreContent = generateIgnoreFile2(ignorePatterns.patterns, generatorConfig.tool);
|
|
1757
|
+
outputs.push({
|
|
1758
|
+
tool: generatorConfig.tool,
|
|
1759
|
+
filepath: ignorePath,
|
|
1760
|
+
content: ignoreContent
|
|
1761
|
+
});
|
|
1762
|
+
}
|
|
1619
1763
|
if (generatorConfig.updateAdditionalConfig) {
|
|
1620
1764
|
const additionalOutputs = await generatorConfig.updateAdditionalConfig(
|
|
1621
1765
|
ignorePatterns.patterns,
|
|
@@ -1649,7 +1793,7 @@ async function generateAugmentcodeConfig(rules, config, baseDir) {
|
|
|
1649
1793
|
"augmentcode",
|
|
1650
1794
|
config,
|
|
1651
1795
|
baseDir,
|
|
1652
|
-
|
|
1796
|
+
join9(".augment", "rules", `${rule.filename}.md`),
|
|
1653
1797
|
generateRuleFile(rule)
|
|
1654
1798
|
);
|
|
1655
1799
|
});
|
|
@@ -1702,19 +1846,19 @@ function generateLegacyGuidelinesFile(allRules) {
|
|
|
1702
1846
|
}
|
|
1703
1847
|
|
|
1704
1848
|
// src/generators/rules/claudecode.ts
|
|
1705
|
-
import { join as
|
|
1849
|
+
import { join as join10 } from "path";
|
|
1706
1850
|
async function generateClaudecodeConfig(rules, config, baseDir) {
|
|
1707
1851
|
const generatorConfig = {
|
|
1708
1852
|
tool: "claudecode",
|
|
1709
1853
|
fileExtension: ".md",
|
|
1710
|
-
ignoreFileName
|
|
1854
|
+
// ignoreFileName omitted - Claude Code uses settings.json permissions.deny instead of ignore files
|
|
1711
1855
|
generateContent: generateMemoryFile,
|
|
1712
1856
|
generateRootContent: generateClaudeMarkdown,
|
|
1713
1857
|
rootFilePath: "CLAUDE.md",
|
|
1714
1858
|
generateDetailContent: generateMemoryFile,
|
|
1715
1859
|
detailSubDir: ".claude/memories",
|
|
1716
1860
|
updateAdditionalConfig: async (ignorePatterns, baseDir2) => {
|
|
1717
|
-
const settingsPath = resolvePath(
|
|
1861
|
+
const settingsPath = resolvePath(join10(".claude", "settings.json"), baseDir2);
|
|
1718
1862
|
await updateClaudeSettings(settingsPath, ignorePatterns);
|
|
1719
1863
|
return [];
|
|
1720
1864
|
}
|
|
@@ -1778,7 +1922,7 @@ async function updateClaudeSettings(settingsPath, ignorePatterns) {
|
|
|
1778
1922
|
}
|
|
1779
1923
|
|
|
1780
1924
|
// src/generators/rules/generator-registry.ts
|
|
1781
|
-
import { join as
|
|
1925
|
+
import { join as join11 } from "path";
|
|
1782
1926
|
function determineCursorRuleType(frontmatter) {
|
|
1783
1927
|
if (frontmatter.cursorRuleType) {
|
|
1784
1928
|
return frontmatter.cursorRuleType;
|
|
@@ -1858,7 +2002,7 @@ var GENERATOR_REGISTRY = {
|
|
|
1858
2002
|
},
|
|
1859
2003
|
pathResolver: (rule, outputDir) => {
|
|
1860
2004
|
const baseFilename = rule.filename.replace(/\.md$/, "");
|
|
1861
|
-
return
|
|
2005
|
+
return join11(outputDir, `${baseFilename}.instructions.md`);
|
|
1862
2006
|
}
|
|
1863
2007
|
},
|
|
1864
2008
|
cursor: {
|
|
@@ -1898,7 +2042,7 @@ var GENERATOR_REGISTRY = {
|
|
|
1898
2042
|
return lines.join("\n");
|
|
1899
2043
|
},
|
|
1900
2044
|
pathResolver: (rule, outputDir) => {
|
|
1901
|
-
return
|
|
2045
|
+
return join11(outputDir, `${rule.filename}.mdc`);
|
|
1902
2046
|
}
|
|
1903
2047
|
},
|
|
1904
2048
|
codexcli: {
|
|
@@ -1934,10 +2078,10 @@ var GENERATOR_REGISTRY = {
|
|
|
1934
2078
|
pathResolver: (rule, outputDir) => {
|
|
1935
2079
|
const outputFormat = rule.frontmatter.windsurfOutputFormat || "directory";
|
|
1936
2080
|
if (outputFormat === "single-file") {
|
|
1937
|
-
return
|
|
2081
|
+
return join11(outputDir, ".windsurf-rules");
|
|
1938
2082
|
} else {
|
|
1939
|
-
const rulesDir =
|
|
1940
|
-
return
|
|
2083
|
+
const rulesDir = join11(outputDir, ".windsurf", "rules");
|
|
2084
|
+
return join11(rulesDir, `${rule.filename}.md`);
|
|
1941
2085
|
}
|
|
1942
2086
|
}
|
|
1943
2087
|
},
|
|
@@ -2201,7 +2345,7 @@ function filterRulesForTool(rules, tool, config) {
|
|
|
2201
2345
|
if (!targets.includes(tool)) {
|
|
2202
2346
|
return false;
|
|
2203
2347
|
}
|
|
2204
|
-
return
|
|
2348
|
+
return true;
|
|
2205
2349
|
});
|
|
2206
2350
|
}
|
|
2207
2351
|
async function generateForTool(tool, rules, config, baseDir) {
|
|
@@ -2256,8 +2400,8 @@ async function generateForTool(tool, rules, config, baseDir) {
|
|
|
2256
2400
|
}
|
|
2257
2401
|
|
|
2258
2402
|
// src/core/parser.ts
|
|
2259
|
-
import { basename } from "path";
|
|
2260
|
-
import
|
|
2403
|
+
import { basename as basename2 } from "path";
|
|
2404
|
+
import matter2 from "gray-matter";
|
|
2261
2405
|
async function parseRulesFromDirectory(aiRulesDir) {
|
|
2262
2406
|
const ignorePatterns = await loadIgnorePatterns();
|
|
2263
2407
|
const allRuleFiles = await findFiles(aiRulesDir, ".md");
|
|
@@ -2291,10 +2435,26 @@ ${errors.join("\n")}`);
|
|
|
2291
2435
|
}
|
|
2292
2436
|
async function parseRuleFile(filepath) {
|
|
2293
2437
|
const content = await readFileContent(filepath);
|
|
2294
|
-
const parsed =
|
|
2438
|
+
const parsed = matter2(content);
|
|
2295
2439
|
try {
|
|
2296
|
-
const
|
|
2297
|
-
const
|
|
2440
|
+
const validatedData = RuleFrontmatterSchema.parse(parsed.data);
|
|
2441
|
+
const frontmatter = {
|
|
2442
|
+
root: validatedData.root ?? false,
|
|
2443
|
+
targets: validatedData.targets ?? ["*"],
|
|
2444
|
+
description: validatedData.description ?? "",
|
|
2445
|
+
globs: validatedData.globs ?? [],
|
|
2446
|
+
...validatedData.cursorRuleType !== void 0 && {
|
|
2447
|
+
cursorRuleType: validatedData.cursorRuleType
|
|
2448
|
+
},
|
|
2449
|
+
...validatedData.windsurfActivationMode !== void 0 && {
|
|
2450
|
+
windsurfActivationMode: validatedData.windsurfActivationMode
|
|
2451
|
+
},
|
|
2452
|
+
...validatedData.windsurfOutputFormat !== void 0 && {
|
|
2453
|
+
windsurfOutputFormat: validatedData.windsurfOutputFormat
|
|
2454
|
+
},
|
|
2455
|
+
...validatedData.tags !== void 0 && { tags: validatedData.tags }
|
|
2456
|
+
};
|
|
2457
|
+
const filename = basename2(filepath, ".md");
|
|
2298
2458
|
return {
|
|
2299
2459
|
frontmatter,
|
|
2300
2460
|
content: parsed.content,
|
|
@@ -2584,12 +2744,12 @@ async function generateCommand(options = {}) {
|
|
|
2584
2744
|
for (const tool of targetTools) {
|
|
2585
2745
|
switch (tool) {
|
|
2586
2746
|
case "augmentcode":
|
|
2587
|
-
deleteTasks.push(removeDirectory(
|
|
2588
|
-
deleteTasks.push(removeDirectory(
|
|
2747
|
+
deleteTasks.push(removeDirectory(join14(".augment", "rules")));
|
|
2748
|
+
deleteTasks.push(removeDirectory(join14(".augment", "ignore")));
|
|
2589
2749
|
break;
|
|
2590
2750
|
case "augmentcode-legacy":
|
|
2591
2751
|
deleteTasks.push(removeClaudeGeneratedFiles());
|
|
2592
|
-
deleteTasks.push(removeDirectory(
|
|
2752
|
+
deleteTasks.push(removeDirectory(join14(".augment", "ignore")));
|
|
2593
2753
|
break;
|
|
2594
2754
|
case "copilot":
|
|
2595
2755
|
deleteTasks.push(removeDirectory(config.outputPaths.copilot));
|
|
@@ -2602,12 +2762,14 @@ async function generateCommand(options = {}) {
|
|
|
2602
2762
|
break;
|
|
2603
2763
|
case "claudecode":
|
|
2604
2764
|
deleteTasks.push(removeClaudeGeneratedFiles());
|
|
2765
|
+
deleteTasks.push(removeDirectory(join14(".claude", "commands")));
|
|
2605
2766
|
break;
|
|
2606
2767
|
case "roo":
|
|
2607
2768
|
deleteTasks.push(removeDirectory(config.outputPaths.roo));
|
|
2608
2769
|
break;
|
|
2609
2770
|
case "geminicli":
|
|
2610
2771
|
deleteTasks.push(removeDirectory(config.outputPaths.geminicli));
|
|
2772
|
+
deleteTasks.push(removeDirectory(join14(".gemini", "commands")));
|
|
2611
2773
|
break;
|
|
2612
2774
|
case "kiro":
|
|
2613
2775
|
deleteTasks.push(removeDirectory(config.outputPaths.kiro));
|
|
@@ -2672,11 +2834,37 @@ Generating configurations for base directory: ${baseDir}`);
|
|
|
2672
2834
|
}
|
|
2673
2835
|
}
|
|
2674
2836
|
}
|
|
2675
|
-
|
|
2837
|
+
if (config.verbose) {
|
|
2838
|
+
console.log("\nGenerating command files...");
|
|
2839
|
+
}
|
|
2840
|
+
let totalCommandOutputs = 0;
|
|
2841
|
+
for (const baseDir of baseDirs) {
|
|
2842
|
+
const commandResults = await generateCommands(
|
|
2843
|
+
process.cwd(),
|
|
2844
|
+
baseDir === process.cwd() ? void 0 : baseDir,
|
|
2845
|
+
config.defaultTargets
|
|
2846
|
+
);
|
|
2847
|
+
if (commandResults.length === 0) {
|
|
2848
|
+
if (config.verbose) {
|
|
2849
|
+
console.log(`No commands found for ${baseDir}`);
|
|
2850
|
+
}
|
|
2851
|
+
continue;
|
|
2852
|
+
}
|
|
2853
|
+
for (const result of commandResults) {
|
|
2854
|
+
await writeFileContent(result.filepath, result.content);
|
|
2855
|
+
console.log(`\u2705 Generated ${result.tool} command: ${result.filepath}`);
|
|
2856
|
+
totalCommandOutputs++;
|
|
2857
|
+
}
|
|
2858
|
+
}
|
|
2859
|
+
const totalGenerated = totalOutputs + totalMcpOutputs + totalCommandOutputs;
|
|
2676
2860
|
if (totalGenerated > 0) {
|
|
2861
|
+
const parts = [];
|
|
2862
|
+
if (totalOutputs > 0) parts.push(`${totalOutputs} configurations`);
|
|
2863
|
+
if (totalMcpOutputs > 0) parts.push(`${totalMcpOutputs} MCP configurations`);
|
|
2864
|
+
if (totalCommandOutputs > 0) parts.push(`${totalCommandOutputs} commands`);
|
|
2677
2865
|
console.log(
|
|
2678
2866
|
`
|
|
2679
|
-
\u{1F389} All done! Generated ${totalGenerated} file(s) total (${
|
|
2867
|
+
\u{1F389} All done! Generated ${totalGenerated} file(s) total (${parts.join(" + ")})`
|
|
2680
2868
|
);
|
|
2681
2869
|
}
|
|
2682
2870
|
} catch (error) {
|
|
@@ -2687,9 +2875,9 @@ Generating configurations for base directory: ${baseDir}`);
|
|
|
2687
2875
|
|
|
2688
2876
|
// src/cli/commands/gitignore.ts
|
|
2689
2877
|
import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
2690
|
-
import { join as
|
|
2878
|
+
import { join as join15 } from "path";
|
|
2691
2879
|
var gitignoreCommand = async () => {
|
|
2692
|
-
const gitignorePath =
|
|
2880
|
+
const gitignorePath = join15(process.cwd(), ".gitignore");
|
|
2693
2881
|
const rulesFilesToIgnore = [
|
|
2694
2882
|
"# Generated by rulesync - AI tool configuration files",
|
|
2695
2883
|
"**/.github/copilot-instructions.md",
|
|
@@ -2700,6 +2888,7 @@ var gitignoreCommand = async () => {
|
|
|
2700
2888
|
"**/.clineignore",
|
|
2701
2889
|
"**/CLAUDE.md",
|
|
2702
2890
|
"**/.claude/memories/",
|
|
2891
|
+
"**/.claude/commands/",
|
|
2703
2892
|
"**/codex.md",
|
|
2704
2893
|
"**/.codexignore",
|
|
2705
2894
|
"**/.roo/rules/",
|
|
@@ -2707,6 +2896,7 @@ var gitignoreCommand = async () => {
|
|
|
2707
2896
|
"**/.copilotignore",
|
|
2708
2897
|
"**/GEMINI.md",
|
|
2709
2898
|
"**/.gemini/memories/",
|
|
2899
|
+
"**/.gemini/commands/",
|
|
2710
2900
|
"**/.aiexclude",
|
|
2711
2901
|
"**/.aiignore",
|
|
2712
2902
|
"**/.augmentignore",
|
|
@@ -2753,12 +2943,12 @@ ${linesToAdd.join("\n")}
|
|
|
2753
2943
|
};
|
|
2754
2944
|
|
|
2755
2945
|
// src/core/importer.ts
|
|
2756
|
-
import { join as
|
|
2757
|
-
import
|
|
2946
|
+
import { join as join22 } from "path";
|
|
2947
|
+
import matter7 from "gray-matter";
|
|
2758
2948
|
|
|
2759
2949
|
// src/parsers/augmentcode.ts
|
|
2760
|
-
import { basename as
|
|
2761
|
-
import
|
|
2950
|
+
import { basename as basename3, join as join16 } from "path";
|
|
2951
|
+
import matter3 from "gray-matter";
|
|
2762
2952
|
|
|
2763
2953
|
// src/utils/parser-helpers.ts
|
|
2764
2954
|
function createParseResult() {
|
|
@@ -2806,7 +2996,7 @@ async function parseAugmentcodeLegacyConfiguration(baseDir = process.cwd()) {
|
|
|
2806
2996
|
async function parseUnifiedAugmentcode(baseDir, config) {
|
|
2807
2997
|
const result = createParseResult();
|
|
2808
2998
|
if (config.rulesDir) {
|
|
2809
|
-
const rulesDir =
|
|
2999
|
+
const rulesDir = join16(baseDir, config.rulesDir);
|
|
2810
3000
|
if (await fileExists(rulesDir)) {
|
|
2811
3001
|
const rulesResult = await parseAugmentRules(rulesDir, config);
|
|
2812
3002
|
addRules(result, rulesResult.rules);
|
|
@@ -2819,7 +3009,7 @@ async function parseUnifiedAugmentcode(baseDir, config) {
|
|
|
2819
3009
|
}
|
|
2820
3010
|
}
|
|
2821
3011
|
if (config.legacyFilePath) {
|
|
2822
|
-
const legacyPath =
|
|
3012
|
+
const legacyPath = join16(baseDir, config.legacyFilePath);
|
|
2823
3013
|
if (await fileExists(legacyPath)) {
|
|
2824
3014
|
const legacyResult = await parseAugmentGuidelines(legacyPath, config);
|
|
2825
3015
|
if (legacyResult.rule) {
|
|
@@ -2843,16 +3033,16 @@ async function parseAugmentRules(rulesDir, config) {
|
|
|
2843
3033
|
const files = await readdir2(rulesDir);
|
|
2844
3034
|
for (const file of files) {
|
|
2845
3035
|
if (file.endsWith(".md") || file.endsWith(".mdc")) {
|
|
2846
|
-
const filePath =
|
|
3036
|
+
const filePath = join16(rulesDir, file);
|
|
2847
3037
|
try {
|
|
2848
3038
|
const rawContent = await readFileContent(filePath);
|
|
2849
|
-
const parsed =
|
|
3039
|
+
const parsed = matter3(rawContent);
|
|
2850
3040
|
const frontmatterData = parsed.data;
|
|
2851
3041
|
const ruleType = frontmatterData.type || "manual";
|
|
2852
3042
|
const description = frontmatterData.description || "";
|
|
2853
3043
|
const tags = Array.isArray(frontmatterData.tags) ? frontmatterData.tags : void 0;
|
|
2854
3044
|
const isRoot = ruleType === "always";
|
|
2855
|
-
const filename =
|
|
3045
|
+
const filename = basename3(file, file.endsWith(".mdc") ? ".mdc" : ".md");
|
|
2856
3046
|
const frontmatter = {
|
|
2857
3047
|
root: isRoot,
|
|
2858
3048
|
targets: [config.targetName],
|
|
@@ -2910,8 +3100,8 @@ async function parseAugmentGuidelines(guidelinesPath, config) {
|
|
|
2910
3100
|
}
|
|
2911
3101
|
|
|
2912
3102
|
// src/parsers/shared-helpers.ts
|
|
2913
|
-
import { basename as
|
|
2914
|
-
import
|
|
3103
|
+
import { basename as basename4, join as join17 } from "path";
|
|
3104
|
+
import matter4 from "gray-matter";
|
|
2915
3105
|
async function parseConfigurationFiles(baseDir = process.cwd(), config) {
|
|
2916
3106
|
const errors = [];
|
|
2917
3107
|
const rules = [];
|
|
@@ -2924,7 +3114,7 @@ async function parseConfigurationFiles(baseDir = process.cwd(), config) {
|
|
|
2924
3114
|
let content;
|
|
2925
3115
|
let frontmatter;
|
|
2926
3116
|
if (mainFile.useFrontmatter) {
|
|
2927
|
-
const parsed =
|
|
3117
|
+
const parsed = matter4(rawContent);
|
|
2928
3118
|
content = parsed.content.trim();
|
|
2929
3119
|
const parsedFrontmatter = parsed.data;
|
|
2930
3120
|
frontmatter = {
|
|
@@ -2966,14 +3156,14 @@ async function parseConfigurationFiles(baseDir = process.cwd(), config) {
|
|
|
2966
3156
|
const files = await readdir2(dirPath);
|
|
2967
3157
|
for (const file of files) {
|
|
2968
3158
|
if (file.endsWith(dirConfig.filePattern)) {
|
|
2969
|
-
const filePath =
|
|
3159
|
+
const filePath = join17(dirPath, file);
|
|
2970
3160
|
const fileResult = await safeAsyncOperation(async () => {
|
|
2971
3161
|
const rawContent = await readFileContent(filePath);
|
|
2972
3162
|
let content;
|
|
2973
3163
|
let frontmatter;
|
|
2974
3164
|
const filename = file.replace(new RegExp(`\\${dirConfig.filePattern}$`), "");
|
|
2975
3165
|
if (dirConfig.filePattern === ".instructions.md") {
|
|
2976
|
-
const parsed =
|
|
3166
|
+
const parsed = matter4(rawContent);
|
|
2977
3167
|
content = parsed.content.trim();
|
|
2978
3168
|
const parsedFrontmatter = parsed.data;
|
|
2979
3169
|
frontmatter = {
|
|
@@ -3039,6 +3229,13 @@ async function parseMemoryBasedConfiguration(baseDir = process.cwd(), config) {
|
|
|
3039
3229
|
const memoryRules = await parseMemoryFiles(memoryDir, config);
|
|
3040
3230
|
rules.push(...memoryRules);
|
|
3041
3231
|
}
|
|
3232
|
+
if (config.commandsDirPath) {
|
|
3233
|
+
const commandsDir = resolvePath(config.commandsDirPath, baseDir);
|
|
3234
|
+
if (await fileExists(commandsDir)) {
|
|
3235
|
+
const commandsRules = await parseCommandsFiles(commandsDir, config);
|
|
3236
|
+
rules.push(...commandsRules);
|
|
3237
|
+
}
|
|
3238
|
+
}
|
|
3042
3239
|
const settingsPath = resolvePath(config.settingsPath, baseDir);
|
|
3043
3240
|
if (await fileExists(settingsPath)) {
|
|
3044
3241
|
const settingsResult = await parseSettingsFile(settingsPath, config.tool);
|
|
@@ -3104,10 +3301,10 @@ async function parseMemoryFiles(memoryDir, config) {
|
|
|
3104
3301
|
const files = await readdir2(memoryDir);
|
|
3105
3302
|
for (const file of files) {
|
|
3106
3303
|
if (file.endsWith(".md")) {
|
|
3107
|
-
const filePath =
|
|
3304
|
+
const filePath = join17(memoryDir, file);
|
|
3108
3305
|
const content = await readFileContent(filePath);
|
|
3109
3306
|
if (content.trim()) {
|
|
3110
|
-
const filename =
|
|
3307
|
+
const filename = basename4(file, ".md");
|
|
3111
3308
|
const frontmatter = {
|
|
3112
3309
|
root: false,
|
|
3113
3310
|
targets: [config.tool],
|
|
@@ -3127,6 +3324,54 @@ async function parseMemoryFiles(memoryDir, config) {
|
|
|
3127
3324
|
}
|
|
3128
3325
|
return rules;
|
|
3129
3326
|
}
|
|
3327
|
+
async function parseCommandsFiles(commandsDir, config) {
|
|
3328
|
+
const rules = [];
|
|
3329
|
+
try {
|
|
3330
|
+
const { readdir: readdir2 } = await import("fs/promises");
|
|
3331
|
+
const files = await readdir2(commandsDir);
|
|
3332
|
+
for (const file of files) {
|
|
3333
|
+
if (file.endsWith(".md")) {
|
|
3334
|
+
const filePath = join17(commandsDir, file);
|
|
3335
|
+
const content = await readFileContent(filePath);
|
|
3336
|
+
if (content.trim()) {
|
|
3337
|
+
const filename = basename4(file, ".md");
|
|
3338
|
+
let frontmatter;
|
|
3339
|
+
let ruleContent;
|
|
3340
|
+
try {
|
|
3341
|
+
const parsed = matter4(content);
|
|
3342
|
+
ruleContent = parsed.content.trim();
|
|
3343
|
+
const parsedFrontmatter = parsed.data;
|
|
3344
|
+
frontmatter = {
|
|
3345
|
+
root: false,
|
|
3346
|
+
targets: [config.tool],
|
|
3347
|
+
description: parsedFrontmatter.description || `Command: ${filename}`,
|
|
3348
|
+
globs: ["**/*"]
|
|
3349
|
+
};
|
|
3350
|
+
} catch {
|
|
3351
|
+
ruleContent = content.trim();
|
|
3352
|
+
frontmatter = {
|
|
3353
|
+
root: false,
|
|
3354
|
+
targets: [config.tool],
|
|
3355
|
+
description: `Command: ${filename}`,
|
|
3356
|
+
globs: ["**/*"]
|
|
3357
|
+
};
|
|
3358
|
+
}
|
|
3359
|
+
if (ruleContent) {
|
|
3360
|
+
rules.push({
|
|
3361
|
+
frontmatter,
|
|
3362
|
+
content: ruleContent,
|
|
3363
|
+
filename,
|
|
3364
|
+
filepath: filePath,
|
|
3365
|
+
type: "command"
|
|
3366
|
+
});
|
|
3367
|
+
}
|
|
3368
|
+
}
|
|
3369
|
+
}
|
|
3370
|
+
}
|
|
3371
|
+
} catch {
|
|
3372
|
+
}
|
|
3373
|
+
return rules;
|
|
3374
|
+
}
|
|
3130
3375
|
async function parseSettingsFile(settingsPath, tool) {
|
|
3131
3376
|
const errors = [];
|
|
3132
3377
|
let ignorePatterns;
|
|
@@ -3174,7 +3419,8 @@ async function parseClaudeConfiguration(baseDir = process.cwd()) {
|
|
|
3174
3419
|
settingsPath: ".claude/settings.json",
|
|
3175
3420
|
mainDescription: "Main Claude Code configuration",
|
|
3176
3421
|
memoryDescription: "Memory file",
|
|
3177
|
-
filenamePrefix: "claude"
|
|
3422
|
+
filenamePrefix: "claude",
|
|
3423
|
+
commandsDirPath: ".claude/commands"
|
|
3178
3424
|
});
|
|
3179
3425
|
}
|
|
3180
3426
|
|
|
@@ -3199,7 +3445,7 @@ async function parseClineConfiguration(baseDir = process.cwd()) {
|
|
|
3199
3445
|
}
|
|
3200
3446
|
|
|
3201
3447
|
// src/parsers/codexcli.ts
|
|
3202
|
-
import { join as
|
|
3448
|
+
import { join as join18 } from "path";
|
|
3203
3449
|
|
|
3204
3450
|
// src/parsers/copilot.ts
|
|
3205
3451
|
async function parseCopilotConfiguration(baseDir = process.cwd()) {
|
|
@@ -3222,10 +3468,10 @@ async function parseCopilotConfiguration(baseDir = process.cwd()) {
|
|
|
3222
3468
|
}
|
|
3223
3469
|
|
|
3224
3470
|
// src/parsers/cursor.ts
|
|
3225
|
-
import { basename as
|
|
3226
|
-
import
|
|
3471
|
+
import { basename as basename5, join as join19 } from "path";
|
|
3472
|
+
import matter5 from "gray-matter";
|
|
3227
3473
|
import { DEFAULT_SCHEMA, FAILSAFE_SCHEMA, load } from "js-yaml";
|
|
3228
|
-
import { z as
|
|
3474
|
+
import { z as z7 } from "zod/mini";
|
|
3229
3475
|
var customMatterOptions = {
|
|
3230
3476
|
engines: {
|
|
3231
3477
|
yaml: {
|
|
@@ -3253,7 +3499,7 @@ var customMatterOptions = {
|
|
|
3253
3499
|
}
|
|
3254
3500
|
};
|
|
3255
3501
|
function convertCursorMdcFrontmatter(cursorFrontmatter, _filename) {
|
|
3256
|
-
const FrontmatterSchema =
|
|
3502
|
+
const FrontmatterSchema = z7.record(z7.string(), z7.unknown());
|
|
3257
3503
|
const parseResult = FrontmatterSchema.safeParse(cursorFrontmatter);
|
|
3258
3504
|
if (!parseResult.success) {
|
|
3259
3505
|
return {
|
|
@@ -3347,11 +3593,11 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
|
|
|
3347
3593
|
const rules = [];
|
|
3348
3594
|
let ignorePatterns;
|
|
3349
3595
|
let mcpServers;
|
|
3350
|
-
const cursorFilePath =
|
|
3596
|
+
const cursorFilePath = join19(baseDir, ".cursorrules");
|
|
3351
3597
|
if (await fileExists(cursorFilePath)) {
|
|
3352
3598
|
try {
|
|
3353
3599
|
const rawContent = await readFileContent(cursorFilePath);
|
|
3354
|
-
const parsed =
|
|
3600
|
+
const parsed = matter5(rawContent, customMatterOptions);
|
|
3355
3601
|
const content = parsed.content.trim();
|
|
3356
3602
|
if (content) {
|
|
3357
3603
|
const frontmatter = convertCursorMdcFrontmatter(parsed.data, "cursorrules");
|
|
@@ -3368,20 +3614,20 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
|
|
|
3368
3614
|
errors.push(`Failed to parse .cursorrules file: ${errorMessage}`);
|
|
3369
3615
|
}
|
|
3370
3616
|
}
|
|
3371
|
-
const cursorRulesDir =
|
|
3617
|
+
const cursorRulesDir = join19(baseDir, ".cursor", "rules");
|
|
3372
3618
|
if (await fileExists(cursorRulesDir)) {
|
|
3373
3619
|
try {
|
|
3374
3620
|
const { readdir: readdir2 } = await import("fs/promises");
|
|
3375
3621
|
const files = await readdir2(cursorRulesDir);
|
|
3376
3622
|
for (const file of files) {
|
|
3377
3623
|
if (file.endsWith(".mdc")) {
|
|
3378
|
-
const filePath =
|
|
3624
|
+
const filePath = join19(cursorRulesDir, file);
|
|
3379
3625
|
try {
|
|
3380
3626
|
const rawContent = await readFileContent(filePath);
|
|
3381
|
-
const parsed =
|
|
3627
|
+
const parsed = matter5(rawContent, customMatterOptions);
|
|
3382
3628
|
const content = parsed.content.trim();
|
|
3383
3629
|
if (content) {
|
|
3384
|
-
const filename =
|
|
3630
|
+
const filename = basename5(file, ".mdc");
|
|
3385
3631
|
const frontmatter = convertCursorMdcFrontmatter(parsed.data, filename);
|
|
3386
3632
|
rules.push({
|
|
3387
3633
|
frontmatter,
|
|
@@ -3404,7 +3650,7 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
|
|
|
3404
3650
|
if (rules.length === 0) {
|
|
3405
3651
|
errors.push("No Cursor configuration files found (.cursorrules or .cursor/rules/*.mdc)");
|
|
3406
3652
|
}
|
|
3407
|
-
const cursorIgnorePath =
|
|
3653
|
+
const cursorIgnorePath = join19(baseDir, ".cursorignore");
|
|
3408
3654
|
if (await fileExists(cursorIgnorePath)) {
|
|
3409
3655
|
try {
|
|
3410
3656
|
const content = await readFileContent(cursorIgnorePath);
|
|
@@ -3417,7 +3663,7 @@ async function parseCursorConfiguration(baseDir = process.cwd()) {
|
|
|
3417
3663
|
errors.push(`Failed to parse .cursorignore: ${errorMessage}`);
|
|
3418
3664
|
}
|
|
3419
3665
|
}
|
|
3420
|
-
const cursorMcpPath =
|
|
3666
|
+
const cursorMcpPath = join19(baseDir, ".cursor", "mcp.json");
|
|
3421
3667
|
if (await fileExists(cursorMcpPath)) {
|
|
3422
3668
|
try {
|
|
3423
3669
|
const content = await readFileContent(cursorMcpPath);
|
|
@@ -3461,16 +3707,17 @@ async function parseGeminiConfiguration(baseDir = process.cwd()) {
|
|
|
3461
3707
|
additionalIgnoreFile: {
|
|
3462
3708
|
path: ".aiexclude",
|
|
3463
3709
|
parser: parseAiexclude
|
|
3464
|
-
}
|
|
3710
|
+
},
|
|
3711
|
+
commandsDirPath: ".gemini/commands"
|
|
3465
3712
|
});
|
|
3466
3713
|
}
|
|
3467
3714
|
|
|
3468
3715
|
// src/parsers/junie.ts
|
|
3469
|
-
import { join as
|
|
3716
|
+
import { join as join20 } from "path";
|
|
3470
3717
|
async function parseJunieConfiguration(baseDir = process.cwd()) {
|
|
3471
3718
|
const errors = [];
|
|
3472
3719
|
const rules = [];
|
|
3473
|
-
const guidelinesPath =
|
|
3720
|
+
const guidelinesPath = join20(baseDir, ".junie", "guidelines.md");
|
|
3474
3721
|
if (!await fileExists(guidelinesPath)) {
|
|
3475
3722
|
errors.push(".junie/guidelines.md file not found");
|
|
3476
3723
|
return { rules, errors };
|
|
@@ -3523,8 +3770,8 @@ async function parseRooConfiguration(baseDir = process.cwd()) {
|
|
|
3523
3770
|
|
|
3524
3771
|
// src/parsers/windsurf.ts
|
|
3525
3772
|
import { readFile as readFile2 } from "fs/promises";
|
|
3526
|
-
import { join as
|
|
3527
|
-
import
|
|
3773
|
+
import { join as join21 } from "path";
|
|
3774
|
+
import matter6 from "gray-matter";
|
|
3528
3775
|
|
|
3529
3776
|
// src/core/importer.ts
|
|
3530
3777
|
async function importConfiguration(options) {
|
|
@@ -3610,7 +3857,7 @@ async function importConfiguration(options) {
|
|
|
3610
3857
|
if (rules.length === 0 && !ignorePatterns && !mcpServers) {
|
|
3611
3858
|
return { success: false, rulesCreated: 0, errors };
|
|
3612
3859
|
}
|
|
3613
|
-
const rulesDirPath =
|
|
3860
|
+
const rulesDirPath = join22(baseDir, rulesDir);
|
|
3614
3861
|
try {
|
|
3615
3862
|
const { mkdir: mkdir3 } = await import("fs/promises");
|
|
3616
3863
|
await mkdir3(rulesDirPath, { recursive: true });
|
|
@@ -3623,8 +3870,13 @@ async function importConfiguration(options) {
|
|
|
3623
3870
|
for (const rule of rules) {
|
|
3624
3871
|
try {
|
|
3625
3872
|
const baseFilename = rule.filename;
|
|
3626
|
-
|
|
3627
|
-
|
|
3873
|
+
let targetDir = rulesDirPath;
|
|
3874
|
+
if (rule.type === "command") {
|
|
3875
|
+
targetDir = join22(rulesDirPath, "commands");
|
|
3876
|
+
const { mkdir: mkdir3 } = await import("fs/promises");
|
|
3877
|
+
await mkdir3(targetDir, { recursive: true });
|
|
3878
|
+
}
|
|
3879
|
+
const filePath = join22(targetDir, `${baseFilename}.md`);
|
|
3628
3880
|
const content = generateRuleFileContent(rule);
|
|
3629
3881
|
await writeFileContent(filePath, content);
|
|
3630
3882
|
rulesCreated++;
|
|
@@ -3639,7 +3891,7 @@ async function importConfiguration(options) {
|
|
|
3639
3891
|
let ignoreFileCreated = false;
|
|
3640
3892
|
if (ignorePatterns && ignorePatterns.length > 0) {
|
|
3641
3893
|
try {
|
|
3642
|
-
const rulesyncignorePath =
|
|
3894
|
+
const rulesyncignorePath = join22(baseDir, ".rulesyncignore");
|
|
3643
3895
|
const ignoreContent = `${ignorePatterns.join("\n")}
|
|
3644
3896
|
`;
|
|
3645
3897
|
await writeFileContent(rulesyncignorePath, ignoreContent);
|
|
@@ -3655,7 +3907,7 @@ async function importConfiguration(options) {
|
|
|
3655
3907
|
let mcpFileCreated = false;
|
|
3656
3908
|
if (mcpServers && Object.keys(mcpServers).length > 0) {
|
|
3657
3909
|
try {
|
|
3658
|
-
const mcpPath =
|
|
3910
|
+
const mcpPath = join22(baseDir, rulesDir, ".mcp.json");
|
|
3659
3911
|
const mcpContent = `${JSON.stringify({ mcpServers }, null, 2)}
|
|
3660
3912
|
`;
|
|
3661
3913
|
await writeFileContent(mcpPath, mcpContent);
|
|
@@ -3677,17 +3929,16 @@ async function importConfiguration(options) {
|
|
|
3677
3929
|
};
|
|
3678
3930
|
}
|
|
3679
3931
|
function generateRuleFileContent(rule) {
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
filename = `${baseFilename}-${counter}`;
|
|
3688
|
-
counter++;
|
|
3932
|
+
if (rule.type === "command") {
|
|
3933
|
+
const simplifiedFrontmatter = {
|
|
3934
|
+
description: rule.frontmatter.description,
|
|
3935
|
+
targets: rule.frontmatter.targets
|
|
3936
|
+
};
|
|
3937
|
+
const frontmatter2 = matter7.stringify("", simplifiedFrontmatter);
|
|
3938
|
+
return frontmatter2 + rule.content;
|
|
3689
3939
|
}
|
|
3690
|
-
|
|
3940
|
+
const frontmatter = matter7.stringify("", rule.frontmatter);
|
|
3941
|
+
return frontmatter + rule.content;
|
|
3691
3942
|
}
|
|
3692
3943
|
|
|
3693
3944
|
// src/cli/commands/import.ts
|
|
@@ -3750,7 +4001,7 @@ async function importCommand(options = {}) {
|
|
|
3750
4001
|
}
|
|
3751
4002
|
|
|
3752
4003
|
// src/cli/commands/init.ts
|
|
3753
|
-
import { join as
|
|
4004
|
+
import { join as join23 } from "path";
|
|
3754
4005
|
async function initCommand() {
|
|
3755
4006
|
const aiRulesDir = ".rulesync";
|
|
3756
4007
|
console.log("Initializing rulesync...");
|
|
@@ -3797,7 +4048,7 @@ globs: ["**/*"]
|
|
|
3797
4048
|
- Follow single responsibility principle
|
|
3798
4049
|
`
|
|
3799
4050
|
};
|
|
3800
|
-
const filepath =
|
|
4051
|
+
const filepath = join23(aiRulesDir, sampleFile.filename);
|
|
3801
4052
|
if (!await fileExists(filepath)) {
|
|
3802
4053
|
await writeFileContent(filepath, sampleFile.content);
|
|
3803
4054
|
console.log(`Created ${filepath}`);
|
|
@@ -3941,7 +4192,7 @@ async function watchCommand() {
|
|
|
3941
4192
|
|
|
3942
4193
|
// src/cli/index.ts
|
|
3943
4194
|
var program = new Command();
|
|
3944
|
-
program.name("rulesync").description("Unified AI rules management CLI tool").version("0.
|
|
4195
|
+
program.name("rulesync").description("Unified AI rules management CLI tool").version("0.58.0");
|
|
3945
4196
|
program.command("init").description("Initialize rulesync in current directory").action(initCommand);
|
|
3946
4197
|
program.command("add <filename>").description("Add a new rule file").action(addCommand);
|
|
3947
4198
|
program.command("gitignore").description("Add generated files to .gitignore").action(gitignoreCommand);
|