rulesync 3.17.1 → 3.19.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 CHANGED
@@ -70,16 +70,38 @@ chmod +x rulesync
70
70
  sudo mv rulesync /usr/local/bin/
71
71
  ```
72
72
 
73
+ #### Windows (x64)
74
+
75
+ ```powershell
76
+ # PowerShell
77
+ Invoke-WebRequest -Uri "https://github.com/dyoshikawa/rulesync/releases/latest/download/rulesync-windows-x64.exe" -OutFile "rulesync.exe"
78
+ # Add to PATH or place in a directory already in PATH
79
+ Move-Item rulesync.exe C:\Windows\System32\
80
+ ```
81
+
82
+ Or using curl (if available):
83
+
84
+ ```bash
85
+ curl -L https://github.com/dyoshikawa/rulesync/releases/latest/download/rulesync-windows-x64.exe -o rulesync.exe
86
+ # Place the binary wherever set PATH
87
+ ```
88
+
73
89
  #### Verify checksums
74
90
 
75
91
  ```bash
76
92
  curl -L https://github.com/dyoshikawa/rulesync/releases/latest/download/SHA256SUMS -o SHA256SUMS
77
- sha256sum -c SHA256SUMS
78
- ```
79
93
 
80
- #### Windows support?
94
+ # Linux/macOS
95
+ sha256sum -c SHA256SUMS
81
96
 
82
- Though Windows is not supported yet, we are positively considering it.
97
+ # Windows (PowerShell)
98
+ # Download SHA256SUMS file first, then verify:
99
+ Get-FileHash rulesync.exe -Algorithm SHA256 | ForEach-Object {
100
+ $actual = $_.Hash.ToLower()
101
+ $expected = (Get-Content SHA256SUMS | Select-String "rulesync-windows-x64.exe").ToString().Split()[0]
102
+ if ($actual -eq $expected) { "✓ Checksum verified" } else { "✗ Checksum mismatch" }
103
+ }
104
+ ```
83
105
  </details>
84
106
 
85
107
  ## Getting Started
@@ -535,6 +557,52 @@ Focus on the difference of MCP tools usage.
535
557
  So, in this case, approximately 92% reduction in MCP tools consumption!
536
558
  </details>
537
559
 
560
+ ## Rulesync MCP Server (Experimental)
561
+
562
+ Rulesync provides an MCP (Model Context Protocol) server that enables AI agents to manage your Rulesync files. This allows AI agents to discover, read, create, update, and delete files dynamically.
563
+
564
+ ### Usage
565
+
566
+ #### Starting the MCP Server
567
+
568
+ ```bash
569
+ rulesync mcp
570
+ ```
571
+
572
+ This starts an MCP server using stdio transport that AI agents can communicate with.
573
+
574
+ #### Configuration
575
+
576
+ Add the Rulesync MCP server to your `.rulesync/mcp.json`:
577
+
578
+ ```json
579
+ {
580
+ "mcpServers": {
581
+ "rulesync-mcp": {
582
+ "type": "stdio",
583
+ "command": "npx",
584
+ "args": ["-y", "rulesync", "mcp"],
585
+ "env": {}
586
+ }
587
+ }
588
+ }
589
+ ```
590
+
591
+ For development, you can use:
592
+
593
+ ```json
594
+ {
595
+ "mcpServers": {
596
+ "rulesync-mcp": {
597
+ "type": "stdio",
598
+ "command": "pnpm",
599
+ "args": ["dev", "mcp"],
600
+ "env": {}
601
+ }
602
+ }
603
+ }
604
+ ```
605
+
538
606
  ## Contributing
539
607
 
540
608
  Issues and Pull Requests are welcome!
package/dist/index.cjs CHANGED
@@ -37,6 +37,26 @@ var FeatureSchema = import_mini.z.enum(ALL_FEATURES);
37
37
  var FeaturesSchema = import_mini.z.array(FeatureSchema);
38
38
  var RulesyncFeaturesSchema = import_mini.z.array(import_mini.z.enum(ALL_FEATURES_WITH_WILDCARD));
39
39
 
