rulesync 7.15.2 → 7.16.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
@@ -351,14 +351,21 @@ var ToolTargetSchema = import_mini2.z.enum(ALL_TOOL_TARGETS);
351
351
  var ToolTargetsSchema = import_mini2.z.array(ToolTargetSchema);
352
352
  var RulesyncTargetsSchema = import_mini2.z.array(import_mini2.z.enum(ALL_TOOL_TARGETS_WITH_WILDCARD));
353
353
 
354
- // src/config/config.ts
355
- function hasControlCharacters(value) {
354
+ // src/utils/validation.ts
355
+ function findControlCharacter(value) {
356
356
  for (let i = 0; i < value.length; i++) {
357
357
  const code = value.charCodeAt(i);
358
- if (code >= 0 && code <= 31 || code === 127) return true;
358
+ if (code >= 0 && code <= 31 || code === 127) {
359
+ return { position: i, hex: `0x${code.toString(16).padStart(2, "0")}` };
360
+ }
359
361
  }
360
- return false;
362
+ return null;
361
363
  }
364
+ function hasControlCharacters(value) {
365
+ return findControlCharacter(value) !== null;
366
+ }
367
+
368
+ // src/config/config.ts
362
369
  var SourceEntrySchema = import_mini3.z.object({
363
370
  source: import_mini3.z.string().check((0, import_mini3.minLength)(1, "source must be a non-empty string")),
364
371
  skills: (0, import_mini3.optional)(import_mini3.z.array(import_mini3.z.string())),
@@ -738,6 +745,7 @@ var import_node_path7 = require("path");
738
745
 
739
746
  // src/utils/frontmatter.ts
740
747
  var import_gray_matter = __toESM(require("gray-matter"), 1);
748
+ var import_js_yaml = require("js-yaml");
741
749
  function isPlainObject(value) {
742
750
  if (value === null || typeof value !== "object") return false;
743
751
  const prototype = Object.getPrototypeOf(value);
@@ -776,8 +784,55 @@ function deepRemoveNullishObject(obj) {
776
784
  }
777
785
  return result;
778
786
  }
779
- function stringifyFrontmatter(body, frontmatter) {
780
- const cleanFrontmatter = deepRemoveNullishObject(frontmatter);
787
+ function deepFlattenStringsValue(value) {
788
+ if (value === null || value === void 0) {
789
+ return void 0;
790
+ }
791
+ if (typeof value === "string") {
792
+ return value.replace(/\n+/g, " ").trim();
793
+ }
794
+ if (Array.isArray(value)) {
795
+ const cleanedArray = value.map((item) => deepFlattenStringsValue(item)).filter((item) => item !== void 0);
796
+ return cleanedArray;
797
+ }
798
+ if (isPlainObject(value)) {
799
+ const result = {};
800
+ for (const [key, val] of Object.entries(value)) {
801
+ const cleaned = deepFlattenStringsValue(val);
802
+ if (cleaned !== void 0) {
803
+ result[key] = cleaned;
804
+ }
805
+ }
806
+ return result;
807
+ }
808
+ return value;
809
+ }
810
+ function deepFlattenStringsObject(obj) {
811
+ if (!obj || typeof obj !== "object") {
812
+ return {};
813
+ }
814
+ const result = {};
815
+ for (const [key, val] of Object.entries(obj)) {
816
+ const cleaned = deepFlattenStringsValue(val);
817
+ if (cleaned !== void 0) {
818
+ result[key] = cleaned;
819
+ }
820
+ }
821
+ return result;
822
+ }
823
+ function stringifyFrontmatter(body, frontmatter, options) {
824
+ const { avoidBlockScalars = false } = options ?? {};
825
+ const cleanFrontmatter = avoidBlockScalars ? deepFlattenStringsObject(frontmatter) : deepRemoveNullishObject(frontmatter);
826
+ if (avoidBlockScalars) {
827
+ return import_gray_matter.default.stringify(body, cleanFrontmatter, {
828
+ engines: {
829
+ yaml: {
830
+ parse: (input) => (0, import_js_yaml.load)(input) ?? {},
831
+ stringify: (data) => (0, import_js_yaml.dump)(data, { lineWidth: -1 })
832
+ }
833
+ }
834
+ });
835
+ }
781
836
  return import_gray_matter.default.stringify(body, cleanFrontmatter);
782
837
  }
783
838
  function parseFrontmatter(content, filePath) {
@@ -1896,7 +1951,7 @@ var CursorCommand = class _CursorCommand extends ToolCommand {
1896
1951
  }
1897
1952
  super({
1898
1953
  ...rest,
1899
- fileContent: stringifyFrontmatter(body, frontmatter)
1954
+ fileContent: stringifyFrontmatter(body, frontmatter, { avoidBlockScalars: true })
1900
1955
  });
1901
1956
  this.frontmatter = frontmatter;
1902
1957
  this.body = body;
@@ -6322,12 +6377,26 @@ var CursorMcp = class _CursorMcp extends ToolMcp {
6322
6377
  json;
6323
6378
  constructor(params) {
6324
6379
  super(params);
6325
- this.json = this.fileContent !== void 0 ? JSON.parse(this.fileContent) : {};
6380
+ if (this.fileContent !== void 0) {
6381
+ try {
6382
+ this.json = JSON.parse(this.fileContent);
6383
+ } catch (error) {
6384
+ throw new Error(
6385
+ `Failed to parse Cursor MCP config at ${(0, import_node_path48.join)(this.relativeDirPath, this.relativeFilePath)}: ${formatError(error)}`,
6386
+ { cause: error }
6387
+ );
6388
+ }
6389
+ } else {
6390
+ this.json = {};
6391
+ }
6326
6392
  }
6327
6393
  getJson() {
6328
6394
  return this.json;
6329
6395
  }
6330
- static getSettablePaths() {
6396
+ isDeletable() {
6397
+ return !this.global;
6398
+ }
6399
+ static getSettablePaths(_options) {
6331
6400
  return {
6332
6401
  relativeDirPath: ".cursor",
6333
6402
  relativeFilePath: "mcp.json"
@@ -6335,41 +6404,62 @@ var CursorMcp = class _CursorMcp extends ToolMcp {
6335
6404
  }
6336
6405
  static async fromFile({
6337
6406
  baseDir = process.cwd(),
6338
- validate = true
6407
+ validate = true,
6408
+ global = false
6339
6409
  }) {
6340
- const fileContent = await readFileContent(
6341
- (0, import_node_path48.join)(
6342
- baseDir,
6343
- this.getSettablePaths().relativeDirPath,
6344
- this.getSettablePaths().relativeFilePath
6345
- )
6346
- );
6410
+ const paths = this.getSettablePaths({ global });
6411
+ const filePath = (0, import_node_path48.join)(baseDir, paths.relativeDirPath, paths.relativeFilePath);
6412
+ const fileContent = await readFileContentOrNull(filePath) ?? '{"mcpServers":{}}';
6413
+ let json;
6414
+ try {
6415
+ json = JSON.parse(fileContent);
6416
+ } catch (error) {
6417
+ throw new Error(
6418
+ `Failed to parse Cursor MCP config at ${(0, import_node_path48.join)(paths.relativeDirPath, paths.relativeFilePath)}: ${formatError(error)}`,
6419
+ { cause: error }
6420
+ );
6421
+ }
6422
+ const newJson = { ...json, mcpServers: json.mcpServers ?? {} };
6347
6423
  return new _CursorMcp({
6348
6424
  baseDir,
6349
- relativeDirPath: this.getSettablePaths().relativeDirPath,
6350
- relativeFilePath: this.getSettablePaths().relativeFilePath,
6351
- fileContent,
6352
- validate
6425
+ relativeDirPath: paths.relativeDirPath,
6426
+ relativeFilePath: paths.relativeFilePath,
6427
+ fileContent: JSON.stringify(newJson, null, 2),
6428
+ validate,
6429
+ global
6353
6430
  });
6354
6431
  }
6355
- static fromRulesyncMcp({
6432
+ static async fromRulesyncMcp({
6356
6433
  baseDir = process.cwd(),
6357
6434
  rulesyncMcp,
6358
- validate = true
6435
+ validate = true,
6436
+ global = false
6359
6437
  }) {
6360
- const json = rulesyncMcp.getJson();
6361
- const mcpServers = isMcpServers(json.mcpServers) ? json.mcpServers : {};
6438
+ const paths = this.getSettablePaths({ global });
6439
+ const fileContent = await readOrInitializeFileContent(
6440
+ (0, import_node_path48.join)(baseDir, paths.relativeDirPath, paths.relativeFilePath),
6441
+ JSON.stringify({ mcpServers: {} }, null, 2)
6442
+ );
6443
+ let json;
6444
+ try {
6445
+ json = JSON.parse(fileContent);
6446
+ } catch (error) {
6447
+ throw new Error(
6448
+ `Failed to parse Cursor MCP config at ${(0, import_node_path48.join)(paths.relativeDirPath, paths.relativeFilePath)}: ${formatError(error)}`,
6449
+ { cause: error }
6450
+ );
6451
+ }
6452
+ const rulesyncJson = rulesyncMcp.getJson();
6453
+ const mcpServers = isMcpServers(rulesyncJson.mcpServers) ? rulesyncJson.mcpServers : {};
6362
6454
  const transformedServers = convertEnvToCursorFormat(mcpServers);
6363
- const cursorConfig = {
6364
- mcpServers: transformedServers
6365
- };
6366
- const fileContent = JSON.stringify(cursorConfig, null, 2);
6455
+ const cursorConfig = { ...json, mcpServers: transformedServers };
6367
6456
  return new _CursorMcp({
6368
6457
  baseDir,
6369
- relativeDirPath: this.getSettablePaths().relativeDirPath,
6370
- relativeFilePath: this.getSettablePaths().relativeFilePath,
6371
- fileContent,
6372
- validate
6458
+ relativeDirPath: paths.relativeDirPath,
6459
+ relativeFilePath: paths.relativeFilePath,
6460
+ fileContent: JSON.stringify(cursorConfig, null, 2),
6461
+ validate,
6462
+ global
6373
6463
  });
6374
6464
  }
6375
6465
  toRulesyncMcp() {
@@ -6393,14 +6483,16 @@ var CursorMcp = class _CursorMcp extends ToolMcp {
6393
6483
  static forDeletion({
6394
6484
  baseDir = process.cwd(),
6395
6485
  relativeDirPath,
6396
- relativeFilePath
6486
+ relativeFilePath,
6487
+ global = false
6397
6488
  }) {
6398
6489
  return new _CursorMcp({
6399
6490
  baseDir,
6400
6491
  relativeDirPath,
6401
6492
  relativeFilePath,
6402
6493
  fileContent: "{}",
6403
- validate: false
6494
+ validate: false,
6495
+ global
6404
6496
  });
6405
6497
  }
6406
6498
  };
@@ -7229,7 +7321,7 @@ var toolMcpFactories = /* @__PURE__ */ new Map([
7229
7321
  class: CursorMcp,
7230
7322
  meta: {
7231
7323
  supportsProject: true,
7232
- supportsGlobal: false,
7324
+ supportsGlobal: true,
7233
7325
  supportsEnabledTools: false,
7234
7326
  supportsDisabledTools: false
7235
7327
  }
@@ -7942,9 +8034,15 @@ var import_node_path61 = require("path");
7942
8034
  var DirFeatureProcessor = class {
7943
8035
  baseDir;
7944
8036
  dryRun;
7945
- constructor({ baseDir = process.cwd(), dryRun = false }) {
8037
+ avoidBlockScalars;
8038
+ constructor({
8039
+ baseDir = process.cwd(),
8040
+ dryRun = false,
8041
+ avoidBlockScalars = false
8042
+ }) {
7946
8043
  this.baseDir = baseDir;
7947
8044
  this.dryRun = dryRun;
8045
+ this.avoidBlockScalars = avoidBlockScalars;
7948
8046
  }
7949
8047
  /**
7950
8048
  * Return tool targets that this feature supports.
@@ -7970,7 +8068,9 @@ var DirFeatureProcessor = class {
7970
8068
  let mainFileContent;
7971
8069
  if (mainFile) {
7972
8070
  const mainFilePath = (0, import_node_path61.join)(dirPath, mainFile.name);
7973
- const content = stringifyFrontmatter(mainFile.body, mainFile.frontmatter);
8071
+ const content = stringifyFrontmatter(mainFile.body, mainFile.frontmatter, {
8072
+ avoidBlockScalars: this.avoidBlockScalars
8073
+ });
7974
8074
  mainFileContent = addTrailingNewline(content);
7975
8075
  const existingContent = await readFileContentOrNull(mainFilePath);
7976
8076
  if (existingContent !== mainFileContent) {
@@ -10709,7 +10809,7 @@ var SkillsProcessor = class extends DirFeatureProcessor {
10709
10809
  getFactory = defaultGetFactory4,
10710
10810
  dryRun = false
10711
10811
  }) {
10712
- super({ baseDir, dryRun });
10812
+ super({ baseDir, dryRun, avoidBlockScalars: toolTarget === "cursor" });
10713
10813
  const result = SkillsProcessorToolTargetSchema.safeParse(toolTarget);
10714
10814
  if (!result.success) {
10715
10815
  throw new Error(
@@ -11790,7 +11890,7 @@ var CursorSubagent = class _CursorSubagent extends ToolSubagent {
11790
11890
  ...cursorSection
11791
11891
  };
11792
11892
  const body = rulesyncSubagent.getBody();
11793
- const fileContent = stringifyFrontmatter(body, cursorFrontmatter);
11893
+ const fileContent = stringifyFrontmatter(body, cursorFrontmatter, { avoidBlockScalars: true });
11794
11894
  const paths = this.getSettablePaths({ global });
11795
11895
  return new _CursorSubagent({
11796
11896
  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-E5YWRHGW.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.16.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",