rulesync 8.5.0 → 8.6.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/cli/index.js CHANGED
@@ -74,7 +74,7 @@ import {
74
74
  stringifyFrontmatter,
75
75
  toPosixPath,
76
76
  writeFileContent
77
- } from "../chunk-Q5FDY7NL.js";
77
+ } from "../chunk-ILMHM7BF.js";
78
78
 
79
79
  // src/cli/index.ts
80
80
  import { Command } from "commander";
@@ -4625,7 +4625,7 @@ function wrapCommand({
4625
4625
  }
4626
4626
 
4627
4627
  // src/cli/index.ts
4628
- var getVersion = () => "8.5.0";
4628
+ var getVersion = () => "8.6.0";
4629
4629
  function wrapCommand2(name, errorCode, handler) {
4630
4630
  return wrapCommand({ name, errorCode, handler, getVersion });
4631
4631
  }
package/dist/index.cjs CHANGED
@@ -9771,15 +9771,78 @@ function mapBashActionToDecision(action) {
9771
9771
 
9772
9772
  // src/features/permissions/geminicli-permissions.ts
9773
9773
  var import_node_path67 = require("path");
9774
+ var smolToml5 = __toESM(require("smol-toml"), 1);
9774
9775
  var import_mini29 = require("zod/mini");
9775
- var GeminiCliSettingsSchema = import_mini29.z.looseObject({
9776
- tools: import_mini29.z.optional(
9777
- import_mini29.z.looseObject({
9778
- allowed: import_mini29.z.optional(import_mini29.z.array(import_mini29.z.string())),
9779
- exclude: import_mini29.z.optional(import_mini29.z.array(import_mini29.z.string()))
9780
- })
9781
- )
9782
- });
9776
+
9777
+ // src/utils/logger.ts
9778
+ var BaseLogger = class {
9779
+ _verbose = false;
9780
+ _silent = false;
9781
+ constructor({ verbose = false, silent = false } = {}) {
9782
+ this._silent = silent;
9783
+ this._verbose = verbose && !silent;
9784
+ }
9785
+ get verbose() {
9786
+ return this._verbose;
9787
+ }
9788
+ get silent() {
9789
+ return this._silent;
9790
+ }
9791
+ configure({ verbose, silent }) {
9792
+ if (verbose && silent) {
9793
+ this._silent = false;
9794
+ if (!isEnvTest()) {
9795
+ this.onConflictingFlags();
9796
+ }
9797
+ }
9798
+ this._silent = silent;
9799
+ this._verbose = verbose && !silent;
9800
+ }
9801
+ onConflictingFlags() {
9802
+ console.warn("Both --verbose and --silent specified; --silent takes precedence");
9803
+ }
9804
+ };
9805
+ var ConsoleLogger = class extends BaseLogger {
9806
+ isSuppressed() {
9807
+ return isEnvTest() || this._silent;
9808
+ }
9809
+ get jsonMode() {
9810
+ return false;
9811
+ }
9812
+ captureData(_key, _value) {
9813
+ }
9814
+ getJsonData() {
9815
+ return {};
9816
+ }
9817
+ outputJson(_success, _error) {
9818
+ }
9819
+ info(message, ...args) {
9820
+ if (this.isSuppressed()) return;
9821
+ console.log(message, ...args);
9822
+ }
9823
+ success(message, ...args) {
9824
+ if (this.isSuppressed()) return;
9825
+ console.log(message, ...args);
9826
+ }
9827
+ warn(message, ...args) {
9828
+ if (this.isSuppressed()) return;
9829
+ console.warn(message, ...args);
9830
+ }
9831
+ // Errors are always emitted, even in silent mode
9832
+ error(message, _code, ...args) {
9833
+ if (isEnvTest()) return;
9834
+ const errorMessage = message instanceof Error ? message.message : message;
9835
+ console.error(errorMessage, ...args);
9836
+ }
9837
+ debug(message, ...args) {
9838
+ if (!this._verbose || this.isSuppressed()) return;
9839
+ console.log(message, ...args);
9840
+ }
9841
+ };
9842
+
9843
+ // src/features/permissions/geminicli-permissions.ts
9844
+ var GEMINICLI_POLICY_RELATIVE_DIR_PATH = (0, import_node_path67.join)(".gemini", "policies");
9845
+ var GEMINICLI_POLICY_FILE_NAME = "rulesync.toml";
9783
9846
  var RULESYNC_TO_GEMINICLI_TOOL_NAME = {
9784
9847
  bash: "run_shell_command",
9785
9848
  read: "read_file",
@@ -9787,16 +9850,32 @@ var RULESYNC_TO_GEMINICLI_TOOL_NAME = {
9787
9850
  write: "write_file",
9788
9851
  webfetch: "web_fetch"
9789
9852
  };
9853
+ var GEMINICLI_TO_RULESYNC_TOOL_NAME = Object.fromEntries(
9854
+ Object.entries(RULESYNC_TO_GEMINICLI_TOOL_NAME).map(([k, v]) => [v, k])
9855
+ );
9856
+ var PRIORITY_DENY = 1e6;
9857
+ var PRIORITY_ASK = 1e3;
9858
+ var PRIORITY_ALLOW = 1;
9859
+ var SINGLE_STAR_REGEX = '[^/\\"]*';
9860
+ var DOUBLE_STAR_REGEX = '[^\\"]*';
9861
+ var SINGLE_CHAR_REGEX = '[^/\\"]';
9862
+ var LEGACY_SINGLE_STAR_REGEX = '[^\\"]*';
9863
+ var LEGACY_DOUBLE_STAR_REGEX = ".*";
9864
+ var COMMAND_ARGS_ANCHOR = '"command":"';
9865
+ var VALUE_END_ANCHOR = '\\"';
9866
+ var RESERVED_OBJECT_KEYS = /* @__PURE__ */ new Set([
9867
+ "__proto__",
9868
+ "constructor",
9869
+ "prototype"
9870
+ ]);
9871
+ var moduleLogger = new ConsoleLogger();
9790
9872
  var GeminicliPermissions = class _GeminicliPermissions extends ToolPermissions {
9791
9873
  static getSettablePaths(_options = {}) {
9792
9874
  return {
9793
- relativeDirPath: ".gemini",
9794
- relativeFilePath: "settings.json"
9875
+ relativeDirPath: GEMINICLI_POLICY_RELATIVE_DIR_PATH,
9876
+ relativeFilePath: GEMINICLI_POLICY_FILE_NAME
9795
9877
  };
9796
9878
  }
9797
- isDeletable() {
9798
- return false;
9799
- }
9800
9879
  static async fromFile({
9801
9880
  baseDir = process.cwd(),
9802
9881
  validate = true,
@@ -9804,7 +9883,7 @@ var GeminicliPermissions = class _GeminicliPermissions extends ToolPermissions {
9804
9883
  }) {
9805
9884
  const paths = this.getSettablePaths({ global });
9806
9885
  const filePath = (0, import_node_path67.join)(baseDir, paths.relativeDirPath, paths.relativeFilePath);
9807
- const fileContent = await readFileContentOrNull(filePath) ?? JSON.stringify({}, null, 2);
9886
+ const fileContent = await readFileContentOrNull(filePath) ?? "";
9808
9887
  return new _GeminicliPermissions({
9809
9888
  baseDir,
9810
9889
  relativeDirPath: paths.relativeDirPath,
@@ -9813,63 +9892,72 @@ var GeminicliPermissions = class _GeminicliPermissions extends ToolPermissions {
9813
9892
  validate
9814
9893
  });
9815
9894
  }
9816
- static async fromRulesyncPermissions({
9895
+ static fromRulesyncPermissions({
9817
9896
  baseDir = process.cwd(),
9818
9897
  rulesyncPermissions,
9819
9898
  validate = true,
9820
- logger,
9821
- global = false
9899
+ global = false,
9900
+ logger = moduleLogger
9822
9901
  }) {
9823
9902
  const paths = this.getSettablePaths({ global });
9824
- const filePath = (0, import_node_path67.join)(baseDir, paths.relativeDirPath, paths.relativeFilePath);
9825
- const existingContent = await readFileContentOrNull(filePath) ?? JSON.stringify({}, null, 2);
9826
- const settingsResult = GeminiCliSettingsSchema.safeParse(JSON.parse(existingContent));
9827
- if (!settingsResult.success) {
9828
- throw new Error(
9829
- `Failed to parse existing Gemini CLI settings at ${filePath}: ${formatError(settingsResult.error)}`
9830
- );
9831
- }
9832
- const { allowed, exclude } = convertRulesyncToGeminicliTools({
9833
- config: rulesyncPermissions.getJson(),
9834
- logger
9835
- });
9836
- const merged = {
9837
- ...settingsResult.data,
9838
- tools: {
9839
- ...settingsResult.data.tools,
9840
- ...allowed.length > 0 ? { allowed } : {},
9841
- ...exclude.length > 0 ? { exclude } : {}
9842
- }
9843
- };
9903
+ const fileContent = buildGeminicliPolicyContent(rulesyncPermissions.getJson(), logger);
9844
9904
  return new _GeminicliPermissions({
9845
9905
  baseDir,
9846
9906
  relativeDirPath: paths.relativeDirPath,
9847
9907
  relativeFilePath: paths.relativeFilePath,
9848
- fileContent: JSON.stringify(merged, null, 2),
9908
+ fileContent,
9849
9909
  validate
9850
9910
  });
9851
9911
  }
9852
9912
  toRulesyncPermissions() {
9853
- let settings;
9854
- try {
9855
- const parsed = JSON.parse(this.getFileContent());
9856
- settings = GeminiCliSettingsSchema.parse(parsed);
9857
- } catch (error) {
9858
- throw new Error(
9859
- `Failed to parse Gemini CLI permissions content in ${(0, import_node_path67.join)(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`,
9860
- { cause: error }
9861
- );
9862
- }
9863
9913
  const permission = {};
9864
- for (const toolEntry of settings.tools?.allowed ?? []) {
9865
- const mapped = parseGeminicliToolEntry({ entry: toolEntry });
9866
- const rules = permission[mapped.category] ??= {};
9867
- rules[mapped.pattern] = "allow";
9868
- }
9869
- for (const toolEntry of settings.tools?.exclude ?? []) {
9870
- const mapped = parseGeminicliToolEntry({ entry: toolEntry });
9871
- const rules = permission[mapped.category] ??= {};
9872
- rules[mapped.pattern] = "deny";
9914
+ const fileContent = this.getFileContent();
9915
+ if (fileContent.trim().length > 0) {
9916
+ let parsed;
9917
+ try {
9918
+ parsed = smolToml5.parse(fileContent);
9919
+ } catch (error) {
9920
+ throw new Error(
9921
+ `Failed to parse Gemini CLI policy TOML in ${(0, import_node_path67.join)(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`,
9922
+ { cause: error }
9923
+ );
9924
+ }
9925
+ const rules = extractRules(parsed, moduleLogger);
9926
+ for (const [index, rule] of rules.entries()) {
9927
+ const mappedCategory = Object.hasOwn(GEMINICLI_TO_RULESYNC_TOOL_NAME, rule.toolName) ? GEMINICLI_TO_RULESYNC_TOOL_NAME[rule.toolName] : void 0;
9928
+ const category = mappedCategory ?? rule.toolName;
9929
+ if (RESERVED_OBJECT_KEYS.has(category)) {
9930
+ moduleLogger.warn(
9931
+ `Skipping rule #${index} in ${this.getRelativeFilePath()}: toolName "${rule.toolName}" maps to a reserved object key ("${category}") and would risk prototype pollution.`
9932
+ );
9933
+ continue;
9934
+ }
9935
+ const action = mapFromGeminicliDecision(rule.decision);
9936
+ if (!action) {
9937
+ moduleLogger.warn(
9938
+ `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)}`
9939
+ );
9940
+ continue;
9941
+ }
9942
+ if (rule.toolName === "run_shell_command" && rule.commandPrefix !== void 0 && rule.argsPattern !== void 0) {
9943
+ moduleLogger.warn(
9944
+ `Rule #${index} in ${this.getRelativeFilePath()} sets both commandPrefix and argsPattern; rulesync will honor argsPattern and ignore commandPrefix=${JSON.stringify(rule.commandPrefix)}.`
9945
+ );
9946
+ }
9947
+ const pattern = extractPattern(rule);
9948
+ if (RESERVED_OBJECT_KEYS.has(pattern)) {
9949
+ moduleLogger.warn(
9950
+ `Skipping rule #${index} in ${this.getRelativeFilePath()}: pattern "${pattern}" is a reserved object key.`
9951
+ );
9952
+ continue;
9953
+ }
9954
+ const existing = Object.hasOwn(permission, category) ? permission[category] : void 0;
9955
+ const target = existing ?? {};
9956
+ if (existing === void 0) {
9957
+ permission[category] = target;
9958
+ }
9959
+ target[pattern] = action;
9960
+ }
9873
9961
  }
9874
9962
  return this.toRulesyncPermissionsDefault({
9875
9963
  fileContent: JSON.stringify({ permission }, null, 2)
@@ -9887,50 +9975,238 @@ var GeminicliPermissions = class _GeminicliPermissions extends ToolPermissions {
9887
9975
  baseDir,
9888
9976
  relativeDirPath,
9889
9977
  relativeFilePath,
9890
- fileContent: JSON.stringify({}, null, 2),
9978
+ fileContent: "",
9891
9979
  validate: false
9892
9980
  });
9893
9981
  }
9894
9982
  };
9895
- function convertRulesyncToGeminicliTools({
9896
- config,
9897
- logger
9898
- }) {
9899
- const allowed = [];
9900
- const exclude = [];
9901
- for (const [toolName, rules] of Object.entries(config.permission)) {
9983
+ function buildGeminicliPolicyContent(config, logger) {
9984
+ const rules = [];
9985
+ let order = 0;
9986
+ for (const [toolName, entries] of Object.entries(config.permission)) {
9902
9987
  const mappedToolName = RULESYNC_TO_GEMINICLI_TOOL_NAME[toolName] ?? toolName;
9903
- if (!RULESYNC_TO_GEMINICLI_TOOL_NAME[toolName]) {
9904
- logger?.warn(`Gemini CLI permissions use direct tool names. Mapping as-is: ${toolName}`);
9905
- }
9906
- for (const [pattern, action] of Object.entries(rules)) {
9907
- if (action === "ask") {
9908
- logger?.warn(
9909
- `Gemini CLI does not support explicit "ask" rules in settings. Skipping ${toolName}:${pattern}`
9988
+ for (const [pattern, action] of Object.entries(entries)) {
9989
+ if (pattern === "") {
9990
+ logger.warn(
9991
+ `Skipping rule "${toolName}: "": empty pattern is not a valid permission target and would silently match every invocation (bash) or nothing (other tools).`
9910
9992
  );
9911
9993
  continue;
9912
9994
  }
9913
- const geminiEntry = pattern === "*" ? mappedToolName : `${mappedToolName}(${pattern})`;
9914
- if (action === "allow") {
9915
- allowed.push(geminiEntry);
9916
- } else {
9917
- exclude.push(geminiEntry);
9995
+ if (hasUnsafeAnchorChar(pattern)) {
9996
+ logger.warn(
9997
+ `Skipping rule "${toolName}: ${pattern}": pattern contains a character (" or \\) that would break JSON-anchor matching in the Gemini CLI Policy Engine.`
9998
+ );
9999
+ continue;
10000
+ }
10001
+ const decision = mapToGeminicliDecision(action);
10002
+ if (mappedToolName === "run_shell_command" && (pattern === "*" || pattern === "**") && decision !== "ask_user") {
10003
+ logger.warn(
10004
+ `Skipping rule "${toolName}: ${pattern}" with decision ${decision}: bash match-all patterns are only supported with "ask" because they would otherwise affect every shell command.`
10005
+ );
10006
+ continue;
9918
10007
  }
10008
+ const currentRule = {
10009
+ toolName: mappedToolName,
10010
+ decision,
10011
+ priority: priorityForDecision(decision)
10012
+ };
10013
+ if (mappedToolName === "run_shell_command") {
10014
+ applyShellPattern({ rule: currentRule, pattern, toolName, logger });
10015
+ } else if (pattern !== "*") {
10016
+ currentRule.argsPattern = buildNonShellArgsPattern(pattern);
10017
+ }
10018
+ rules.push({ rule: currentRule, order: order++ });
9919
10019
  }
9920
10020
  }
9921
- return { allowed, exclude };
10021
+ rules.sort((a, b) => {
10022
+ const diff = toNumber(b.rule.priority) - toNumber(a.rule.priority);
10023
+ return diff !== 0 ? diff : a.order - b.order;
10024
+ });
10025
+ return smolToml5.stringify({ rule: rules.map((entry) => entry.rule) });
9922
10026
  }
9923
- function parseGeminicliToolEntry({ entry }) {
9924
- const match = /^([^()]+?)(?:\((.*)\))?$/.exec(entry);
9925
- if (!match) return { category: entry, pattern: "*" };
9926
- const rawToolName = match[1]?.trim() ?? entry;
9927
- const mappedCategory = Object.entries(RULESYNC_TO_GEMINICLI_TOOL_NAME).find(
9928
- ([, value]) => value === rawToolName
9929
- )?.[0];
9930
- return {
9931
- category: mappedCategory ?? rawToolName,
9932
- pattern: (match[2] ?? "*").trim()
9933
- };
10027
+ function buildNonShellArgsPattern(pattern) {
10028
+ return `"${globPatternToRegex(pattern)}${VALUE_END_ANCHOR}`;
10029
+ }
10030
+ function hasUnsafeAnchorChar(pattern) {
10031
+ return pattern.includes('"') || pattern.includes("\\");
10032
+ }
10033
+ function toNumber(value) {
10034
+ return typeof value === "number" ? value : 0;
10035
+ }
10036
+ function applyShellPattern({
10037
+ rule,
10038
+ pattern,
10039
+ toolName,
10040
+ logger
10041
+ }) {
10042
+ if (pattern === "*") {
10043
+ return;
10044
+ }
10045
+ const trailingWildcardStripped = pattern.endsWith(" *") ? pattern.slice(0, -2) : pattern;
10046
+ if (hasGlobMetacharacter(trailingWildcardStripped)) {
10047
+ rule.argsPattern = `${COMMAND_ARGS_ANCHOR}${globPatternToRegex(pattern)}`;
10048
+ logger.warn(
10049
+ `Gemini CLI does not support glob metacharacters inside a bash command prefix; emitting argsPattern for rule "${toolName}: ${pattern}".`
10050
+ );
10051
+ return;
10052
+ }
10053
+ rule.commandPrefix = trailingWildcardStripped;
10054
+ }
10055
+ function hasGlobMetacharacter(pattern) {
10056
+ return /[*?[\]]/.test(pattern);
10057
+ }
10058
+ function priorityForDecision(decision) {
10059
+ if (decision === "deny") return PRIORITY_DENY;
10060
+ if (decision === "ask_user") return PRIORITY_ASK;
10061
+ return PRIORITY_ALLOW;
10062
+ }
10063
+ function mapToGeminicliDecision(action) {
10064
+ if (action === "ask") {
10065
+ return "ask_user";
10066
+ }
10067
+ return action;
10068
+ }
10069
+ function mapFromGeminicliDecision(decision) {
10070
+ if (decision === "allow") return "allow";
10071
+ if (decision === "deny") return "deny";
10072
+ if (decision === "ask_user") return "ask";
10073
+ return null;
10074
+ }
10075
+ function globPatternToRegex(pattern) {
10076
+ let regex = "";
10077
+ let i = 0;
10078
+ while (i < pattern.length) {
10079
+ const char = pattern[i];
10080
+ if (char === void 0) {
10081
+ break;
10082
+ }
10083
+ if (char === "*" && pattern[i + 1] === "*") {
10084
+ regex += DOUBLE_STAR_REGEX;
10085
+ i += 2;
10086
+ continue;
10087
+ }
10088
+ if (char === "*") {
10089
+ regex += SINGLE_STAR_REGEX;
10090
+ i += 1;
10091
+ continue;
10092
+ }
10093
+ if (char === "?") {
10094
+ regex += SINGLE_CHAR_REGEX;
10095
+ i += 1;
10096
+ continue;
10097
+ }
10098
+ if (char === "[") {
10099
+ regex += escapeRegexChar(char);
10100
+ i += 1;
10101
+ continue;
10102
+ }
10103
+ if (char === "]") {
10104
+ regex += escapeRegexChar(char);
10105
+ i += 1;
10106
+ continue;
10107
+ }
10108
+ if (isRegexMetacharacter(char)) {
10109
+ regex += `\\${char}`;
10110
+ i += 1;
10111
+ continue;
10112
+ }
10113
+ regex += char;
10114
+ i += 1;
10115
+ }
10116
+ return regex;
10117
+ }
10118
+ function escapeRegexChar(char) {
10119
+ return `\\${char}`;
10120
+ }
10121
+ function isRegexMetacharacter(char) {
10122
+ return /[.+^${}()|\\]/.test(char);
10123
+ }
10124
+ function regexToGlobPattern(regex) {
10125
+ let source = regex;
10126
+ if (source.endsWith(VALUE_END_ANCHOR)) {
10127
+ source = source.slice(0, -VALUE_END_ANCHOR.length);
10128
+ }
10129
+ let glob = "";
10130
+ let i = 0;
10131
+ while (i < source.length) {
10132
+ if (source.startsWith(DOUBLE_STAR_REGEX, i)) {
10133
+ glob += "**";
10134
+ i += DOUBLE_STAR_REGEX.length;
10135
+ continue;
10136
+ }
10137
+ if (source.startsWith(LEGACY_DOUBLE_STAR_REGEX, i)) {
10138
+ glob += "**";
10139
+ i += LEGACY_DOUBLE_STAR_REGEX.length;
10140
+ continue;
10141
+ }
10142
+ if (source.startsWith(SINGLE_STAR_REGEX, i)) {
10143
+ glob += "*";
10144
+ i += SINGLE_STAR_REGEX.length;
10145
+ continue;
10146
+ }
10147
+ if (source.startsWith(LEGACY_SINGLE_STAR_REGEX, i)) {
10148
+ glob += "*";
10149
+ i += LEGACY_SINGLE_STAR_REGEX.length;
10150
+ continue;
10151
+ }
10152
+ if (source.startsWith(SINGLE_CHAR_REGEX, i)) {
10153
+ glob += "?";
10154
+ i += SINGLE_CHAR_REGEX.length;
10155
+ continue;
10156
+ }
10157
+ const char = source[i];
10158
+ if (char === "\\") {
10159
+ const escaped = source[i + 1];
10160
+ if (escaped !== void 0) {
10161
+ glob += escaped;
10162
+ i += 2;
10163
+ continue;
10164
+ }
10165
+ }
10166
+ glob += char ?? "";
10167
+ i += 1;
10168
+ }
10169
+ return glob;
10170
+ }
10171
+ var GeminicliPolicyRuleSchema = import_mini29.z.looseObject({
10172
+ toolName: import_mini29.z.string(),
10173
+ decision: import_mini29.z.optional(import_mini29.z.unknown()),
10174
+ commandPrefix: import_mini29.z.optional(import_mini29.z.string()),
10175
+ argsPattern: import_mini29.z.optional(import_mini29.z.string())
10176
+ });
10177
+ var GeminicliPolicyFileSchema = import_mini29.z.looseObject({
10178
+ rule: import_mini29.z.optional(import_mini29.z.array(import_mini29.z.looseObject({})))
10179
+ });
10180
+ function extractRules(parsed, logger) {
10181
+ const parsedFile = GeminicliPolicyFileSchema.safeParse(parsed);
10182
+ if (!parsedFile.success || !parsedFile.data.rule) {
10183
+ return [];
10184
+ }
10185
+ const rules = [];
10186
+ for (const [index, entry] of parsedFile.data.rule.entries()) {
10187
+ const result = GeminicliPolicyRuleSchema.safeParse(entry);
10188
+ if (result.success) {
10189
+ rules.push(result.data);
10190
+ continue;
10191
+ }
10192
+ logger.warn(
10193
+ `Skipping malformed Gemini CLI policy rule at index ${index}: ${formatError(result.error)}`
10194
+ );
10195
+ }
10196
+ return rules;
10197
+ }
10198
+ function extractPattern(rule) {
10199
+ if (rule.toolName === "run_shell_command") {
10200
+ if (rule.argsPattern) {
10201
+ const stripped = rule.argsPattern.startsWith(COMMAND_ARGS_ANCHOR) ? rule.argsPattern.slice(COMMAND_ARGS_ANCHOR.length) : rule.argsPattern;
10202
+ return regexToGlobPattern(stripped);
10203
+ }
10204
+ if (!rule.commandPrefix) return "*";
10205
+ return rule.commandPrefix.endsWith(" *") || rule.commandPrefix.endsWith("*") ? rule.commandPrefix : `${rule.commandPrefix} *`;
10206
+ }
10207
+ if (!rule.argsPattern) return "*";
10208
+ const regex = rule.argsPattern.startsWith('"') ? rule.argsPattern.slice(1) : rule.argsPattern;
10209
+ return regexToGlobPattern(regex);
9934
10210
  }
9935
10211
 
9936
10212
  // src/features/permissions/kiro-permissions.ts
@@ -15188,7 +15464,7 @@ var ClaudecodeSubagent = class _ClaudecodeSubagent extends ToolSubagent {
15188
15464
 
15189
15465
  // src/features/subagents/codexcli-subagent.ts
15190
15466
  var import_node_path104 = require("path");
15191
- var smolToml5 = __toESM(require("smol-toml"), 1);
15467
+ var smolToml6 = __toESM(require("smol-toml"), 1);
15192
15468
  var import_mini58 = require("zod/mini");
15193
15469
  var CodexCliSubagentTomlSchema = import_mini58.z.looseObject({
15194
15470
  name: import_mini58.z.string(),
@@ -15200,13 +15476,13 @@ var CodexCliSubagentTomlSchema = import_mini58.z.looseObject({
15200
15476
  });
15201
15477
  function stringifyCodexCliSubagentToml(tomlObj) {
15202
15478
  const { developer_instructions, ...restFields } = tomlObj;
15203
- const restToml = smolToml5.stringify(restFields).trimEnd();
15479
+ const restToml = smolToml6.stringify(restFields).trimEnd();
15204
15480
  if (developer_instructions === void 0) {
15205
15481
  return restToml;
15206
15482
  }
15207
- const developerInstructionsToml = developer_instructions.includes("\n") ? developer_instructions.includes("'''") ? smolToml5.stringify({ developer_instructions }).trimEnd() : `developer_instructions = '''
15483
+ const developerInstructionsToml = developer_instructions.includes("\n") ? developer_instructions.includes("'''") ? smolToml6.stringify({ developer_instructions }).trimEnd() : `developer_instructions = '''
15208
15484
  ${developer_instructions}
15209
- '''` : smolToml5.stringify({ developer_instructions }).trimEnd();
15485
+ '''` : smolToml6.stringify({ developer_instructions }).trimEnd();
15210
15486
  return [restToml, developerInstructionsToml].filter((value) => value.length > 0).join("\n");
15211
15487
  }
15212
15488
  var CodexCliSubagent = class _CodexCliSubagent extends ToolSubagent {
@@ -15214,7 +15490,7 @@ var CodexCliSubagent = class _CodexCliSubagent extends ToolSubagent {
15214
15490
  constructor({ body, ...rest }) {
15215
15491
  if (rest.validate !== false) {
15216
15492
  try {
15217
- const parsed = smolToml5.parse(body);
15493
+ const parsed = smolToml6.parse(body);
15218
15494
  CodexCliSubagentTomlSchema.parse(parsed);
15219
15495
  } catch (error) {
15220
15496
  throw new Error(
@@ -15239,7 +15515,7 @@ var CodexCliSubagent = class _CodexCliSubagent extends ToolSubagent {
15239
15515
  toRulesyncSubagent() {
15240
15516
  let parsed;
15241
15517
  try {
15242
- parsed = CodexCliSubagentTomlSchema.parse(smolToml5.parse(this.body));
15518
+ parsed = CodexCliSubagentTomlSchema.parse(smolToml6.parse(this.body));
15243
15519
  } catch (error) {
15244
15520
  throw new Error(
15245
15521
  `Failed to parse TOML in ${(0, import_node_path104.join)(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${error instanceof Error ? error.message : String(error)}`,
@@ -15300,7 +15576,7 @@ var CodexCliSubagent = class _CodexCliSubagent extends ToolSubagent {
15300
15576
  }
15301
15577
  validate() {
15302
15578
  try {
15303
- const parsed = smolToml5.parse(this.body);
15579
+ const parsed = smolToml6.parse(this.body);
15304
15580
  CodexCliSubagentTomlSchema.parse(parsed);
15305
15581
  return { success: true, error: null };
15306
15582
  } catch (error) {
@@ -21624,72 +21900,6 @@ async function importPermissionsCore(params) {
21624
21900
  return writtenCount;
21625
21901
  }
21626
21902
 
21627
- // src/utils/logger.ts
21628
- var BaseLogger = class {
21629
- _verbose = false;
21630
- _silent = false;
21631
- constructor({ verbose = false, silent = false } = {}) {
21632
- this._silent = silent;
21633
- this._verbose = verbose && !silent;
21634
- }
21635
- get verbose() {
21636
- return this._verbose;
21637
- }
21638
- get silent() {
21639
- return this._silent;
21640
- }
21641
- configure({ verbose, silent }) {
21642
- if (verbose && silent) {
21643
- this._silent = false;
21644
- if (!isEnvTest()) {
21645
- this.onConflictingFlags();
21646
- }
21647
- }
21648
- this._silent = silent;
21649
- this._verbose = verbose && !silent;
21650
- }
21651
- onConflictingFlags() {
21652
- console.warn("Both --verbose and --silent specified; --silent takes precedence");
21653
- }
21654
- };
21655
- var ConsoleLogger = class extends BaseLogger {
21656
- isSuppressed() {
21657
- return isEnvTest() || this._silent;
21658
- }
21659
- get jsonMode() {
21660
- return false;
21661
- }
21662
- captureData(_key, _value) {
21663
- }
21664
- getJsonData() {
21665
- return {};
21666
- }
21667
- outputJson(_success, _error) {
21668
- }
21669
- info(message, ...args) {
21670
- if (this.isSuppressed()) return;
21671
- console.log(message, ...args);
21672
- }
21673
- success(message, ...args) {
21674
- if (this.isSuppressed()) return;
21675
- console.log(message, ...args);
21676
- }
21677
- warn(message, ...args) {
21678
- if (this.isSuppressed()) return;
21679
- console.warn(message, ...args);
21680
- }
21681
- // Errors are always emitted, even in silent mode
21682
- error(message, _code, ...args) {
21683
- if (isEnvTest()) return;
21684
- const errorMessage = message instanceof Error ? message.message : message;
21685
- console.error(errorMessage, ...args);
21686
- }
21687
- debug(message, ...args) {
21688
- if (!this._verbose || this.isSuppressed()) return;
21689
- console.log(message, ...args);
21690
- }
21691
- };
21692
-
21693
21903
  // src/index.ts
21694
21904
  async function generate2(options = {}) {
21695
21905
  const { silent = true, verbose = false, ...rest } = options;
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  checkRulesyncDirExists,
7
7
  generate,
8
8
  importFromTool
9
- } from "./chunk-Q5FDY7NL.js";
9
+ } from "./chunk-ILMHM7BF.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": "8.5.0",
3
+ "version": "8.6.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",