40
+ // src/utils/error.ts
41
+ var import_zod = require("zod");
42
+ function isZodErrorLike(error) {
43
+ return error !== null && typeof error === "object" && "issues" in error && Array.isArray(error.issues) && error.issues.every(
44
+ (issue) => issue !== null && typeof issue === "object" && "path" in issue && Array.isArray(issue.path) && "message" in issue && typeof issue.message === "string"
45
+ );
46
+ }
47
+ function formatError(error) {
48
+ if (error instanceof import_zod.ZodError || isZodErrorLike(error)) {
49
+ return error.issues.map((issue) => {
50
+ const path2 = issue.path.length > 0 ? `${issue.path.join(".")}: ` : "";
51
+ return `${path2}${issue.message}`;
52
+ }).join("; ");
53
+ }
54
+ if (error instanceof Error) {
55
+ return `${error.name}: ${error.message}`;
56
+ }
57
+ return String(error);
58
+ }
59
+
40
60
  // src/utils/logger.ts
41
61
  var import_consola = require("consola");
42
62
 
@@ -222,26 +242,6 @@ var FeatureProcessor = class {
222
242
  }
223
243
  };
224
244
 
225
- // src/utils/error.ts
226
- var import_zod = require("zod");
227
- function isZodErrorLike(error) {
228
- return error !== null && typeof error === "object" && "issues" in error && Array.isArray(error.issues) && error.issues.every(
229
- (issue) => issue !== null && typeof issue === "object" && "path" in issue && Array.isArray(issue.path) && "message" in issue && typeof issue.message === "string"
230
- );
231
- }
232
- function formatError(error) {
233
- if (error instanceof import_zod.ZodError || isZodErrorLike(error)) {
234
- return error.issues.map((issue) => {
235
- const path2 = issue.path.length > 0 ? `${issue.path.join(".")}: ` : "";
236
- return `${path2}${issue.message}`;
237
- }).join("; ");
238
- }
239
- if (error instanceof Error) {
240
- return `${error.name}: ${error.message}`;
241
- }
242
- return String(error);
243
- }
244
-
245
245
  // src/commands/agentsmd-command.ts
246
246
  var import_node_path4 = require("path");
247
247
 
@@ -7706,8 +7706,200 @@ Attention, again, you are just the planner, so though you can read any files and
7706
7706
  }
7707
7707
  }
7708
7708
 
