rulesync 7.15.1 → 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/README.md CHANGED
@@ -71,7 +71,7 @@ See [Quick Start guide](https://dyoshikawa.github.io/rulesync/getting-started/qu
71
71
  | Gemini CLI | geminicli | ✅ 🌏 | ✅ | ✅ 🌏 | ✅ 🌏 | 🎮 | ✅ 🌏 | ✅ 🌏 |
72
72
  | Goose | goose | ✅ 🌏 | ✅ | | | | | |
73
73
  | GitHub Copilot | copilot | ✅ 🌏 | | ✅ | ✅ | ✅ | ✅ | ✅ |
74
- | Cursor | cursor | ✅ | ✅ | | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | ✅ |
74
+ | Cursor | cursor | ✅ | ✅ | 🌏 | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | ✅ |
75
75
  | Factory Droid | factorydroid | ✅ 🌏 | | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 |
76
76
  | OpenCode | opencode | ✅ 🌏 | | ✅ 🌏 🔧 | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 | ✅ 🌏 |
77
77
  | Cline | cline | ✅ | ✅ | ✅ | ✅ 🌏 | | ✅ 🌏 | |
@@ -342,13 +342,22 @@ async function removeTempDirectory(tempDir) {
342
342
  // src/config/config.ts
343
343
  import { isAbsolute } from "path";
344
344
  import { minLength, optional, refine, z as z3 } from "zod/mini";
345
- function hasControlCharacters(value) {
345
+
346
+ // src/utils/validation.ts
347
+ function findControlCharacter(value) {
346
348
  for (let i = 0; i < value.length; i++) {
347
349
  const code = value.charCodeAt(i);
348
- if (code >= 0 && code <= 31 || code === 127) return true;
350
+ if (code >= 0 && code <= 31 || code === 127) {
351
+ return { position: i, hex: `0x${code.toString(16).padStart(2, "0")}` };
352
+ }
349
353
  }
350
- return false;
354
+ return null;
351
355
  }
356
+ function hasControlCharacters(value) {
357
+ return findControlCharacter(value) !== null;
358
+ }
359
+
360
+ // src/config/config.ts
352
361
  var SourceEntrySchema = z3.object({
353
362
  source: z3.string().check(minLength(1, "source must be a non-empty string")),
354
363
  skills: optional(z3.array(z3.string())),
@@ -728,6 +737,7 @@ import { join as join5 } from "path";
728
737
 
729
738
  // src/utils/frontmatter.ts
730
739
  import matter from "gray-matter";
740
+ import { dump, load } from "js-yaml";
731
741
  function isPlainObject(value) {
732
742
  if (value === null || typeof value !== "object") return false;
733
743
  const prototype = Object.getPrototypeOf(value);
@@ -766,8 +776,55 @@ function deepRemoveNullishObject(obj) {
766
776
  }
767
777
  return result;
768
778
  }
769
- function stringifyFrontmatter(body, frontmatter) {
770
- const cleanFrontmatter = deepRemoveNullishObject(frontmatter);
779
+ function deepFlattenStringsValue(value) {
780
+ if (value === null || value === void 0) {
781
+ return void 0;
782
+ }
783
+ if (typeof value === "string") {
784
+ return value.replace(/\n+/g, " ").trim();
785
+ }
786
+ if (Array.isArray(value)) {
787
+ const cleanedArray = value.map((item) => deepFlattenStringsValue(item)).filter((item) => item !== void 0);
788
+ return cleanedArray;
789
+ }
790
+ if (isPlainObject(value)) {
791
+ const result = {};
792
+ for (const [key, val] of Object.entries(value)) {
793
+ const cleaned = deepFlattenStringsValue(val);
794
+ if (cleaned !== void 0) {
795
+ result[key] = cleaned;
796
+ }
797
+ }
798
+ return result;
799
+ }
800
+ return value;
801
+ }
802
+ function deepFlattenStringsObject(obj) {
803
+ if (!obj || typeof obj !== "object") {
804
+ return {};
805
+ }
806
+ const result = {};
807
+ for (const [key, val] of Object.entries(obj)) {
808
+ const cleaned = deepFlattenStringsValue(val);
809
+ if (cleaned !== void 0) {
810
+ result[key] = cleaned;
811
+ }
812
+ }
813
+ return result;
814
+ }
815
+ function stringifyFrontmatter(body, frontmatter, options) {
816
+ const { avoidBlockScalars = false } = options ?? {};
817
+ const cleanFrontmatter = avoidBlockScalars ? deepFlattenStringsObject(frontmatter) : deepRemoveNullishObject(frontmatter);
818
+ if (avoidBlockScalars) {
819
+ return matter.stringify(body, cleanFrontmatter, {
820
+ engines: {
821
+ yaml: {
822
+ parse: (input) => load(input) ?? {},
823
+ stringify: (data) => dump(data, { lineWidth: -1 })
824
+ }
825
+ }
826
+ });
827
+ }
771
828
  return matter.stringify(body, cleanFrontmatter);
772
829
  }
773
830
  function parseFrontmatter(content, filePath) {
@@ -1886,7 +1943,7 @@ var CursorCommand = class _CursorCommand extends ToolCommand {
1886
1943
  }
1887
1944
  super({
1888
1945
  ...rest,
1889
- fileContent: stringifyFrontmatter(body, frontmatter)
1946
+ fileContent: stringifyFrontmatter(body, frontmatter, { avoidBlockScalars: true })
1890
1947
  });
1891
1948
  this.frontmatter = frontmatter;
1892
1949
  this.body = body;
@@ -5803,7 +5860,10 @@ var RulesyncMcp = class _RulesyncMcp extends RulesyncFile {
5803
5860
  stripMcpServerFields(fields) {
5804
5861
  if (fields.length === 0) return this;
5805
5862
  const filteredServers = Object.fromEntries(
5806
- Object.entries(this.json.mcpServers).map(([name, config]) => [name, omit(config, fields)])
5863
+ Object.entries(this.json.mcpServers).map(([name, config]) => [
5864
+ name,
5865
+ Object.fromEntries(Object.entries(config).filter(([key]) => !fields.includes(key)))
5866
+ ])
5807
5867
  );
5808
5868
  return new _RulesyncMcp({
5809
5869
  baseDir: this.baseDir,
@@ -6309,12 +6369,26 @@ var CursorMcp = class _CursorMcp extends ToolMcp {
6309
6369
  json;
6310
6370
  constructor(params) {
6311
6371
  super(params);
6312
- this.json = this.fileContent !== void 0 ? JSON.parse(this.fileContent) : {};
6372
+ if (this.fileContent !== void 0) {
6373
+ try {
6374
+ this.json = JSON.parse(this.fileContent);
6375
+ } catch (error) {
6376
+ throw new Error(
6377
+ `Failed to parse Cursor MCP config at ${join46(this.relativeDirPath, this.relativeFilePath)}: ${formatError(error)}`,
6378
+ { cause: error }
6379
+ );
6380
+ }
6381
+ } else {
6382
+ this.json = {};
6383
+ }
6313
6384
  }
6314
6385
  getJson() {
6315
6386
  return this.json;
6316
6387
  }
6317
- static getSettablePaths() {
6388
+ isDeletable() {
6389
+ return !this.global;
6390
+ }
6391
+ static getSettablePaths(_options) {
6318
6392
  return {
6319
6393
  relativeDirPath: ".cursor",
6320
6394
  relativeFilePath: "mcp.json"
@@ -6322,41 +6396,62 @@ var CursorMcp = class _CursorMcp extends ToolMcp {
6322
6396
  }
6323
6397
  static async fromFile({
6324
6398
  baseDir = process.cwd(),
6325
- validate = true
6399
+ validate = true,
6400
+ global = false
6326
6401
  }) {
6327
- const fileContent = await readFileContent(
6328
- join46(
6329
- baseDir,
6330
- this.getSettablePaths().relativeDirPath,
6331
- this.getSettablePaths().relativeFilePath
6332
- )
6333
- );
6402
+ const paths = this.getSettablePaths({ global });
6403
+ const filePath = join46(baseDir, paths.relativeDirPath, paths.relativeFilePath);
6404
+ const fileContent = await readFileContentOrNull(filePath) ?? '{"mcpServers":{}}';
6405
+ let json;
6406
+ try {
6407
+ json = JSON.parse(fileContent);
6408
+ } catch (error) {
6409
+ throw new Error(
6410
+ `Failed to parse Cursor MCP config at ${join46(paths.relativeDirPath, paths.relativeFilePath)}: ${formatError(error)}`,
6411
+ { cause: error }
6412
+ );
6413
+ }
6414
+ const newJson = { ...json, mcpServers: json.mcpServers ?? {} };
6334
6415
  return new _CursorMcp({
6335
6416
  baseDir,
6336
- relativeDirPath: this.getSettablePaths().relativeDirPath,
6337
- relativeFilePath: this.getSettablePaths().relativeFilePath,
6338
- fileContent,
6339
- validate
6417
+ relativeDirPath: paths.relativeDirPath,
6418
+ relativeFilePath: paths.relativeFilePath,
6419
+ fileContent: JSON.stringify(newJson, null, 2),
6420
+ validate,
6421
+ global
6340
6422
  });
6341
6423
  }
6342
- static fromRulesyncMcp({
6424
+ static async fromRulesyncMcp({
6343
6425
  baseDir = process.cwd(),
6344
6426
  rulesyncMcp,
6345
- validate = true
6427
+ validate = true,
6428
+ global = false
6346
6429
  }) {
6347
- const json = rulesyncMcp.getJson();
6348
- const mcpServers = isMcpServers(json.mcpServers) ? json.mcpServers : {};
6430
+ const paths = this.getSettablePaths({ global });
6431
+ const fileContent = await readOrInitializeFileContent(
6432
+ join46(baseDir, paths.relativeDirPath, paths.relativeFilePath),
6433
+ JSON.stringify({ mcpServers: {} }, null, 2)
6434
+ );
6435
+ let json;
6436
+ try {
6437
+ json = JSON.parse(fileContent);
6438
+ } catch (error) {
6439
+ throw new Error(
6440
+ `Failed to parse Cursor MCP config at ${join46(paths.relativeDirPath, paths.relativeFilePath)}: ${formatError(error)}`,
6441
+ { cause: error }
6442
+ );
6443
+ }
6444
+ const rulesyncJson = rulesyncMcp.getJson();
6445
+ const mcpServers = isMcpServers(rulesyncJson.mcpServers) ? rulesyncJson.mcpServers : {};
6349
6446
  const transformedServers = convertEnvToCursorFormat(mcpServers);
6350
- const cursorConfig = {
6351
- mcpServers: transformedServers
6352
- };
6353
- const fileContent = JSON.stringify(cursorConfig, null, 2);
6447
+ const cursorConfig = { ...json, mcpServers: transformedServers };
6354
6448
  return new _CursorMcp({
6355
6449
  baseDir,
6356
- relativeDirPath: this.getSettablePaths().relativeDirPath,
6357
- relativeFilePath: this.getSettablePaths().relativeFilePath,
6358
- fileContent,
6359
- validate
6450
+ relativeDirPath: paths.relativeDirPath,
6451
+ relativeFilePath: paths.relativeFilePath,
6452
+ fileContent: JSON.stringify(cursorConfig, null, 2),
6453
+ validate,
6454
+ global
6360
6455
  });
6361
6456
  }
6362
6457
  toRulesyncMcp() {
@@ -6380,14 +6475,16 @@ var CursorMcp = class _CursorMcp extends ToolMcp {
6380
6475
  static forDeletion({
6381
6476
  baseDir = process.cwd(),
6382
6477
  relativeDirPath,
6383
- relativeFilePath
6478
+ relativeFilePath,
6479
+ global = false
6384
6480
  }) {
6385
6481
  return new _CursorMcp({
6386
6482
  baseDir,
6387
6483
  relativeDirPath,
6388
6484
  relativeFilePath,
6389
6485
  fileContent: "{}",
6390
- validate: false
6486
+ validate: false,
6487
+ global
6391
6488
  });
6392
6489
  }
6393
6490
  };
@@ -7216,7 +7313,7 @@ var toolMcpFactories = /* @__PURE__ */ new Map([
7216
7313
  class: CursorMcp,
7217
7314
  meta: {
7218
7315
  supportsProject: true,
7219
- supportsGlobal: false,
7316
+ supportsGlobal: true,
7220
7317
  supportsEnabledTools: false,
7221
7318
  supportsDisabledTools: false
7222
7319
  }
@@ -7929,9 +8026,15 @@ import { join as join59 } from "path";
7929
8026
  var DirFeatureProcessor = class {
7930
8027
  baseDir;
7931
8028
  dryRun;
7932
- constructor({ baseDir = process.cwd(), dryRun = false }) {
8029
+ avoidBlockScalars;
8030
+ constructor({
8031
+ baseDir = process.cwd(),
8032
+ dryRun = false,
8033
+ avoidBlockScalars = false
8034
+ }) {
7933
8035
  this.baseDir = baseDir;
7934
8036
  this.dryRun = dryRun;
8037
+ this.avoidBlockScalars = avoidBlockScalars;
7935
8038
  }
7936
8039
  /**
7937
8040
  * Return tool targets that this feature supports.
@@ -7957,7 +8060,9 @@ var DirFeatureProcessor = class {
7957
8060
  let mainFileContent;
7958
8061
  if (mainFile) {
7959
8062
  const mainFilePath = join59(dirPath, mainFile.name);
7960
- const content = stringifyFrontmatter(mainFile.body, mainFile.frontmatter);
8063
+ const content = stringifyFrontmatter(mainFile.body, mainFile.frontmatter, {
8064
+ avoidBlockScalars: this.avoidBlockScalars
8065
+ });
7961
8066
  mainFileContent = addTrailingNewline(content);
7962
8067
  const existingContent = await readFileContentOrNull(mainFilePath);
7963
8068
  if (existingContent !== mainFileContent) {
@@ -10696,7 +10801,7 @@ var SkillsProcessor = class extends DirFeatureProcessor {
10696
10801
  getFactory = defaultGetFactory4,
10697
10802
  dryRun = false
10698
10803
  }) {
10699
- super({ baseDir, dryRun });
10804
+ super({ baseDir, dryRun, avoidBlockScalars: toolTarget === "cursor" });
10700
10805
  const result = SkillsProcessorToolTargetSchema.safeParse(toolTarget);
10701
10806
  if (!result.success) {
10702
10807
  throw new Error(
@@ -11777,7 +11882,7 @@ var CursorSubagent = class _CursorSubagent extends ToolSubagent {
11777
11882
  ...cursorSection
11778
11883
  };
11779
11884
  const body = rulesyncSubagent.getBody();
11780
- const fileContent = stringifyFrontmatter(body, cursorFrontmatter);
11885
+ const fileContent = stringifyFrontmatter(body, cursorFrontmatter, { avoidBlockScalars: true });
11781
11886
  const paths = this.getSettablePaths({ global });
11782
11887
  return new _CursorSubagent({
11783
11888
  baseDir,
@@ -16912,6 +17017,7 @@ export {
16912
17017
  ALL_FEATURES,
16913
17018
  ALL_FEATURES_WITH_WILDCARD,
16914
17019
  ALL_TOOL_TARGETS,
17020
+ findControlCharacter,
16915
17021
  ConfigResolver,
16916
17022
  stringifyFrontmatter,
16917
17023
  RulesyncCommandFrontmatterSchema,