rulesync 8.15.0 → 8.15.1

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.
@@ -473,7 +473,8 @@ function validateOutputRoot(outputRoot) {
473
473
  throw new Error("outputRoot cannot be an empty string");
474
474
  }
475
475
  if (isAbsolute(outputRoot)) {
476
- const segments = outputRoot.split(/[/\\]/);
476
+ const separatorRegex = process.platform === "win32" ? /[/\\]/ : /\//;
477
+ const segments = outputRoot.split(separatorRegex);
477
478
  if (segments.includes("..")) {
478
479
  throw new Error(`Path traversal detected: ${outputRoot}`);
479
480
  }
@@ -10092,6 +10093,7 @@ var ToolPermissions = class extends ToolFile {
10092
10093
  };
10093
10094
 
10094
10095
  // src/features/permissions/augmentcode-permissions.ts
10096
+ var moduleLogger = new ConsoleLogger();
10095
10097
  var AugmentPermissionTypeSchema = z30.enum(["allow", "deny", "ask-user"]);
10096
10098
  var AugmentToolPermissionSchema = z30.looseObject({
10097
10099
  toolName: z30.string(),
@@ -10183,6 +10185,33 @@ function shellRegexToGlob(regex) {
10183
10185
  }
10184
10186
  return glob;
10185
10187
  }
10188
+ function isShellRegexRoundtrippable(regex) {
10189
+ if (!regex.startsWith("^") || !regex.endsWith("$")) {
10190
+ return false;
10191
+ }
10192
+ const body = regex.slice(1, -1);
10193
+ let i = 0;
10194
+ while (i < body.length) {
10195
+ const ch = body[i];
10196
+ if (ch === "\\" && i + 1 < body.length) {
10197
+ i += 2;
10198
+ continue;
10199
+ }
10200
+ if (ch === "." && body[i + 1] === "*") {
10201
+ i += 2;
10202
+ continue;
10203
+ }
10204
+ if (ch === ".") {
10205
+ i += 1;
10206
+ continue;
10207
+ }
10208
+ if (/[$^|+?*(){}[\]]/.test(ch ?? "")) {
10209
+ return false;
10210
+ }
10211
+ i += 1;
10212
+ }
10213
+ return true;
10214
+ }
10186
10215
  var MANAGED_AUGMENT_TOOL_NAMES = new Set(Object.values(CANONICAL_TO_AUGMENT_TOOL_NAMES));
10187
10216
  var AugmentcodePermissions = class _AugmentcodePermissions extends ToolPermissions {
10188
10217
  constructor(params) {
@@ -10190,6 +10219,12 @@ var AugmentcodePermissions = class _AugmentcodePermissions extends ToolPermissio
10190
10219
  ...params,
10191
10220
  fileContent: params.fileContent ?? "{}"
10192
10221
  });
10222
+ if (params.validate) {
10223
+ const result = this.validate();
10224
+ if (!result.success) {
10225
+ throw result.error;
10226
+ }
10227
+ }
10193
10228
  }
10194
10229
  isDeletable() {
10195
10230
  return false;
@@ -10283,14 +10318,27 @@ var AugmentcodePermissions = class _AugmentcodePermissions extends ToolPermissio
10283
10318
  );
10284
10319
  }
10285
10320
  const config = convertAugmentToRulesyncPermissions({
10286
- entries: settings.toolPermissions ?? []
10321
+ entries: settings.toolPermissions ?? [],
10322
+ logger: moduleLogger
10287
10323
  });
10288
10324
  return this.toRulesyncPermissionsDefault({
10289
10325
  fileContent: JSON.stringify(config, null, 2)
10290
10326
  });
10291
10327
  }
10292
10328
  validate() {
10293
- return { success: true, error: null };
10329
+ try {
10330
+ const parsed = JSON.parse(this.fileContent || "{}");
10331
+ const result = AugmentSettingsSchema.safeParse(parsed);
10332
+ if (!result.success) {
10333
+ return { success: false, error: result.error };
10334
+ }
10335
+ return { success: true, error: null };
10336
+ } catch (error) {
10337
+ return {
10338
+ success: false,
10339
+ error: new Error(`Failed to parse AugmentCode permissions JSON: ${formatError(error)}`)
10340
+ };
10341
+ }
10294
10342
  }