7709
+ // src/cli/commands/mcp.ts
7710
+ var import_node_path68 = require("path");
7711
+ var import_fastmcp = require("fastmcp");
7712
+ var import_mini23 = require("zod/mini");
7713
+ var MAX_RULE_SIZE_BYTES = 1024 * 1024;
7714
+ var MAX_RULES_COUNT = 1e3;
7715
+ var RULES_DIR_PREFIX = ".rulesync/rules";
7716
+ function validateRulePath(relativePathFromCwd) {
7717
+ const normalizedPath = relativePathFromCwd.replace(/\\/g, "/");
7718
+ if (!normalizedPath.startsWith(`${RULES_DIR_PREFIX}/`)) {
7719
+ throw new Error(`Invalid rule path: must be within ${RULES_DIR_PREFIX}/ directory`);
7720
+ }
7721
+ try {
7722
+ resolvePath(normalizedPath, process.cwd());
7723
+ } catch (error) {
7724
+ throw new Error(`Path validation failed: ${formatError(error)}`, { cause: error });
7725
+ }
7726
+ const filename = (0, import_node_path68.basename)(normalizedPath);
7727
+ if (!/^[a-zA-Z0-9_-]+\.md$/.test(filename)) {
7728
+ throw new Error(
7729
+ `Invalid filename: ${filename}. Must match pattern [a-zA-Z0-9_-]+.md (alphanumeric, hyphens, underscores only)`
7730
+ );
7731
+ }
7732
+ }
7733
+ async function listRules() {
7734
+ const rulesDir = (0, import_node_path68.join)(process.cwd(), ".rulesync", "rules");
7735
+ try {
7736
+ const files = await listDirectoryFiles(rulesDir);
7737
+ const mdFiles = files.filter((file) => file.endsWith(".md"));
7738
+ const rules = await Promise.all(
7739
+ mdFiles.map(async (file) => {
7740
+ try {
7741
+ const rule = await RulesyncRule.fromFile({
7742
+ relativeFilePath: file,
7743
+ validate: true
7744
+ });
7745
+ const frontmatter = rule.getFrontmatter();
7746
+ return {
7747
+ relativePathFromCwd: (0, import_node_path68.join)(".rulesync", "rules", file),
7748
+ frontmatter
7749
+ };
7750
+ } catch (error) {
7751
+ logger.error(`Failed to read rule file ${file}: ${formatError(error)}`);
7752
+ return null;
7753
+ }
7754
+ })
7755
+ );
7756
+ return rules.filter((rule) => rule !== null);
7757
+ } catch (error) {
7758
+ logger.error(`Failed to read rules directory: ${formatError(error)}`);
7759
+ return [];
7760
+ }
7761
+ }
7762
+ async function getRule({ relativePathFromCwd }) {
7763
+ validateRulePath(relativePathFromCwd);
7764
+ const filename = (0, import_node_path68.basename)(relativePathFromCwd);
7765
+ try {
7766
+ const rule = await RulesyncRule.fromFile({
7767
+ relativeFilePath: filename,
7768
+ validate: true
7769
+ });
7770
+ return {
7771
+ relativePathFromCwd: (0, import_node_path68.join)(".rulesync", "rules", filename),
7772
+ frontmatter: rule.getFrontmatter(),
7773
+ body: rule.getBody()
7774
+ };
7775
+ } catch (error) {
7776
+ throw new Error(`Failed to read rule file ${relativePathFromCwd}: ${formatError(error)}`, {
7777
+ cause: error
7778
+ });
7779
+ }
7780
+ }
7781
+ async function putRule({
7782
+ relativePathFromCwd,
7783
+ frontmatter,
7784
+ body
7785
+ }) {
7786
+ validateRulePath(relativePathFromCwd);
7787
+ const filename = (0, import_node_path68.basename)(relativePathFromCwd);
7788
+ const estimatedSize = JSON.stringify(frontmatter).length + body.length;
7789
+ if (estimatedSize > MAX_RULE_SIZE_BYTES) {
7790
+ throw new Error(
7791
+ `Rule size ${estimatedSize} bytes exceeds maximum ${MAX_RULE_SIZE_BYTES} bytes (1MB)`
7792
+ );
7793
+ }
7794
+ try {
7795
+ const existingRules = await listRules();
7796
+ const isUpdate = existingRules.some(
7797
+ (rule2) => rule2.relativePathFromCwd === (0, import_node_path68.join)(".rulesync", "rules", filename)
7798
+ );
7799
+ if (!isUpdate && existingRules.length >= MAX_RULES_COUNT) {
7800
+ throw new Error(`Maximum number of rules (${MAX_RULES_COUNT}) reached`);
7801
+ }
7802
+ const rule = new RulesyncRule({
7803
+ baseDir: process.cwd(),
7804
+ relativeDirPath: ".rulesync/rules",
7805
+ relativeFilePath: filename,
7806
+ frontmatter,
7807
+ body,
7808
+ validate: true
7809
+ });
7810
+ const rulesDir = (0, import_node_path68.join)(process.cwd(), ".rulesync", "rules");
7811
+ await ensureDir(rulesDir);
7812
+ await writeFileContent(rule.getFilePath(), rule.getFileContent());
7813
+ return {
7814
+ relativePathFromCwd: (0, import_node_path68.join)(".rulesync", "rules", filename),
7815
+ frontmatter: rule.getFrontmatter(),
7816
+ body: rule.getBody()
7817
+ };
7818
+ } catch (error) {
7819
+ throw new Error(`Failed to write rule file ${relativePathFromCwd}: ${formatError(error)}`, {
7820
+ cause: error
7821
+ });
7822
+ }
7823
+ }
7824
+ async function deleteRule({ relativePathFromCwd }) {
7825
+ validateRulePath(relativePathFromCwd);
7826
+ const filename = (0, import_node_path68.basename)(relativePathFromCwd);
7827
+ const fullPath = (0, import_node_path68.join)(process.cwd(), ".rulesync", "rules", filename);
7828
+ try {
7829
+ await removeFile(fullPath);
7830
+ return {
7831
+ relativePathFromCwd: (0, import_node_path68.join)(".rulesync", "rules", filename)
7832
+ };
7833
+ } catch (error) {
7834
+ throw new Error(`Failed to delete rule file ${relativePathFromCwd}: ${formatError(error)}`, {
7835
+ cause: error
7836
+ });
7837
+ }
7838
+ }
7839
+ async function mcpCommand({ version }) {
7840
+ const server = new import_fastmcp.FastMCP({
7841
+ name: "rulesync-mcp-server",
7842
+ // Type assertion is safe here because version comes from package.json which follows semver
7843
+ // eslint-disable-next-line no-type-assertion/no-type-assertion
7844
+ version
7845
+ });
7846
+ server.addTool({
7847
+ name: "listRules",
7848
+ description: "List all rules from .rulesync/rules/*.md with their frontmatter",
7849
+ parameters: import_mini23.z.object({}),
7850
+ execute: async () => {
7851
+ const rules = await listRules();
7852
+ const output = { rules };
7853
+ return JSON.stringify(output, null, 2);
7854
+ }
7855
+ });
7856
+ server.addTool({
7857
+ name: "getRule",
7858
+ description: "Get detailed information about a specific rule. relativePathFromCwd parameter is required.",
7859
+ parameters: import_mini23.z.object({
7860
+ relativePathFromCwd: import_mini23.z.string()
7861
+ }),
7862
+ execute: async (args) => {
7863
+ const result = await getRule({ relativePathFromCwd: args.relativePathFromCwd });
7864
+ return JSON.stringify(result, null, 2);
7865
+ }
7866
+ });
7867
+ server.addTool({
7868
+ name: "putRule",
7869
+ description: "Create or update a rule (upsert operation). relativePathFromCwd, frontmatter, and body parameters are required.",
7870
+ parameters: import_mini23.z.object({
7871
+ relativePathFromCwd: import_mini23.z.string(),
7872
+ frontmatter: RulesyncRuleFrontmatterSchema,
7873
+ body: import_mini23.z.string()
7874
+ }),
7875
+ execute: async (args) => {
7876
+ const result = await putRule({
7877
+ relativePathFromCwd: args.relativePathFromCwd,
7878
+ frontmatter: args.frontmatter,
7879
+ body: args.body
7880
+ });
7881
+ return JSON.stringify(result, null, 2);
7882
+ }
7883
+ });
7884
+ server.addTool({
7885
+ name: "deleteRule",
7886
+ description: "Delete a rule file. relativePathFromCwd parameter is required.",
7887
+ parameters: import_mini23.z.object({
7888
+ relativePathFromCwd: import_mini23.z.string()
7889
+ }),
7890
+ execute: async (args) => {
7891
+ const result = await deleteRule({ relativePathFromCwd: args.relativePathFromCwd });
7892
+ return JSON.stringify(result, null, 2);
7893
+ }
7894
+ });
7895
+ logger.info("Rulesync MCP server started via stdio");
7896
+ void server.start({
7897
+ transportType: "stdio"
7898
+ });
7899
+ }
7900
+
7709
7901
  // src/cli/index.ts
