rulesync 7.15.2 → 7.17.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/dist/index.cjs CHANGED
@@ -60,6 +60,8 @@ var RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH = (0, import_node_path.join)(
60
60
  RULESYNC_SKILLS_RELATIVE_DIR_PATH,
61
61
  ".curated"
62
62
  );
63
+ var RULESYNC_MCP_FILE_NAME = "mcp.json";
64
+ var RULESYNC_MCP_SCHEMA_URL = "https://github.com/dyoshikawa/rulesync/releases/latest/download/mcp-schema.json";
63
65
  var MAX_FILE_SIZE = 10 * 1024 * 1024;
64
66
 
65
67
  // src/utils/error.ts
@@ -351,14 +353,21 @@ var ToolTargetSchema = import_mini2.z.enum(ALL_TOOL_TARGETS);
351
353
  var ToolTargetsSchema = import_mini2.z.array(ToolTargetSchema);
352
354
  var RulesyncTargetsSchema = import_mini2.z.array(import_mini2.z.enum(ALL_TOOL_TARGETS_WITH_WILDCARD));
353
355
 
354
- // src/config/config.ts
355
- function hasControlCharacters(value) {
356
+ // src/utils/validation.ts
357
+ function findControlCharacter(value) {
356
358
  for (let i = 0; i < value.length; i++) {
357
359
  const code = value.charCodeAt(i);
358
- if (code >= 0 && code <= 31 || code === 127) return true;
360
+ if (code >= 0 && code <= 31 || code === 127) {
361
+ return { position: i, hex: `0x${code.toString(16).padStart(2, "0")}` };
362
+ }
359
363
  }
360
- return false;
364
+ return null;
361
365
  }
366
+ function hasControlCharacters(value) {
367
+ return findControlCharacter(value) !== null;
368
+ }
369
+
370
+ // src/config/config.ts
362
371
  var SourceEntrySchema = import_mini3.z.object({
363
372
  source: import_mini3.z.string().check((0, import_mini3.minLength)(1, "source must be a non-empty string")),
364
373
  skills: (0, import_mini3.optional)(import_mini3.z.array(import_mini3.z.string())),
@@ -738,6 +747,7 @@ var import_node_path7 = require("path");
738
747
 
739
748
  // src/utils/frontmatter.ts
740
749
  var import_gray_matter = __toESM(require("gray-matter"), 1);
750
+ var import_js_yaml = require("js-yaml");
741
751
  function isPlainObject(value) {
742
752
  if (value === null || typeof value !== "object") return false;
743
753
  const prototype = Object.getPrototypeOf(value);
@@ -776,8 +786,55 @@ function deepRemoveNullishObject(obj) {
776
786
  }
777
787
  return result;
778
788
  }
779
- function stringifyFrontmatter(body, frontmatter) {
780
- const cleanFrontmatter = deepRemoveNullishObject(frontmatter);
789
+ function deepFlattenStringsValue(value) {
790
+ if (value === null || value === void 0) {
791
+ return void 0;
792
+ }
793
+ if (typeof value === "string") {
794
+ return value.replace(/\n+/g, " ").trim();
795
+ }
796
+ if (Array.isArray(value)) {
797
+ const cleanedArray = value.map((item) => deepFlattenStringsValue(item)).filter((item) => item !== void 0);
798
+ return cleanedArray;
799
+ }
800
+ if (isPlainObject(value)) {
801
+ const result = {};
802
+ for (const [key, val] of Object.entries(value)) {
803
+ const cleaned = deepFlattenStringsValue(val);
804
+ if (cleaned !== void 0) {
805
+ result[key] = cleaned;
806
+ }
807
+ }
808
+ return result;
809
+ }
810
+ return value;
811
+ }
812
+ function deepFlattenStringsObject(obj) {
813
+ if (!obj || typeof obj !== "object") {
814
+ return {};
815
+ }
816
+ const result = {};
817
+ for (const [key, val] of Object.entries(obj)) {
818
+ const cleaned = deepFlattenStringsValue(val);
819
+ if (cleaned !== void 0) {
820
+ result[key] = cleaned;
821
+ }
822
+ }
823
+ return result;
824
+ }
825
+ function stringifyFrontmatter(body, frontmatter, options) {
826
+ const { avoidBlockScalars = false } = options ?? {};
827
+ const cleanFrontmatter = avoidBlockScalars ? deepFlattenStringsObject(frontmatter) : deepRemoveNullishObject(frontmatter);
828
+ if (avoidBlockScalars) {
829
+ return import_gray_matter.default.stringify(body, cleanFrontmatter, {
830
+ engines: {
831
+ yaml: {
832
+ parse: (input) => (0, import_js_yaml.load)(input) ?? {},
833
+ stringify: (data) => (0, import_js_yaml.dump)(data, { lineWidth: -1 })
834
+ }
835
+ }
836
+ });
837
+ }
781
838
  return import_gray_matter.default.stringify(body, cleanFrontmatter);
782
839
  }
783
840
  function parseFrontmatter(content, filePath) {
@@ -1896,7 +1953,7 @@ var CursorCommand = class _CursorCommand extends ToolCommand {
1896
1953
  }
1897
1954
  super({
1898
1955
  ...rest,
1899
- fileContent: stringifyFrontmatter(body, frontmatter)
1956
+ fileContent: stringifyFrontmatter(body, frontmatter, { avoidBlockScalars: true })
1900
1957
  });
1901
1958
  this.frontmatter = frontmatter;
1902
1959
  this.body = body;
@@ -5694,7 +5751,7 @@ var import_mini20 = require("zod/mini");
5694
5751
 
5695
5752
  // src/types/mcp.ts
5696
5753
  var import_mini19 = require("zod/mini");
5697
- var McpServerSchema = import_mini19.z.object({
5754
+ var McpServerSchema = import_mini19.z.looseObject({
5698
5755
  type: import_mini19.z.optional(import_mini19.z.enum(["stdio", "sse", "http"])),
5699
5756
  command: import_mini19.z.optional(import_mini19.z.union([import_mini19.z.string(), import_mini19.z.array(import_mini19.z.string())])),
5700
5757
  args: import_mini19.z.optional(import_mini19.z.array(import_mini19.z.string())),
@@ -5726,6 +5783,10 @@ var RulesyncMcpServerSchema = import_mini20.z.extend(McpServerSchema, {
5726
5783
  var RulesyncMcpConfigSchema = import_mini20.z.object({
5727
5784
  mcpServers: import_mini20.z.record(import_mini20.z.string(), RulesyncMcpServerSchema)
5728
5785
  });
5786
+ var RulesyncMcpFileSchema = import_mini20.z.looseObject({
5787
+ $schema: import_mini20.z.optional(import_mini20.z.string()),
5788
+ ...RulesyncMcpConfigSchema.shape
5789
+ });
5729
5790
  var RulesyncMcp = class _RulesyncMcp extends RulesyncFile {
5730
5791
  json;
5731
5792
  constructor(params) {
@@ -5751,7 +5812,7 @@ var RulesyncMcp = class _RulesyncMcp extends RulesyncFile {
5751
5812
  };
5752
5813
  }
5753
5814
  validate() {
5754
- const result = RulesyncMcpConfigSchema.safeParse(this.json);
5815
+ const result = RulesyncMcpFileSchema.safeParse(this.json);
5755
5816
  if (!result.success) {
5756
5817
  return { success: false, error: result.error };
5757
5818
  }
@@ -5854,11 +5915,17 @@ var ToolMcp = class extends ToolFile {
5854
5915
  toRulesyncMcpDefault({
5855
5916
  fileContent = void 0
5856
5917
  } = {}) {
5918
+ const content = fileContent ?? this.fileContent;
5919
+ const { $schema: _, ...json } = JSON.parse(content);
5920
+ const withSchema = {
5921
+ $schema: RULESYNC_MCP_SCHEMA_URL,
5922
+ ...json
5923
+ };
5857
5924
  return new RulesyncMcp({
5858
5925
  baseDir: this.baseDir,
5859
5926
  relativeDirPath: RULESYNC_RELATIVE_DIR_PATH,
5860
- relativeFilePath: ".mcp.json",
5861
- fileContent: fileContent ?? this.fileContent
5927
+ relativeFilePath: RULESYNC_MCP_FILE_NAME,
5928
+ fileContent: JSON.stringify(withSchema, null, 2)
5862
5929
  });
5863
5930
  }
5864
5931
  static async fromFile(_params) {
@@ -6161,10 +6228,7 @@ var CodexcliMcp = class _CodexcliMcp extends ToolMcp {
6161
6228
  toRulesyncMcp() {
6162
6229
  const mcpServers = this.toml.mcp_servers ?? {};
6163
6230
  const converted = convertFromCodexFormat(mcpServers);
6164
- return new RulesyncMcp({
6165
- baseDir: this.baseDir,
6166
- relativeDirPath: RULESYNC_RELATIVE_DIR_PATH,
6167
- relativeFilePath: ".mcp.json",
6231
+ return this.toRulesyncMcpDefault({
6168
6232
  fileContent: JSON.stringify({ mcpServers: converted }, null, 2)
6169
6233
  });
6170
6234
  }
@@ -6322,12 +6386,26 @@ var CursorMcp = class _CursorMcp extends ToolMcp {
6322
6386
  json;
6323
6387
  constructor(params) {
6324
6388
  super(params);
6325
- this.json = this.fileContent !== void 0 ? JSON.parse(this.fileContent) : {};
6389
+ if (this.fileContent !== void 0) {
6390
+ try {
6391
+ this.json = JSON.parse(this.fileContent);
6392
+ } catch (error) {
6393
+ throw new Error(
6394
+ `Failed to parse Cursor MCP config at ${(0, import_node_path48.join)(this.relativeDirPath, this.relativeFilePath)}: ${formatError(error)}`,
6395
+ { cause: error }
6396
+ );
6397
+ }
6398
+ } else {
6399
+ this.json = {};
6400
+ }
6326
6401
  }
6327
6402
  getJson() {
6328
6403
  return this.json;
6329
6404
  }
6330
- static getSettablePaths() {
6405
+ isDeletable() {
6406
+ return !this.global;
6407
+ }
6408
+ static getSettablePaths(_options) {
6331
6409
  return {
6332
6410
  relativeDirPath: ".cursor",
6333
6411
  relativeFilePath: "mcp.json"
@@ -6335,41 +6413,62 @@ var CursorMcp = class _CursorMcp extends ToolMcp {
6335
6413
  }
6336
6414
  static async fromFile({
6337
6415
  baseDir = process.cwd(),
6338
- validate = true
6416
+ validate = true,
6417
+ global = false
6339
6418
  }) {
6340
- const fileContent = await readFileContent(
6341
- (0, import_node_path48.join)(
6342
- baseDir,
6343
- this.getSettablePaths().relativeDirPath,
6344
- this.getSettablePaths().relativeFilePath
6345
- )
6346
- );
6419
+ const paths = this.getSettablePaths({ global });
6420
+ const filePath = (0, import_node_path48.join)(baseDir, paths.relativeDirPath, paths.relativeFilePath);
6421
+ const fileContent = await readFileContentOrNull(filePath) ?? '{"mcpServers":{}}';
6422
+ let json;
6423
+ try {
6424
+ json = JSON.parse(fileContent);
6425
+ } catch (error) {
6426
+ throw new Error(
6427
+ `Failed to parse Cursor MCP config at ${(0, import_node_path48.join)(paths.relativeDirPath, paths.relativeFilePath)}: ${formatError(error)}`,
6428
+ { cause: error }
6429
+ );
6430
+ }
6431
+ const newJson = { ...json, mcpServers: json.mcpServers ?? {} };
6347
6432
  return new _CursorMcp({
6348
6433
  baseDir,
6349
- relativeDirPath: this.getSettablePaths().relativeDirPath,
6350
- relativeFilePath: this.getSettablePaths().relativeFilePath,
6351
- fileContent,
6352
- validate
6434
+ relativeDirPath: paths.relativeDirPath,
6435
+ relativeFilePath: paths.relativeFilePath,
6436
+ fileContent: JSON.stringify(newJson, null, 2),
6437
+ validate,
6438
+ global
6353
6439
  });
6354
6440
  }
6355
- static fromRulesyncMcp({
6441
+ static async fromRulesyncMcp({
6356
6442
  baseDir = process.cwd(),
6357
6443
  rulesyncMcp,
6358
- validate = true
6444
+ validate = true,
6445
+ global = false
6359
6446
  }) {
6360
- const json = rulesyncMcp.getJson();
6361
- const mcpServers = isMcpServers(json.mcpServers) ? json.mcpServers : {};
6447
+ const paths = this.getSettablePaths({ global });
6448
+ const fileContent = await readOrInitializeFileContent(
6449
+ (0, import_node_path48.join)(baseDir, paths.relativeDirPath, paths.relativeFilePath),
6450
+ JSON.stringify({ mcpServers: {} }, null, 2)
6451
+ );
6452
+ let json;
6453
+ try {
6454
+ json = JSON.parse(fileContent);
6455
+ } catch (error) {
6456
+ throw new Error(
6457
+ `Failed to parse Cursor MCP config at ${(0, import_node_path48.join)(paths.relativeDirPath, paths.relativeFilePath)}: ${formatError(error)}`,
6458
+ { cause: error }
6459
+ );
6460
+ }
6461
+ const rulesyncJson = rulesyncMcp.getJson();
6462
+ const mcpServers = isMcpServers(rulesyncJson.mcpServers) ? rulesyncJson.mcpServers : {};
6362
6463
  const transformedServers = convertEnvToCursorFormat(mcpServers);
6363
- const cursorConfig = {
6364
- mcpServers: transformedServers
6365
- };
6366
- const fileContent = JSON.stringify(cursorConfig, null, 2);
6464
+ const cursorConfig = { ...json, mcpServers: transformedServers };
6367
6465
  return new _CursorMcp({
6368
6466
  baseDir,
6369
- relativeDirPath: this.getSettablePaths().relativeDirPath,
6370
- relativeFilePath: this.getSettablePaths().relativeFilePath,
6371
- fileContent,
6372
- validate
6467
+ relativeDirPath: paths.relativeDirPath,
6468
+ relativeFilePath: paths.relativeFilePath,
6469
+ fileContent: JSON.stringify(cursorConfig, null, 2),
6470
+ validate,
6471
+ global
6373
6472
  });
6374
6473
  }
6375
6474
  toRulesyncMcp() {
@@ -6379,12 +6478,8 @@ var CursorMcp = class _CursorMcp extends ToolMcp {
6379
6478
  ...this.json,
6380
6479
  mcpServers: transformedServers
6381
6480
  };
6382
- return new RulesyncMcp({
6383
- baseDir: this.baseDir,
6384
- relativeDirPath: this.relativeDirPath,
6385
- relativeFilePath: "rulesync.mcp.json",
6386
- fileContent: JSON.stringify(transformedJson),
6387
- validate: true
6481
+ return this.toRulesyncMcpDefault({
6482
+ fileContent: JSON.stringify(transformedJson, null, 2)
6388
6483
  });
6389
6484
  }
6390
6485
  validate() {
@@ -6393,14 +6488,16 @@ var CursorMcp = class _CursorMcp extends ToolMcp {
6393
6488
  static forDeletion({
6394
6489
  baseDir = process.cwd(),
6395
6490
  relativeDirPath,
6396
- relativeFilePath
6491
+ relativeFilePath,
6492
+ global = false
6397
6493
  }) {
6398
6494
  return new _CursorMcp({
6399
6495
  baseDir,
6400
6496
  relativeDirPath,
6401
6497
  relativeFilePath,
6402
6498
  fileContent: "{}",
6403
- validate: false
6499
+ validate: false,
6500
+ global
6404
6501
  });
6405
6502
  }
6406
6503
  };
@@ -6460,13 +6557,7 @@ var FactorydroidMcp = class _FactorydroidMcp extends ToolMcp {
6460
6557
  });
6461
6558
  }
6462
6559
  toRulesyncMcp() {
6463
- return new RulesyncMcp({
6464
- baseDir: this.baseDir,
6465
- relativeDirPath: this.relativeDirPath,
6466
- relativeFilePath: "rulesync.mcp.json",
6467
- fileContent: JSON.stringify(this.json),
6468
- validate: true
6469
- });
6560
+ return this.toRulesyncMcpDefault();
6470
6561
  }
6471
6562
  validate() {
6472
6563
  return { success: true, error: null };
@@ -7229,7 +7320,7 @@ var toolMcpFactories = /* @__PURE__ */ new Map([
7229
7320
  class: CursorMcp,
7230
7321
  meta: {
7231
7322
  supportsProject: true,
7232
- supportsGlobal: false,
7323
+ supportsGlobal: true,
7233
7324
  supportsEnabledTools: false,
7234
7325
  supportsDisabledTools: false
7235
7326
  }
@@ -7942,9 +8033,15 @@ var import_node_path61 = require("path");
7942
8033
  var DirFeatureProcessor = class {
7943
8034
  baseDir;
7944
8035
  dryRun;
7945
- constructor({ baseDir = process.cwd(), dryRun = false }) {
8036
+ avoidBlockScalars;
8037
+ constructor({
8038
+ baseDir = process.cwd(),
8039
+ dryRun = false,
8040
+ avoidBlockScalars = false
8041
+ }) {
7946
8042
  this.baseDir = baseDir;
7947
8043
  this.dryRun = dryRun;
8044
+ this.avoidBlockScalars = avoidBlockScalars;
7948
8045
  }
7949
8046
  /**
7950
8047
  * Return tool targets that this feature supports.
@@ -7970,7 +8067,9 @@ var DirFeatureProcessor = class {
7970
8067
  let mainFileContent;
7971
8068
  if (mainFile) {
7972
8069
  const mainFilePath = (0, import_node_path61.join)(dirPath, mainFile.name);
7973
- const content = stringifyFrontmatter(mainFile.body, mainFile.frontmatter);
8070
+ const content = stringifyFrontmatter(mainFile.body, mainFile.frontmatter, {
8071
+ avoidBlockScalars: this.avoidBlockScalars
8072
+ });
7974
8073
  mainFileContent = addTrailingNewline(content);
7975
8074
  const existingContent = await readFileContentOrNull(mainFilePath);
7976
8075
  if (existingContent !== mainFileContent) {
@@ -10709,7 +10808,7 @@ var SkillsProcessor = class extends DirFeatureProcessor {
10709
10808
  getFactory = defaultGetFactory4,
10710
10809
  dryRun = false
10711
10810
  }) {
10712
- super({ baseDir, dryRun });
10811
+ super({ baseDir, dryRun, avoidBlockScalars: toolTarget === "cursor" });
10713
10812
  const result = SkillsProcessorToolTargetSchema.safeParse(toolTarget);
10714
10813
  if (!result.success) {
10715
10814
  throw new Error(
@@ -11790,7 +11889,7 @@ var CursorSubagent = class _CursorSubagent extends ToolSubagent {
11790
11889
  ...cursorSection
11791
11890
  };
11792
11891
  const body = rulesyncSubagent.getBody();
11793
- const fileContent = stringifyFrontmatter(body, cursorFrontmatter);
11892
+ const fileContent = stringifyFrontmatter(body, cursorFrontmatter, { avoidBlockScalars: true });
11794
11893
  const paths = this.getSettablePaths({ global });
11795
11894
  return new _CursorSubagent({
11796
11895
  baseDir,
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  generate,
7
7
  importFromTool,
8
8
  logger
9
- } from "./chunk-WY325EI7.js";
9
+ } from "./chunk-ZQUEAGJI.js";
10
10
 
11
11
  // src/index.ts
12
12
  async function generate2(options = {}) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rulesync",
3
- "version": "7.15.2",
3
+ "version": "7.17.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",