10295
10343
  static forDeletion({
10296
10344
  outputRoot = process.cwd(),
@@ -10388,7 +10436,8 @@ function sortAugmentEntries(entries) {
10388
10436
  return decorated.map((d) => d.entry);
10389
10437
  }
10390
10438
  function convertAugmentToRulesyncPermissions({
10391
- entries
10439
+ entries,
10440
+ logger
10392
10441
  }) {
10393
10442
  const actionPriority = {
10394
10443
  deny: 2,
@@ -10399,7 +10448,27 @@ function convertAugmentToRulesyncPermissions({
10399
10448
  for (const entry of entries) {
10400
10449
  const canonical = toCanonicalToolName(entry.toolName);
10401
10450
  const action = augmentTypeToAction(entry.permission.type);
10402
- const pattern = entry.toolName === "launch-process" && entry.shellInputRegex ? shellRegexToGlob(entry.shellInputRegex) : "*";
10451
+ let pattern;
10452
+ if (entry.toolName === "launch-process" && entry.shellInputRegex) {
10453
+ const regex = entry.shellInputRegex;
10454
+ if (isShellRegexRoundtrippable(regex)) {
10455
+ pattern = shellRegexToGlob(regex);
10456
+ } else {
10457
+ if (action === "deny") {
10458
+ logger?.warn(
10459
+ `AugmentCode permissions: shellInputRegex '${regex}' on tool '${entry.toolName}' is not faithfully roundtrippable to a glob. Importing as the catch-all '*' pattern (fail-closed) so the deny rule cannot be silently narrowed on regenerate.`
10460
+ );
10461
+ pattern = "*";
10462
+ } else {
10463
+ pattern = shellRegexToGlob(regex);
10464
+ logger?.warn(
10465
+ `AugmentCode permissions: shellInputRegex '${regex}' on tool '${entry.toolName}' is not faithfully roundtrippable to a glob. Importing as glob '${pattern}'; the rule may match a different set of inputs after regenerate.`
10466
+ );
10467
+ }
10468
+ }
10469
+ } else {
10470
+ pattern = "*";
10471
+ }
10403
10472
  if (!permission[canonical]) {
10404
10473
  permission[canonical] = {};
10405
10474
  }
@@ -10654,6 +10723,12 @@ var ClinePermissions = class _ClinePermissions extends ToolPermissions {
10654
10723
  ...params,
10655
10724
  fileContent: params.fileContent ?? "{}"
10656
10725
  });
10726
+ if (params.validate) {
10727
+ const result = this.validate();
10728
+ if (!result.success) {
10729
+ throw result.error;
10730
+ }
10731
+ }
10657
10732
  }
10658
10733
  isDeletable() {
10659
10734
  return false;
@@ -10792,7 +10867,19 @@ var ClinePermissions = class _ClinePermissions extends ToolPermissions {
10792
10867
  });
10793
10868
  }
10794
10869
  validate() {
10795
- return { success: true, error: null };
10870
+ try {
10871
+ const parsed = JSON.parse(this.fileContent || "{}");
10872
+ const result = ClineCommandPermissionsSchema.safeParse(parsed);
10873
+ if (!result.success) {
10874
+ return { success: false, error: result.error };
10875
+ }
10876
+ return { success: true, error: null };
10877
+ } catch (error) {
10878
+ return {
10879
+ success: false,
10880
+ error: new Error(`Failed to parse Cline permissions JSON: ${formatError(error)}`)
10881
+ };
10882
+ }
10796
10883
  }
10797
10884
  static forDeletion({
10798
10885
  outputRoot = process.cwd(),
@@ -11385,7 +11472,7 @@ var RESERVED_OBJECT_KEYS = /* @__PURE__ */ new Set([
11385
11472
  "constructor",
11386
11473
  "prototype"
11387
11474
  ]);
11388
- var moduleLogger = new ConsoleLogger();
11475
+ var moduleLogger2 = new ConsoleLogger();
11389
11476
  var GeminicliPermissions = class _GeminicliPermissions extends ToolPermissions {
11390
11477
  static getSettablePaths(_options = {}) {
11391
11478
  return {
@@ -11414,7 +11501,7 @@ var GeminicliPermissions = class _GeminicliPermissions extends ToolPermissions {
11414
11501
  rulesyncPermissions,
11415
11502
  validate = true,
11416
11503
  global = false,
11417
- logger = moduleLogger
11504
+ logger = moduleLogger2
11418
11505
  }) {
11419
11506
  const paths = this.getSettablePaths({ global });
11420
11507
  const fileContent = buildGeminicliPolicyContent(rulesyncPermissions.getJson(), logger);
@@ -11439,31 +11526,31 @@ var GeminicliPermissions = class _GeminicliPermissions extends ToolPermissions {
11439
11526
  { cause: error }
11440
11527
  );
11441
11528
  }
11442
- const rules = extractRules(parsed, moduleLogger);
11529
+ const rules = extractRules(parsed, moduleLogger2);
11443
11530
  for (const [index, rule] of rules.entries()) {
11444
11531
  const mappedCategory = Object.hasOwn(GEMINICLI_TO_RULESYNC_TOOL_NAME, rule.toolName) ? GEMINICLI_TO_RULESYNC_TOOL_NAME[rule.toolName] : void 0;
11445
11532
  const category = mappedCategory ?? rule.toolName;
11446
11533
  if (RESERVED_OBJECT_KEYS.has(category)) {
11447
- moduleLogger.warn(
11534
+ moduleLogger2.warn(
11448
11535
  `Skipping rule #${index} in ${this.getRelativeFilePath()}: toolName "${rule.toolName}" maps to a reserved object key ("${category}") and would risk prototype pollution.`
11449
11536
  );
11450
11537
  continue;
11451
11538
  }
11452
11539
  const action = mapFromGeminicliDecision(rule.decision);
11453
11540
  if (!action) {
11454
- moduleLogger.warn(
11541
+ moduleLogger2.warn(
11455
11542
  `Skipping rule #${index} (toolName="${rule.toolName}", commandPrefix=${JSON.stringify(rule.commandPrefix)}, argsPattern=${JSON.stringify(rule.argsPattern)}) in ${this.getRelativeFilePath()}: unknown decision ${JSON.stringify(rule.decision)}`
11456
11543
  );
11457
11544
  continue;
11458
11545
  }
11459
11546
  if (rule.toolName === "run_shell_command" && rule.commandPrefix !== void 0 && rule.argsPattern !== void 0) {
11460
- moduleLogger.warn(
11547
+ moduleLogger2.warn(
11461
11548
  `Rule #${index} in ${this.getRelativeFilePath()} sets both commandPrefix and argsPattern; rulesync will honor argsPattern and ignore commandPrefix=${JSON.stringify(rule.commandPrefix)}.`
11462
11549
  );
11463
11550
  }
11464
11551
  const pattern = extractPattern(rule);
11465
11552
  if (RESERVED_OBJECT_KEYS.has(pattern)) {
11466
- moduleLogger.warn(
11553
+ moduleLogger2.warn(
11467
11554
  `Skipping rule #${index} in ${this.getRelativeFilePath()}: pattern "${pattern}" is a reserved object key.`
11468
11555
  );
11469
11556
  continue;
@@ -12232,7 +12319,7 @@ var QwenSettingsPermissionsSchema = z36.looseObject({
12232
12319
  var QwenSettingsSchema = z36.looseObject({
12233
12320
  permissions: z36.optional(QwenSettingsPermissionsSchema)
12234
12321
  });
12235
- var moduleLogger2 = new ConsoleLogger();
12322
+ var moduleLogger3 = new ConsoleLogger();
12236
12323
  var CANONICAL_TO_QWEN_TOOL_NAMES = {
12237
12324
  bash: "Bash",
12238
12325
  read: "Read",
@@ -12287,6 +12374,12 @@ var QwencodePermissions = class _QwencodePermissions extends ToolPermissions {
12287
12374
  ...params,
12288
12375
  fileContent: params.fileContent ?? "{}"
12289
12376
  });
12377
+ if (params.validate) {
12378
+ const result = this.validate();
12379
+ if (!result.success) {
12380
+ throw result.error;
12381
+ }
12382
+ }
12290
12383
  }
12291
12384
  isDeletable() {
12292
12385
  return false;
@@ -12408,7 +12501,19 @@ var QwencodePermissions = class _QwencodePermissions extends ToolPermissions {
12408
12501
  });
12409
12502
  }
12410
12503
  validate() {
12411
- return { success: true, error: null };
12504
+ try {
12505
+ const parsed = JSON.parse(this.fileContent || "{}");
12506
+ const result = QwenSettingsSchema.safeParse(parsed);
12507
+ if (!result.success) {
12508
+ return { success: false, error: result.error };
12509
+ }
12510
+ return { success: true, error: null };
12511
+ } catch (error) {
12512
+ return {
12513
+ success: false,
12514
+ error: new Error(`Failed to parse Qwen permissions JSON: ${formatError(error)}`)
12515
+ };
12516
+ }
12412
12517
  }
12413
12518
  static forDeletion({
12414
12519
  outputRoot = process.cwd(),
@@ -12449,7 +12554,7 @@ function convertRulesyncToQwenPermissions(config) {
12449
12554
  }
12450
12555
  function convertQwenToRulesyncPermissions(params) {
12451
12556
  const permission = {};
12452
- const logger = params.logger ?? moduleLogger2;
12557
+ const logger = params.logger ?? moduleLogger3;
12453
12558
  const processEntries = (entries, action) => {
12454
12559
  for (const entry of entries) {
12455
12560
  const parsed = parseQwenPermissionEntry(entry, { logger });
@@ -21030,6 +21135,8 @@ var CopilotRuleFrontmatterSchema = z77.object({
21030
21135
  applyTo: z77.optional(z77.string()),
21031
21136
  excludeAgent: z77.optional(z77.union([z77.literal("code-review"), z77.literal("coding-agent")]))
21032
21137
  });
21138
+ var normalizeRelativePath = (path4) => path4.replace(/\\/g, "/").replace(/\/+/g, "/");
21139
+ var sameRelativePath = (leftDir, leftFile, rightDir, rightFile) => normalizeRelativePath(join133(leftDir, leftFile)) === normalizeRelativePath(join133(rightDir, rightFile));
21033
21140
  var CopilotRule = class _CopilotRule extends ToolRule {
21034
21141
  frontmatter;
21035
21142
  body;
@@ -21147,7 +21254,12 @@ var CopilotRule = class _CopilotRule extends ToolRule {
21147
21254
  global = false
21148
21255
  }) {
21149
21256
  const paths = this.getSettablePaths({ global });
21150
- const isRoot = relativeDirPath ? join133(relativeDirPath, relativeFilePath) === join133(paths.root.relativeDirPath, paths.root.relativeFilePath) : relativeFilePath === paths.root.relativeFilePath;
21257
+ const isRoot = relativeDirPath ? sameRelativePath(
21258
+ relativeDirPath,
21259
+ relativeFilePath,
21260
+ paths.root.relativeDirPath,
21261
+ paths.root.relativeFilePath
21262
+ ) : relativeFilePath === paths.root.relativeFilePath;
21151
21263
  const resolvedRelativeDirPath = relativeDirPath ?? (isRoot ? paths.root.relativeDirPath : paths.nonRoot?.relativeDirPath ?? paths.root.relativeDirPath);
21152
21264
  if (isRoot) {
21153
21265
  const relativePath2 = join133(paths.root.relativeDirPath, paths.root.relativeFilePath);
@@ -21191,7 +21303,12 @@ var CopilotRule = class _CopilotRule extends ToolRule {
21191
21303
  global = false
21192
21304
  }) {
21193
21305
  const paths = this.getSettablePaths({ global });
21194
- const isRoot = join133(relativeDirPath, relativeFilePath) === join133(paths.root.relativeDirPath, paths.root.relativeFilePath);
21306
+ const isRoot = sameRelativePath(
21307
+ relativeDirPath,
21308
+ relativeFilePath,
21309
+ paths.root.relativeDirPath,
21310
+ paths.root.relativeFilePath
21311
+ );
21195
21312
  return new _CopilotRule({
21196
21313
  outputRoot,
21197
21314
  relativeDirPath,
@@ -282,7 +282,8 @@ function validateOutputRoot(outputRoot) {
282
282
  throw new Error("outputRoot cannot be an empty string");
283
283
  }
284
284
  if ((0, import_node_path2.isAbsolute)(outputRoot)) {
285
- const segments = outputRoot.split(/[/\\]/);
285
+ const separatorRegex = process.platform === "win32" ? /[/\\]/ : /\//;
286
+ const segments = outputRoot.split(separatorRegex);
286
287
  if (segments.includes("..")) {
287
288
  throw new Error(`Path traversal detected: ${outputRoot}`);
288
289
  }
@@ -10125,6 +10126,7 @@ var ToolPermissions = class extends ToolFile {
10125
10126
  };
10126
10127
 
10127
10128
  // src/features/permissions/augmentcode-permissions.ts
10129
+ var moduleLogger = new ConsoleLogger();
10128
10130
  var AugmentPermissionTypeSchema = import_mini30.z.enum(["allow", "deny", "ask-user"]);
10129
10131
  var AugmentToolPermissionSchema = import_mini30.z.looseObject({
10130
10132
  toolName: import_mini30.z.string(),
@@ -10216,6 +10218,33 @@ function shellRegexToGlob(regex) {
10216
10218
  }
10217
10219
  return glob;
10218
10220
  }
10221
+ function isShellRegexRoundtrippable(regex) {
10222
+ if (!regex.startsWith("^") || !regex.endsWith("$")) {
10223
+ return false;
10224
+ }
10225
+ const body = regex.slice(1, -1);
10226
+ let i = 0;
10227
+ while (i < body.length) {
10228
+ const ch = body[i];
10229
+ if (ch === "\\" && i + 1 < body.length) {
10230
+ i += 2;
10231
+ continue;
10232
+ }
10233
+ if (ch === "." && body[i + 1] === "*") {
10234
+ i += 2;
10235
+ continue;
10236
+ }
10237
+ if (ch === ".") {
10238
+ i += 1;
10239
+ continue;
10240
+ }
10241
+ if (/[$^|+?*(){}[\]]/.test(ch ?? "")) {
10242
+ return false;
10243
+ }
10244
+ i += 1;
10245
+ }
10246
+ return true;
10247
+ }
10219
10248
  var MANAGED_AUGMENT_TOOL_NAMES = new Set(Object.values(CANONICAL_TO_AUGMENT_TOOL_NAMES));
10220
10249
  var AugmentcodePermissions = class _AugmentcodePermissions extends ToolPermissions {
10221
10250
  constructor(params) {
@@ -10223,6 +10252,12 @@ var AugmentcodePermissions = class _AugmentcodePermissions extends ToolPermissio
10223
10252
  ...params,
10224
10253
  fileContent: params.fileContent ?? "{}"
10225
10254
  });
10255
+ if (params.validate) {
10256
+ const result = this.validate();
10257
+ if (!result.success) {
10258
+ throw result.error;
10259
+ }
10260
+ }
10226
10261
  }
10227
10262
  isDeletable() {
10228
10263
  return false;
@@ -10316,14 +10351,27 @@ var AugmentcodePermissions = class _AugmentcodePermissions extends ToolPermissio
10316
10351
  );
10317
10352
  }
10318
10353
  const config = convertAugmentToRulesyncPermissions({
10319
- entries: settings.toolPermissions ?? []
10354
+ entries: settings.toolPermissions ?? [],
10355
+ logger: moduleLogger
10320
10356
  });
10321
10357
  return this.toRulesyncPermissionsDefault({
10322
10358
  fileContent: JSON.stringify(config, null, 2)
10323
10359
  });
10324
10360
  }
10325
10361
  validate() {
10326
- return { success: true, error: null };
10362
+ try {
10363
+ const parsed = JSON.parse(this.fileContent || "{}");
10364
+ const result = AugmentSettingsSchema.safeParse(parsed);
10365
+ if (!result.success) {
10366
+ return { success: false, error: result.error };
10367
+ }
10368
+ return { success: true, error: null };
10369
+ } catch (error) {
10370
+ return {
10371
+ success: false,
10372
+ error: new Error(`Failed to parse AugmentCode permissions JSON: ${formatError(error)}`)
10373
+ };
10374
+ }
10327
10375
  }
10328
10376
  static forDeletion({
10329
10377
  outputRoot = process.cwd(),
@@ -10421,7 +10469,8 @@ function sortAugmentEntries(entries) {
10421
10469
  return decorated.map((d) => d.entry);
10422
10470
  }
10423
10471
  function convertAugmentToRulesyncPermissions({
10424
- entries
10472
+ entries,
10473
+ logger: logger5
10425
10474
  }) {
10426
10475
  const actionPriority = {
10427
10476
  deny: 2,
@@ -10432,7 +10481,27 @@ function convertAugmentToRulesyncPermissions({
10432
10481
  for (const entry of entries) {
10433
10482
  const canonical = toCanonicalToolName(entry.toolName);
10434
10483
  const action = augmentTypeToAction(entry.permission.type);
10435
- const pattern = entry.toolName === "launch-process" && entry.shellInputRegex ? shellRegexToGlob(entry.shellInputRegex) : "*";
10484
+ let pattern;
10485
+ if (entry.toolName === "launch-process" && entry.shellInputRegex) {
10486
+ const regex = entry.shellInputRegex;
10487
+ if (isShellRegexRoundtrippable(regex)) {
10488
+ pattern = shellRegexToGlob(regex);
10489
+ } else {
10490
+ if (action === "deny") {
10491
+ logger5?.warn(
10492
+ `AugmentCode permissions: shellInputRegex '${regex}' on tool '${entry.toolName}' is not faithfully roundtrippable to a glob. Importing as the catch-all '*' pattern (fail-closed) so the deny rule cannot be silently narrowed on regenerate.`
10493
+ );
10494
+ pattern = "*";
10495
+ } else {
10496
+ pattern = shellRegexToGlob(regex);
10497
+ logger5?.warn(
10498
+ `AugmentCode permissions: shellInputRegex '${regex}' on tool '${entry.toolName}' is not faithfully roundtrippable to a glob. Importing as glob '${pattern}'; the rule may match a different set of inputs after regenerate.`
10499
+ );
10500
+ }
10501
+ }
10502
+ } else {
10503
+ pattern = "*";
10504
+ }
10436
10505
  if (!permission[canonical]) {
10437
10506
  permission[canonical] = {};
10438
10507
  }
@@ -10687,6 +10756,12 @@ var ClinePermissions = class _ClinePermissions extends ToolPermissions {
10687
10756
  ...params,
10688
10757
  fileContent: params.fileContent ?? "{}"
10689
10758
  });
10759
+ if (params.validate) {
10760
+ const result = this.validate();
10761
+ if (!result.success) {
10762
+ throw result.error;
10763
+ }
10764
+ }
10690
10765
  }
10691
10766
  isDeletable() {
10692
10767
  return false;
@@ -10825,7 +10900,19 @@ var ClinePermissions = class _ClinePermissions extends ToolPermissions {
10825
10900
  });
10826
10901
  }
10827
10902
  validate() {
10828
- return { success: true, error: null };
10903
+ try {
10904
+ const parsed = JSON.parse(this.fileContent || "{}");
10905
+ const result = ClineCommandPermissionsSchema.safeParse(parsed);
10906
+ if (!result.success) {
10907
+ return { success: false, error: result.error };
10908
+ }
10909
+ return { success: true, error: null };
10910
+ } catch (error) {
10911
+ return {
10912
+ success: false,
10913
+ error: new Error(`Failed to parse Cline permissions JSON: ${formatError(error)}`)
10914
+ };
10915
+ }
10829
10916
  }
10830
10917
  static forDeletion({
10831
10918
  outputRoot = process.cwd(),
@@ -11418,7 +11505,7 @@ var RESERVED_OBJECT_KEYS = /* @__PURE__ */ new Set([
11418
11505
  "constructor",
11419
11506
  "prototype"
11420
11507
  ]);
11421
- var moduleLogger = new ConsoleLogger();
11508
+ var moduleLogger2 = new ConsoleLogger();
11422
11509
  var GeminicliPermissions = class _GeminicliPermissions extends ToolPermissions {
11423
11510
  static getSettablePaths(_options = {}) {
11424
11511
  return {
@@ -11447,7 +11534,7 @@ var GeminicliPermissions = class _GeminicliPermissions extends ToolPermissions {
11447
11534
  rulesyncPermissions,
11448
11535
  validate = true,
11449
11536
  global = false,
11450
- logger: logger5 = moduleLogger
11537
+ logger: logger5 = moduleLogger2
11451
11538
  }) {
11452
11539
  const paths = this.getSettablePaths({ global });
11453
11540
  const fileContent = buildGeminicliPolicyContent(rulesyncPermissions.getJson(), logger5);
@@ -11472,31 +11559,31 @@ var GeminicliPermissions = class _GeminicliPermissions extends ToolPermissions {
11472
11559
  { cause: error }
11473
11560
  );
11474
11561
  }
11475
- const rules = extractRules(parsed, moduleLogger);
11562
+ const rules = extractRules(parsed, moduleLogger2);
11476
11563
  for (const [index, rule] of rules.entries()) {
11477
11564
  const mappedCategory = Object.hasOwn(GEMINICLI_TO_RULESYNC_TOOL_NAME, rule.toolName) ? GEMINICLI_TO_RULESYNC_TOOL_NAME[rule.toolName] : void 0;
11478
11565
  const category = mappedCategory ?? rule.toolName;
11479
11566
  if (RESERVED_OBJECT_KEYS.has(category)) {
11480
- moduleLogger.warn(
11567
+ moduleLogger2.warn(
11481
11568
  `Skipping rule #${index} in ${this.getRelativeFilePath()}: toolName "${rule.toolName}" maps to a reserved object key ("${category}") and would risk prototype pollution.`
11482
11569
  );
11483
11570
  continue;
11484
11571
  }
11485
11572
  const action = mapFromGeminicliDecision(rule.decision);
11486
11573
  if (!action) {
11487
- moduleLogger.warn(
11574
+ moduleLogger2.warn(
11488
11575
  `Skipping rule #${index} (toolName="${rule.toolName}", commandPrefix=${JSON.stringify(rule.commandPrefix)}, argsPattern=${JSON.stringify(rule.argsPattern)}) in ${this.getRelativeFilePath()}: unknown decision ${JSON.stringify(rule.decision)}`
11489
11576
  );
11490
11577
  continue;
11491
11578
  }
11492
11579
  if (rule.toolName === "run_shell_command" && rule.commandPrefix !== void 0 && rule.argsPattern !== void 0) {
11493
- moduleLogger.warn(
11580
+ moduleLogger2.warn(
11494
11581
  `Rule #${index} in ${this.getRelativeFilePath()} sets both commandPrefix and argsPattern; rulesync will honor argsPattern and ignore commandPrefix=${JSON.stringify(rule.commandPrefix)}.`
11495
11582
  );
11496
11583
  }
11497
11584
  const pattern = extractPattern(rule);
11498
11585
  if (RESERVED_OBJECT_KEYS.has(pattern)) {
11499
- moduleLogger.warn(
11586
+ moduleLogger2.warn(
11500
11587
  `Skipping rule #${index} in ${this.getRelativeFilePath()}: pattern "${pattern}" is a reserved object key.`
11501
11588
  );
11502
11589
  continue;
@@ -12265,7 +12352,7 @@ var QwenSettingsPermissionsSchema = import_mini36.z.looseObject({
12265
12352
  var QwenSettingsSchema = import_mini36.z.looseObject({
12266
12353
  permissions: import_mini36.z.optional(QwenSettingsPermissionsSchema)
12267
12354
  });
12268
- var moduleLogger2 = new ConsoleLogger();
12355
+ var moduleLogger3 = new ConsoleLogger();
12269
12356
  var CANONICAL_TO_QWEN_TOOL_NAMES = {
12270
12357
  bash: "Bash",
12271
12358
  read: "Read",
@@ -12320,6 +12407,12 @@ var QwencodePermissions = class _QwencodePermissions extends ToolPermissions {
12320
12407
  ...params,
12321
12408
  fileContent: params.fileContent ?? "{}"
12322
12409
  });
12410
+ if (params.validate) {
12411
+ const result = this.validate();
12412
+ if (!result.success) {
12413
+ throw result.error;
12414
+ }
12415
+ }
12323
12416
  }
12324
12417
  isDeletable() {
12325
12418
  return false;
@@ -12441,7 +12534,19 @@ var QwencodePermissions = class _QwencodePermissions extends ToolPermissions {
12441
12534
  });
12442
12535
  }
12443
12536
  validate() {
12444
- return { success: true, error: null };
12537
+ try {
12538
+ const parsed = JSON.parse(this.fileContent || "{}");
12539
+ const result = QwenSettingsSchema.safeParse(parsed);
12540
+ if (!result.success) {
12541
+ return { success: false, error: result.error };
12542
+ }
12543
+ return { success: true, error: null };
12544
+ } catch (error) {
12545
+ return {
12546
+ success: false,
12547
+ error: new Error(`Failed to parse Qwen permissions JSON: ${formatError(error)}`)
12548
+ };
12549
+ }
12445
12550
  }
12446
12551
  static forDeletion({
12447
12552
  outputRoot = process.cwd(),
@@ -12482,7 +12587,7 @@ function convertRulesyncToQwenPermissions(config) {
12482
12587
  }
12483
12588
  function convertQwenToRulesyncPermissions(params) {
12484
12589
  const permission = {};
12485
- const logger5 = params.logger ?? moduleLogger2;
12590
+ const logger5 = params.logger ?? moduleLogger3;
12486
12591
  const processEntries = (entries, action) => {
12487
12592
  for (const entry of entries) {
12488
12593
  const parsed = parseQwenPermissionEntry(entry, { logger: logger5 });
@@ -21063,6 +21168,8 @@ var CopilotRuleFrontmatterSchema = import_mini77.z.object({
21063
21168
  applyTo: import_mini77.z.optional(import_mini77.z.string()),
21064
21169
  excludeAgent: import_mini77.z.optional(import_mini77.z.union([import_mini77.z.literal("code-review"), import_mini77.z.literal("coding-agent")]))
21065
21170
  });
21171
+ var normalizeRelativePath = (path5) => path5.replace(/\\/g, "/").replace(/\/+/g, "/");
21172
+ var sameRelativePath = (leftDir, leftFile, rightDir, rightFile) => normalizeRelativePath((0, import_node_path136.join)(leftDir, leftFile)) === normalizeRelativePath((0, import_node_path136.join)(rightDir, rightFile));
21066
21173
  var CopilotRule = class _CopilotRule extends ToolRule {
21067
21174
  frontmatter;
21068
21175
  body;
@@ -21180,7 +21287,12 @@ var CopilotRule = class _CopilotRule extends ToolRule {
21180
21287
  global = false
21181
21288
  }) {
21182
21289
  const paths = this.getSettablePaths({ global });
21183
- const isRoot = relativeDirPath ? (0, import_node_path136.join)(relativeDirPath, relativeFilePath) === (0, import_node_path136.join)(paths.root.relativeDirPath, paths.root.relativeFilePath) : relativeFilePath === paths.root.relativeFilePath;
21290
+ const isRoot = relativeDirPath ? sameRelativePath(
21291
+ relativeDirPath,
21292
+ relativeFilePath,
21293
+ paths.root.relativeDirPath,
21294
+ paths.root.relativeFilePath
21295
+ ) : relativeFilePath === paths.root.relativeFilePath;
21184
21296
  const resolvedRelativeDirPath = relativeDirPath ?? (isRoot ? paths.root.relativeDirPath : paths.nonRoot?.relativeDirPath ?? paths.root.relativeDirPath);
21185
21297
  if (isRoot) {
21186
21298
  const relativePath2 = (0, import_node_path136.join)(paths.root.relativeDirPath, paths.root.relativeFilePath);
@@ -21224,7 +21336,12 @@ var CopilotRule = class _CopilotRule extends ToolRule {
21224
21336
  global = false
21225
21337
  }) {
21226
21338
  const paths = this.getSettablePaths({ global });
21227
- const isRoot = (0, import_node_path136.join)(relativeDirPath, relativeFilePath) === (0, import_node_path136.join)(paths.root.relativeDirPath, paths.root.relativeFilePath);
21339
+ const isRoot = sameRelativePath(
21340
+ relativeDirPath,
21341
+ relativeFilePath,
21342
+ paths.root.relativeDirPath,
21343
+ paths.root.relativeFilePath
21344
+ );
21228
21345
  return new _CopilotRule({
21229
21346
  outputRoot,
21230
21347
  relativeDirPath,
@@ -26171,7 +26288,10 @@ var GITIGNORE_ENTRY_REGISTRY = [
26171
26288
  { target: "kilo", feature: "commands", entry: "**/.kilo/workflows/" },
26172
26289
  { target: "kilo", feature: "mcp", entry: "**/.kilo/mcp.json" },
26173
26290
  { target: "kilo", feature: "ignore", entry: "**/.kiloignore" },
26174
- { target: "kilo", feature: "permissions", entry: "**/kilo.jsonc" },
26291
+ // No `**/kilo.jsonc` entry: structurally identical to `opencode.jsonc` (no
26292
+ // entry). The Kilo translator preserves non-permissions Kilo settings on
26293
+ // round-trip, so the file is intended to be checked in by the user — adding
26294
+ // `**/kilo.jsonc` would be too aggressive.
26175
26295
  // Kiro
26176
26296
  { target: "kiro", feature: "rules", entry: "**/.kiro/steering/" },
26177
26297
  { target: "kiro", feature: "commands", entry: "**/.kiro/prompts/" },
@@ -31509,7 +31629,7 @@ function wrapCommand({
31509
31629
  }
31510
31630
 
31511
31631
  // src/cli/index.ts
31512
- var getVersion = () => "8.15.0";
31632
+ var getVersion = () => "8.15.1";
31513
31633
  function wrapCommand2(name, errorCode, handler) {
31514
31634
  return wrapCommand({ name, errorCode, handler, getVersion });
31515
31635
  }
package/dist/cli/index.js CHANGED
@@ -79,7 +79,7 @@ import {
79
79
  stringifyFrontmatter,
80
80
  toPosixPath,
81
81
  writeFileContent
82
- } from "../chunk-UMJWBQ2X.js";
82
+ } from "../chunk-3DM6NLND.js";
83
83
 
84
84
  // src/cli/index.ts
85
85
  import { Command } from "commander";
@@ -1408,7 +1408,10 @@ var GITIGNORE_ENTRY_REGISTRY = [
1408
1408
  { target: "kilo", feature: "commands", entry: "**/.kilo/workflows/" },
1409
1409
  { target: "kilo", feature: "mcp", entry: "**/.kilo/mcp.json" },
1410
1410
  { target: "kilo", feature: "ignore", entry: "**/.kiloignore" },
1411
- { target: "kilo", feature: "permissions", entry: "**/kilo.jsonc" },
1411
+ // No `**/kilo.jsonc` entry: structurally identical to `opencode.jsonc` (no
1412
+ // entry). The Kilo translator preserves non-permissions Kilo settings on
1413
+ // round-trip, so the file is intended to be checked in by the user — adding
1414
+ // `**/kilo.jsonc` would be too aggressive.
1412
1415
  // Kiro
1413
1416
  { target: "kiro", feature: "rules", entry: "**/.kiro/steering/" },
1414
1417
  { target: "kiro", feature: "commands", entry: "**/.kiro/prompts/" },
@@ -6482,7 +6485,7 @@ function wrapCommand({
6482
6485
  }
6483
6486
 
6484
6487
  // src/cli/index.ts
6485
- var getVersion = () => "8.15.0";
6488
+ var getVersion = () => "8.15.1";
6486
6489
  function wrapCommand2(name, errorCode, handler) {
6487
6490
  return wrapCommand({ name, errorCode, handler, getVersion });
6488
6491
  }
package/dist/index.cjs CHANGED
@@ -232,7 +232,8 @@ function validateOutputRoot(outputRoot) {
232
232
  throw new Error("outputRoot cannot be an empty string");
233
233
  }
234
234
  if ((0, import_node_path2.isAbsolute)(outputRoot)) {
235
- const segments = outputRoot.split(/[/\\]/);
235
+ const separatorRegex = process.platform === "win32" ? /[/\\]/ : /\//;
236
+ const segments = outputRoot.split(separatorRegex);
236
237
  if (segments.includes("..")) {
237
238
  throw new Error(`Path traversal detected: ${outputRoot}`);
238
239
  }
@@ -9998,6 +9999,7 @@ var ToolPermissions = class extends ToolFile {
9998
9999
  };
9999
10000
 
10000
10001
  // src/features/permissions/augmentcode-permissions.ts
10002
+ var moduleLogger = new ConsoleLogger();
10001
10003
  var AugmentPermissionTypeSchema = import_mini30.z.enum(["allow", "deny", "ask-user"]);
10002
10004
  var AugmentToolPermissionSchema = import_mini30.z.looseObject({
10003
10005
  toolName: import_mini30.z.string(),
@@ -10089,6 +10091,33 @@ function shellRegexToGlob(regex) {
10089
10091
  }
10090
10092
  return glob;
10091
10093
  }
10094
+ function isShellRegexRoundtrippable(regex) {
10095
+ if (!regex.startsWith("^") || !regex.endsWith("$")) {
10096
+ return false;
10097
+ }
10098
+ const body = regex.slice(1, -1);
10099
+ let i = 0;
10100
+ while (i < body.length) {
10101
+ const ch = body[i];
10102
+ if (ch === "\\" && i + 1 < body.length) {
10103
+ i += 2;
10104
+ continue;
10105
+ }
10106
+ if (ch === "." && body[i + 1] === "*") {
10107
+ i += 2;
10108
+ continue;
10109
+ }
10110
+ if (ch === ".") {
10111
+ i += 1;
10112
+ continue;
10113
+ }
10114
+ if (/[$^|+?*(){}[\]]/.test(ch ?? "")) {
10115
+ return false;
10116
+ }
10117
+ i += 1;
10118
+ }
10119
+ return true;
10120
+ }
10092
10121
  var MANAGED_AUGMENT_TOOL_NAMES = new Set(Object.values(CANONICAL_TO_AUGMENT_TOOL_NAMES));
10093
10122
  var AugmentcodePermissions = class _AugmentcodePermissions extends ToolPermissions {
10094
10123
  constructor(params) {
@@ -10096,6 +10125,12 @@ var AugmentcodePermissions = class _AugmentcodePermissions extends ToolPermissio
10096
10125
  ...params,
10097
10126
  fileContent: params.fileContent ?? "{}"
10098
10127
  });
10128
+ if (params.validate) {
10129
+ const result = this.validate();
10130
+ if (!result.success) {
10131
+ throw result.error;
10132
+ }
10133
+ }
10099
10134
  }
10100
10135
  isDeletable() {
10101
10136
  return false;
@@ -10189,14 +10224,27 @@ var AugmentcodePermissions = class _AugmentcodePermissions extends ToolPermissio
10189
10224
  );
10190
10225
  }
10191
10226
  const config = convertAugmentToRulesyncPermissions({
10192
- entries: settings.toolPermissions ?? []
10227
+ entries: settings.toolPermissions ?? [],
10228
+ logger: moduleLogger
10193
10229
  });
10194
10230
  return this.toRulesyncPermissionsDefault({
10195
10231
  fileContent: JSON.stringify(config, null, 2)
10196
10232
  });
10197
10233
  }
10198
10234
  validate() {
10199
- return { success: true, error: null };
10235
+ try {
10236
+ const parsed = JSON.parse(this.fileContent || "{}");
10237
+ const result = AugmentSettingsSchema.safeParse(parsed);
10238
+ if (!result.success) {
10239
+ return { success: false, error: result.error };
10240
+ }
10241
+ return { success: true, error: null };
10242
+ } catch (error) {
10243
+ return {
10244
+ success: false,
10245
+ error: new Error(`Failed to parse AugmentCode permissions JSON: ${formatError(error)}`)
10246
+ };
10247
+ }
10200
10248
  }
10201
10249
  static forDeletion({
10202
10250
  outputRoot = process.cwd(),
@@ -10294,7 +10342,8 @@ function sortAugmentEntries(entries) {
10294
10342
  return decorated.map((d) => d.entry);
10295
10343
  }
10296
10344
  function convertAugmentToRulesyncPermissions({
10297
- entries
10345
+ entries,
10346
+ logger
10298
10347
  }) {
10299
10348
  const actionPriority = {
10300
10349
  deny: 2,
@@ -10305,7 +10354,27 @@ function convertAugmentToRulesyncPermissions({
10305
10354
  for (const entry of entries) {
10306
10355
  const canonical = toCanonicalToolName(entry.toolName);
10307
10356
  const action = augmentTypeToAction(entry.permission.type);
10308
- const pattern = entry.toolName === "launch-process" && entry.shellInputRegex ? shellRegexToGlob(entry.shellInputRegex) : "*";
10357
+ let pattern;
10358
+ if (entry.toolName === "launch-process" && entry.shellInputRegex) {
10359
+ const regex = entry.shellInputRegex;
10360
+ if (isShellRegexRoundtrippable(regex)) {
10361
+ pattern = shellRegexToGlob(regex);
10362
+ } else {
10363
+ if (action === "deny") {
10364
+ logger?.warn(
10365
+ `AugmentCode permissions: shellInputRegex '${regex}' on tool '${entry.toolName}' is not faithfully roundtrippable to a glob. Importing as the catch-all '*' pattern (fail-closed) so the deny rule cannot be silently narrowed on regenerate.`
10366
+ );
10367
+ pattern = "*";
10368
+ } else {
10369
+ pattern = shellRegexToGlob(regex);
10370
+ logger?.warn(
10371
+ `AugmentCode permissions: shellInputRegex '${regex}' on tool '${entry.toolName}' is not faithfully roundtrippable to a glob. Importing as glob '${pattern}'; the rule may match a different set of inputs after regenerate.`
10372
+ );
10373
+ }
10374
+ }
10375
+ } else {
10376
+ pattern = "*";
10377
+ }
10309
10378
  if (!permission[canonical]) {
10310
10379
  permission[canonical] = {};
10311
10380
  }
@@ -10560,6 +10629,12 @@ var ClinePermissions = class _ClinePermissions extends ToolPermissions {
10560
10629
  ...params,
10561
10630
  fileContent: params.fileContent ?? "{}"
10562
10631
  });
10632
+ if (params.validate) {
10633
+ const result = this.validate();
10634
+ if (!result.success) {
10635
+ throw result.error;
10636
+ }
10637
+ }
10563
10638
  }
10564
10639
  isDeletable() {
10565
10640
  return false;
@@ -10698,7 +10773,19 @@ var ClinePermissions = class _ClinePermissions extends ToolPermissions {
10698
10773
  });
10699
10774
  }
10700
10775
  validate() {
10701
- return { success: true, error: null };
10776
+ try {
10777
+ const parsed = JSON.parse(this.fileContent || "{}");
10778
+ const result = ClineCommandPermissionsSchema.safeParse(parsed);
10779
+ if (!result.success) {
10780
+ return { success: false, error: result.error };
10781
+ }
10782
+ return { success: true, error: null };
10783
+ } catch (error) {
10784
+ return {
10785
+ success: false,
10786
+ error: new Error(`Failed to parse Cline permissions JSON: ${formatError(error)}`)
10787
+ };
10788
+ }
10702
10789
  }
10703
10790
  static forDeletion({
10704
10791
  outputRoot = process.cwd(),
@@ -11291,7 +11378,7 @@ var RESERVED_OBJECT_KEYS = /* @__PURE__ */ new Set([
11291
11378
  "constructor",
11292
11379
  "prototype"
11293
11380
  ]);
11294
- var moduleLogger = new ConsoleLogger();
11381
+ var moduleLogger2 = new ConsoleLogger();
11295
11382
  var GeminicliPermissions = class _GeminicliPermissions extends ToolPermissions {
11296
11383
  static getSettablePaths(_options = {}) {
11297
11384
  return {
@@ -11320,7 +11407,7 @@ var GeminicliPermissions = class _GeminicliPermissions extends ToolPermissions {
11320
11407
  rulesyncPermissions,
11321
11408
  validate = true,
11322
11409
  global = false,
11323
- logger = moduleLogger
11410
+ logger = moduleLogger2
11324
11411
  }) {
11325
11412
  const paths = this.getSettablePaths({ global });
11326
11413
  const fileContent = buildGeminicliPolicyContent(rulesyncPermissions.getJson(), logger);
@@ -11345,31 +11432,31 @@ var GeminicliPermissions = class _GeminicliPermissions extends ToolPermissions {
11345
11432
  { cause: error }
11346
11433
  );
11347
11434
  }
11348
- const rules = extractRules(parsed, moduleLogger);
11435
+ const rules = extractRules(parsed, moduleLogger2);
11349
11436
  for (const [index, rule] of rules.entries()) {
11350
11437
  const mappedCategory = Object.hasOwn(GEMINICLI_TO_RULESYNC_TOOL_NAME, rule.toolName) ? GEMINICLI_TO_RULESYNC_TOOL_NAME[rule.toolName] : void 0;
11351
11438
  const category = mappedCategory ?? rule.toolName;
11352
11439
  if (RESERVED_OBJECT_KEYS.has(category)) {
11353
- moduleLogger.warn(
11440
+ moduleLogger2.warn(
11354
11441
  `Skipping rule #${index} in ${this.getRelativeFilePath()}: toolName "${rule.toolName}" maps to a reserved object key ("${category}") and would risk prototype pollution.`
11355
11442
  );
11356
11443
  continue;
11357
11444
  }
11358
11445
  const action = mapFromGeminicliDecision(rule.decision);
11359
11446
  if (!action) {
11360
- moduleLogger.warn(
11447
+ moduleLogger2.warn(
11361
11448
  `Skipping rule #${index} (toolName="${rule.toolName}", commandPrefix=${JSON.stringify(rule.commandPrefix)}, argsPattern=${JSON.stringify(rule.argsPattern)}) in ${this.getRelativeFilePath()}: unknown decision ${JSON.stringify(rule.decision)}`
11362
11449
  );
11363
11450
  continue;
11364
11451
  }
11365
11452
  if (rule.toolName === "run_shell_command" && rule.commandPrefix !== void 0 && rule.argsPattern !== void 0) {
11366
- moduleLogger.warn(
11453
+ moduleLogger2.warn(
11367
11454
  `Rule #${index} in ${this.getRelativeFilePath()} sets both commandPrefix and argsPattern; rulesync will honor argsPattern and ignore commandPrefix=${JSON.stringify(rule.commandPrefix)}.`
11368
11455
  );
11369
11456
  }
11370
11457
  const pattern = extractPattern(rule);
11371
11458
  if (RESERVED_OBJECT_KEYS.has(pattern)) {
11372
- moduleLogger.warn(
11459
+ moduleLogger2.warn(
11373
11460
  `Skipping rule #${index} in ${this.getRelativeFilePath()}: pattern "${pattern}" is a reserved object key.`
11374
11461
  );
11375
11462
  continue;
@@ -12138,7 +12225,7 @@ var QwenSettingsPermissionsSchema = import_mini36.z.looseObject({
12138
12225
  var QwenSettingsSchema = import_mini36.z.looseObject({
12139
12226
  permissions: import_mini36.z.optional(QwenSettingsPermissionsSchema)
12140
12227
  });
12141
- var moduleLogger2 = new ConsoleLogger();
12228
+ var moduleLogger3 = new ConsoleLogger();
12142
12229
  var CANONICAL_TO_QWEN_TOOL_NAMES = {
12143
12230
  bash: "Bash",
12144
12231
  read: "Read",
@@ -12193,6 +12280,12 @@ var QwencodePermissions = class _QwencodePermissions extends ToolPermissions {
12193
12280
  ...params,
12194
12281
  fileContent: params.fileContent ?? "{}"
12195
12282
  });
12283
+ if (params.validate) {
12284
+ const result = this.validate();
12285
+ if (!result.success) {
12286
+ throw result.error;
12287
+ }
12288
+ }
12196
12289
  }
12197
12290
  isDeletable() {
12198
12291
  return false;
@@ -12314,7 +12407,19 @@ var QwencodePermissions = class _QwencodePermissions extends ToolPermissions {
12314
12407
  });
12315
12408
  }
12316
12409
  validate() {
12317
- return { success: true, error: null };
12410
+ try {
12411
+ const parsed = JSON.parse(this.fileContent || "{}");
12412
+ const result = QwenSettingsSchema.safeParse(parsed);
12413
+ if (!result.success) {
12414
+ return { success: false, error: result.error };
12415
+ }
12416
+ return { success: true, error: null };
12417
+ } catch (error) {
12418
+ return {
12419
+ success: false,
12420
+ error: new Error(`Failed to parse Qwen permissions JSON: ${formatError(error)}`)
12421
+ };
12422
+ }
12318
12423
  }
12319
12424
  static forDeletion({
12320
12425
  outputRoot = process.cwd(),
@@ -12355,7 +12460,7 @@ function convertRulesyncToQwenPermissions(config) {
12355
12460
  }
12356
12461
  function convertQwenToRulesyncPermissions(params) {
12357
12462
  const permission = {};
12358
- const logger = params.logger ?? moduleLogger2;
12463
+ const logger = params.logger ?? moduleLogger3;
12359
12464
  const processEntries = (entries, action) => {
12360
12465
  for (const entry of entries) {
12361
12466
  const parsed = parseQwenPermissionEntry(entry, { logger });
@@ -20936,6 +21041,8 @@ var CopilotRuleFrontmatterSchema = import_mini77.z.object({
20936
21041
  applyTo: import_mini77.z.optional(import_mini77.z.string()),
20937
21042
  excludeAgent: import_mini77.z.optional(import_mini77.z.union([import_mini77.z.literal("code-review"), import_mini77.z.literal("coding-agent")]))
20938
21043
  });
21044
+ var normalizeRelativePath = (path4) => path4.replace(/\\/g, "/").replace(/\/+/g, "/");
21045
+ var sameRelativePath = (leftDir, leftFile, rightDir, rightFile) => normalizeRelativePath((0, import_node_path136.join)(leftDir, leftFile)) === normalizeRelativePath((0, import_node_path136.join)(rightDir, rightFile));
20939
21046
  var CopilotRule = class _CopilotRule extends ToolRule {
20940
21047
  frontmatter;
20941
21048
  body;
@@ -21053,7 +21160,12 @@ var CopilotRule = class _CopilotRule extends ToolRule {
21053
21160
  global = false
21054
21161
  }) {
21055
21162
  const paths = this.getSettablePaths({ global });
21056
- const isRoot = relativeDirPath ? (0, import_node_path136.join)(relativeDirPath, relativeFilePath) === (0, import_node_path136.join)(paths.root.relativeDirPath, paths.root.relativeFilePath) : relativeFilePath === paths.root.relativeFilePath;
21163
+ const isRoot = relativeDirPath ? sameRelativePath(
21164
+ relativeDirPath,
21165
+ relativeFilePath,
21166
+ paths.root.relativeDirPath,
21167
+ paths.root.relativeFilePath
21168
+ ) : relativeFilePath === paths.root.relativeFilePath;
21057
21169
  const resolvedRelativeDirPath = relativeDirPath ?? (isRoot ? paths.root.relativeDirPath : paths.nonRoot?.relativeDirPath ?? paths.root.relativeDirPath);
21058
21170
  if (isRoot) {
21059
21171
  const relativePath2 = (0, import_node_path136.join)(paths.root.relativeDirPath, paths.root.relativeFilePath);
@@ -21097,7 +21209,12 @@ var CopilotRule = class _CopilotRule extends ToolRule {
21097
21209
  global = false
21098
21210
  }) {
21099
21211
  const paths = this.getSettablePaths({ global });
21100
- const isRoot = (0, import_node_path136.join)(relativeDirPath, relativeFilePath) === (0, import_node_path136.join)(paths.root.relativeDirPath, paths.root.relativeFilePath);
21212
+ const isRoot = sameRelativePath(
21213
+ relativeDirPath,
21214
+ relativeFilePath,
21215
+ paths.root.relativeDirPath,
21216
+ paths.root.relativeFilePath
21217
+ );
21101
21218
  return new _CopilotRule({
21102
21219
  outputRoot,
21103
21220
  relativeDirPath,
package/dist/index.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  convertFromTool,
8
8
  generate,
9
9
  importFromTool
10
- } from "./chunk-UMJWBQ2X.js";
10
+ } from "./chunk-3DM6NLND.js";
11
11
 
12
12
  // src/index.ts
13
13
  async function generate2(options = {}) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rulesync",
3
- "version": "8.15.0",
3
+ "version": "8.15.1",
4
4
  "description": "Unified AI rules management CLI tool that generates configuration files for various AI development tools",
5
5
  "keywords": [
6
6
  "ai",
@@ -61,7 +61,7 @@
61
61
  "gray-matter": "4.0.3",
62
62
  "js-yaml": "4.1.1",
63
63
  "jsonc-parser": "3.3.1",
64
- "smol-toml": "1.6.0",
64
+ "smol-toml": "1.6.1",
65
65
  "sury": "10.0.4",
66
66
  "zod": "4.3.6"
67
67
  },