7710
- var getVersion = () => "3.17.1";
7902
+ var getVersion = () => "3.19.0";
7711
7903
  var main = async () => {
7712
7904
  const program = new import_commander.Command();
7713
7905
  const version = getVersion();
@@ -7745,7 +7937,15 @@ var main = async () => {
7745
7937
  experimentalGlobal: options.experimentalGlobal
7746
7938
  });
7747
7939
  } catch (error) {
7748
- logger.error(error instanceof Error ? error.message : String(error));
7940
+ logger.error(formatError(error));
7941
+ process.exit(1);
7942
+ }
7943
+ });
7944
+ program.command("mcp").description("Start MCP server for rulesync").action(async () => {
7945
+ try {
7946
+ await mcpCommand({ version });
7947
+ } catch (error) {
7948
+ logger.error(formatError(error));
7749
7949
  process.exit(1);
7750
7950
  }
7751
7951
  });
@@ -7800,13 +8000,13 @@ var main = async () => {
7800
8000
  experimentalSimulateSubagents: options.experimentalSimulateSubagents
7801
8001
  });
7802
8002
  } catch (error) {
7803
- logger.error(error instanceof Error ? error.message : String(error));
8003
+ logger.error(formatError(error));
7804
8004
  process.exit(1);
7805
8005
  }
7806
8006
  });
7807
8007
  program.parse();
7808
8008
  };
7809
8009
  main().catch((error) => {
7810
- logger.error(error instanceof Error ? error.message : String(error));
8010
+ logger.error(formatError(error));
7811
8011
  process.exit(1);
7812
8012
  });
package/dist/index.js CHANGED
@@ -14,6 +14,26 @@ var FeatureSchema = z.enum(ALL_FEATURES);
14
14
  var FeaturesSchema = z.array(FeatureSchema);
15
15
  var RulesyncFeaturesSchema = z.array(z.enum(ALL_FEATURES_WITH_WILDCARD));
16
16
 
17
+ // src/utils/error.ts
18
+ import { ZodError } from "zod";
19
+ function isZodErrorLike(error) {
20
+ return error !== null && typeof error === "object" && "issues" in error && Array.isArray(error.issues) && error.issues.every(
21
+ (issue) => issue !== null && typeof issue === "object" && "path" in issue && Array.isArray(issue.path) && "message" in issue && typeof issue.message === "string"
22
+ );
23
+ }
24
+ function formatError(error) {
25
+ if (error instanceof ZodError || isZodErrorLike(error)) {
26
+ return error.issues.map((issue) => {
27
+ const path2 = issue.path.length > 0 ? `${issue.path.join(".")}: ` : "";
28
+ return `${path2}${issue.message}`;
29
+ }).join("; ");
30
+ }
31
+ if (error instanceof Error) {
32
+ return `${error.name}: ${error.message}`;
33
+ }
34
+ return String(error);
35
+ }
36
+
17
37
  // src/utils/logger.ts
18
38
  import { consola } from "consola";
19
39
 
@@ -199,26 +219,6 @@ var FeatureProcessor = class {
199
219
  }
200
220
  };
201
221
 
202
- // src/utils/error.ts
203
- import { ZodError } from "zod";
204
- function isZodErrorLike(error) {
205
- return error !== null && typeof error === "object" && "issues" in error && Array.isArray(error.issues) && error.issues.every(
206
- (issue) => issue !== null && typeof issue === "object" && "path" in issue && Array.isArray(issue.path) && "message" in issue && typeof issue.message === "string"
207
- );
208
- }
209
- function formatError(error) {
210
- if (error instanceof ZodError || isZodErrorLike(error)) {
211
- return error.issues.map((issue) => {
212
- const path2 = issue.path.length > 0 ? `${issue.path.join(".")}: ` : "";
213
- return `${path2}${issue.message}`;
214
- }).join("; ");
215
- }
216
- if (error instanceof Error) {
217
- return `${error.name}: ${error.message}`;
218
- }
219
- return String(error);
220
- }
221
-
222
222
  // src/commands/agentsmd-command.ts
223
223
  import { basename as basename3, join as join3 } from "path";
224
224
 
@@ -7683,8 +7683,200 @@ Attention, again, you are just the planner, so though you can read any files and
7683
7683
  }
7684
7684
  }
7685
7685
 
7686
+ // src/cli/commands/mcp.ts
7687
+ import { basename as basename18, join as join66 } from "path";
7688
+ import { FastMCP } from "fastmcp";
7689
+ import { z as z23 } from "zod/mini";
7690
+ var MAX_RULE_SIZE_BYTES = 1024 * 1024;
7691
+ var MAX_RULES_COUNT = 1e3;
7692
+ var RULES_DIR_PREFIX = ".rulesync/rules";
7693
+ function validateRulePath(relativePathFromCwd) {
7694
+ const normalizedPath = relativePathFromCwd.replace(/\\/g, "/");
7695
+ if (!normalizedPath.startsWith(`${RULES_DIR_PREFIX}/`)) {
7696
+ throw new Error(`Invalid rule path: must be within ${RULES_DIR_PREFIX}/ directory`);
7697
+ }
7698
+ try {
7699
+ resolvePath(normalizedPath, process.cwd());
7700
+ } catch (error) {
7701
+ throw new Error(`Path validation failed: ${formatError(error)}`, { cause: error });
7702
+ }
7703
+ const filename = basename18(normalizedPath);
7704
+ if (!/^[a-zA-Z0-9_-]+\.md$/.test(filename)) {
7705
+ throw new Error(
7706
+ `Invalid filename: ${filename}. Must match pattern [a-zA-Z0-9_-]+.md (alphanumeric, hyphens, underscores only)`
7707
+ );
7708
+ }
7709
+ }
7710
+ async function listRules() {
7711
+ const rulesDir = join66(process.cwd(), ".rulesync", "rules");
7712
+ try {
7713
+ const files = await listDirectoryFiles(rulesDir);
7714
+ const mdFiles = files.filter((file) => file.endsWith(".md"));
7715
+ const rules = await Promise.all(
7716
+ mdFiles.map(async (file) => {
7717
+ try {
7718
+ const rule = await RulesyncRule.fromFile({
7719
+ relativeFilePath: file,
7720
+ validate: true
7721
+ });
7722
+ const frontmatter = rule.getFrontmatter();
7723
+ return {
7724
+ relativePathFromCwd: join66(".rulesync", "rules", file),
7725
+ frontmatter
7726
+ };
7727
+ } catch (error) {
7728
+ logger.error(`Failed to read rule file ${file}: ${formatError(error)}`);
7729
+ return null;
7730
+ }
7731
+ })
7732
+ );
7733
+ return rules.filter((rule) => rule !== null);
7734
+ } catch (error) {
7735
+ logger.error(`Failed to read rules directory: ${formatError(error)}`);
7736
+ return [];
7737
+ }
7738
+ }
7739
+ async function getRule({ relativePathFromCwd }) {
7740
+ validateRulePath(relativePathFromCwd);
7741
+ const filename = basename18(relativePathFromCwd);
7742
+ try {
7743
+ const rule = await RulesyncRule.fromFile({
7744
+ relativeFilePath: filename,
7745
+ validate: true
7746
+ });
7747
+ return {
7748
+ relativePathFromCwd: join66(".rulesync", "rules", filename),
7749
+ frontmatter: rule.getFrontmatter(),
7750
+ body: rule.getBody()
7751
+ };
7752
+ } catch (error) {
7753
+ throw new Error(`Failed to read rule file ${relativePathFromCwd}: ${formatError(error)}`, {
7754
+ cause: error
7755
+ });
7756
+ }
7757
+ }
7758
+ async function putRule({
7759
+ relativePathFromCwd,
7760
+ frontmatter,
7761
+ body
7762
+ }) {
7763
+ validateRulePath(relativePathFromCwd);
7764
+ const filename = basename18(relativePathFromCwd);
7765
+ const estimatedSize = JSON.stringify(frontmatter).length + body.length;
7766
+ if (estimatedSize > MAX_RULE_SIZE_BYTES) {
7767
+ throw new Error(
7768
+ `Rule size ${estimatedSize} bytes exceeds maximum ${MAX_RULE_SIZE_BYTES} bytes (1MB)`
7769
+ );
7770
+ }
7771
+ try {
7772
+ const existingRules = await listRules();
7773
+ const isUpdate = existingRules.some(
7774
+ (rule2) => rule2.relativePathFromCwd === join66(".rulesync", "rules", filename)
7775
+ );
7776
+ if (!isUpdate && existingRules.length >= MAX_RULES_COUNT) {
7777
+ throw new Error(`Maximum number of rules (${MAX_RULES_COUNT}) reached`);
7778
+ }
7779
+ const rule = new RulesyncRule({
7780
+ baseDir: process.cwd(),
7781
+ relativeDirPath: ".rulesync/rules",
7782
+ relativeFilePath: filename,
7783
+ frontmatter,
7784
+ body,
7785
+ validate: true
7786
+ });
7787
+ const rulesDir = join66(process.cwd(), ".rulesync", "rules");
7788
+ await ensureDir(rulesDir);
7789
+ await writeFileContent(rule.getFilePath(), rule.getFileContent());
7790
+ return {
7791
+ relativePathFromCwd: join66(".rulesync", "rules", filename),
7792
+ frontmatter: rule.getFrontmatter(),
7793
+ body: rule.getBody()
7794
+ };
7795
+ } catch (error) {
7796
+ throw new Error(`Failed to write rule file ${relativePathFromCwd}: ${formatError(error)}`, {
7797
+ cause: error
7798
+ });
7799
+ }
7800
+ }
7801
+ async function deleteRule({ relativePathFromCwd }) {
7802
+ validateRulePath(relativePathFromCwd);
7803
+ const filename = basename18(relativePathFromCwd);
7804
+ const fullPath = join66(process.cwd(), ".rulesync", "rules", filename);
7805
+ try {
7806
+ await removeFile(fullPath);
7807
+ return {
7808
+ relativePathFromCwd: join66(".rulesync", "rules", filename)
7809
+ };
7810
+ } catch (error) {
7811
+ throw new Error(`Failed to delete rule file ${relativePathFromCwd}: ${formatError(error)}`, {
7812
+ cause: error
7813
+ });
7814
+ }
7815
+ }
7816
+ async function mcpCommand({ version }) {
7817
+ const server = new FastMCP({
7818
+ name: "rulesync-mcp-server",
7819
+ // Type assertion is safe here because version comes from package.json which follows semver
7820
+ // eslint-disable-next-line no-type-assertion/no-type-assertion
7821
+ version
7822
+ });
7823
+ server.addTool({
7824
+ name: "listRules",
7825
+ description: "List all rules from .rulesync/rules/*.md with their frontmatter",
7826
+ parameters: z23.object({}),
7827
+ execute: async () => {
7828
+ const rules = await listRules();
7829
+ const output = { rules };
7830
+ return JSON.stringify(output, null, 2);
7831
+ }
7832
+ });
7833
+ server.addTool({
7834
+ name: "getRule",
7835
+ description: "Get detailed information about a specific rule. relativePathFromCwd parameter is required.",
7836
+ parameters: z23.object({
7837
+ relativePathFromCwd: z23.string()
7838
+ }),
7839
+ execute: async (args) => {
7840
+ const result = await getRule({ relativePathFromCwd: args.relativePathFromCwd });
7841
+ return JSON.stringify(result, null, 2);
7842
+ }
7843
+ });
7844
+ server.addTool({
7845
+ name: "putRule",
7846
+ description: "Create or update a rule (upsert operation). relativePathFromCwd, frontmatter, and body parameters are required.",
7847
+ parameters: z23.object({
7848
+ relativePathFromCwd: z23.string(),
7849
+ frontmatter: RulesyncRuleFrontmatterSchema,
7850
+ body: z23.string()
7851
+ }),
7852
+ execute: async (args) => {
7853
+ const result = await putRule({
7854
+ relativePathFromCwd: args.relativePathFromCwd,
7855
+ frontmatter: args.frontmatter,
7856
+ body: args.body
7857
+ });
7858
+ return JSON.stringify(result, null, 2);
7859
+ }
7860
+ });
7861
+ server.addTool({
7862
+ name: "deleteRule",
7863
+ description: "Delete a rule file. relativePathFromCwd parameter is required.",
7864
+ parameters: z23.object({
7865
+ relativePathFromCwd: z23.string()
7866
+ }),
7867
+ execute: async (args) => {
7868
+ const result = await deleteRule({ relativePathFromCwd: args.relativePathFromCwd });
7869
+ return JSON.stringify(result, null, 2);
7870
+ }
7871
+ });
7872
+ logger.info("Rulesync MCP server started via stdio");
7873
+ void server.start({
7874
+ transportType: "stdio"
7875
+ });
7876
+ }
7877
+
7686
7878
  // src/cli/index.ts
7687
- var getVersion = () => "3.17.1";
7879
+ var getVersion = () => "3.19.0";
7688
7880
  var main = async () => {
7689
7881
  const program = new Command();
7690
7882
  const version = getVersion();
@@ -7722,7 +7914,15 @@ var main = async () => {
7722
7914
  experimentalGlobal: options.experimentalGlobal
7723
7915
  });
7724
7916
  } catch (error) {
7725
- logger.error(error instanceof Error ? error.message : String(error));
7917
+ logger.error(formatError(error));
7918
+ process.exit(1);
7919
+ }
7920
+ });
7921
+ program.command("mcp").description("Start MCP server for rulesync").action(async () => {
7922
+ try {
7923
+ await mcpCommand({ version });
7924
+ } catch (error) {
7925
+ logger.error(formatError(error));
7726
7926
  process.exit(1);
7727
7927
  }
7728
7928
  });
@@ -7777,13 +7977,13 @@ var main = async () => {
7777
7977
  experimentalSimulateSubagents: options.experimentalSimulateSubagents
7778
7978
  });
7779
7979
  } catch (error) {
7780
- logger.error(error instanceof Error ? error.message : String(error));
7980
+ logger.error(formatError(error));
7781
7981
  process.exit(1);
7782
7982
  }
7783
7983
  });
7784
7984
  program.parse();
7785
7985
  };
7786
7986
  main().catch((error) => {
7787
- logger.error(error instanceof Error ? error.message : String(error));
7987
+ logger.error(formatError(error));
7788
7988
  process.exit(1);
7789
7989
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rulesync",
3
- "version": "3.17.1",
3
+ "version": "3.19.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",
@@ -37,6 +37,7 @@
37
37
  },
38
38
  "dependencies": {
39
39
  "@kimuson/modular-mcp": "0.0.5",
40
+ "fastmcp": "^3.22.0",
40
41
  "chokidar": "4.0.3",
41
42
  "commander": "14.0.2",
42
43
  "consola": "3.4.2",