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.
@@ -860,6 +860,176 @@ function getBaseDirsInLightOfGlobal({
860
860
  return resolvedBaseDirs;
861
861
  }
862
862
 
863
+ // src/types/json-output.ts
864
+ var ErrorCodes = {
865
+ CONFIG_NOT_FOUND: "CONFIG_NOT_FOUND",
866
+ RULESYNC_DIR_NOT_FOUND: "RULESYNC_DIR_NOT_FOUND",
867
+ INVALID_TARGET: "INVALID_TARGET",
868
+ FETCH_FAILED: "FETCH_FAILED",
869
+ WRITE_FAILED: "WRITE_FAILED",
870
+ VALIDATION_FAILED: "VALIDATION_FAILED",
871
+ GENERATION_FAILED: "GENERATION_FAILED",
872
+ IMPORT_FAILED: "IMPORT_FAILED",
873
+ INSTALL_FAILED: "INSTALL_FAILED",
874
+ UPDATE_FAILED: "UPDATE_FAILED",
875
+ GITIGNORE_FAILED: "GITIGNORE_FAILED",
876
+ INIT_FAILED: "INIT_FAILED",
877
+ MCP_FAILED: "MCP_FAILED",
878
+ UNKNOWN_ERROR: "UNKNOWN_ERROR"
879
+ };
880
+ var CLIError = class extends Error {
881
+ constructor(message, code = ErrorCodes.UNKNOWN_ERROR, exitCode = 1) {
882
+ super(message);
883
+ this.code = code;
884
+ this.exitCode = exitCode;
885
+ this.name = "CLIError";
886
+ }
887
+ };
888
+
889
+ // src/utils/logger.ts
890
+ var BaseLogger = class {
891
+ _verbose = false;
892
+ _silent = false;
893
+ constructor({ verbose = false, silent = false } = {}) {
894
+ this._silent = silent;
895
+ this._verbose = verbose && !silent;
896
+ }
897
+ get verbose() {
898
+ return this._verbose;
899
+ }
900
+ get silent() {
901
+ return this._silent;
902
+ }
903
+ configure({ verbose, silent }) {
904
+ if (verbose && silent) {
905
+ this._silent = false;
906
+ if (!isEnvTest()) {
907
+ this.onConflictingFlags();
908
+ }
909
+ }
910
+ this._silent = silent;
911
+ this._verbose = verbose && !silent;
912
+ }
913
+ onConflictingFlags() {
914
+ console.warn("Both --verbose and --silent specified; --silent takes precedence");
915
+ }
916
+ };
917
+ var ConsoleLogger = class extends BaseLogger {
918
+ isSuppressed() {
919
+ return isEnvTest() || this._silent;
920
+ }
921
+ get jsonMode() {
922
+ return false;
923
+ }
924
+ captureData(_key, _value) {
925
+ }
926
+ getJsonData() {
927
+ return {};
928
+ }
929
+ outputJson(_success, _error) {
930
+ }
931
+ info(message, ...args) {
932
+ if (this.isSuppressed()) return;
933
+ console.log(message, ...args);
934
+ }
935
+ success(message, ...args) {
936
+ if (this.isSuppressed()) return;
937
+ console.log(message, ...args);
938
+ }
939
+ warn(message, ...args) {
940
+ if (this.isSuppressed()) return;
941
+ console.warn(message, ...args);
942
+ }
943
+ // Errors are always emitted, even in silent mode
944
+ error(message, _code, ...args) {
945
+ if (isEnvTest()) return;
946
+ const errorMessage = message instanceof Error ? message.message : message;
947
+ console.error(errorMessage, ...args);
948
+ }
949
+ debug(message, ...args) {
950
+ if (!this._verbose || this.isSuppressed()) return;
951
+ console.log(message, ...args);
952
+ }
953
+ };
954
+ var JsonLogger = class extends BaseLogger {
955
+ _jsonOutputDone = false;
956
+ _jsonData = {};
957
+ _commandName;
958
+ _version;
959
+ constructor({
960
+ command,
961
+ version,
962
+ verbose = false,
963
+ silent = false
964
+ }) {
965
+ super({ verbose, silent });
966
+ this._commandName = command;
967
+ this._version = version;
968
+ }
969
+ // Suppress raw console.warn in JSON mode to avoid non-JSON text on stderr
970
+ onConflictingFlags() {
971
+ }
972
+ get jsonMode() {
973
+ return true;
974
+ }
975
+ captureData(key, value) {
976
+ this._jsonData[key] = value;
977
+ }
978
+ getJsonData() {
979
+ return { ...this._jsonData };
980
+ }
981
+ outputJson(success, error) {
982
+ if (this._jsonOutputDone) return;
983
+ this._jsonOutputDone = true;
984
+ const output = {
985
+ success,
986
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
987
+ command: this._commandName,
988
+ version: this._version
989
+ };
990
+ if (success) {
991
+ output.data = this._jsonData;
992
+ } else if (error) {
993
+ output.error = {
994
+ code: error.code,
995
+ message: error.message
996
+ };
997
+ if (error.details) {
998
+ output.error.details = error.details;
999
+ }
1000
+ if (error.stack) {
1001
+ output.error.stack = error.stack;
1002
+ }
1003
+ }
1004
+ const jsonStr = JSON.stringify(output, null, 2);
1005
+ if (success) {
1006
+ console.log(jsonStr);
1007
+ } else {
1008
+ console.error(jsonStr);
1009
+ }
1010
+ }
1011
+ info(_message, ..._args) {
1012
+ }
1013
+ success(_message, ..._args) {
1014
+ }
1015
+ warn(_message, ..._args) {
1016
+ }
1017
+ error(message, code, ..._args) {
1018
+ if (isEnvTest()) return;
1019
+ const errorMessage = message instanceof Error ? message.message : message;
1020
+ const errorInfo = {
1021
+ code: code || ErrorCodes.UNKNOWN_ERROR,
1022
+ message: errorMessage
1023
+ };
1024
+ if (this._verbose && message instanceof Error && message.stack) {
1025
+ errorInfo.stack = message.stack;
1026
+ }
1027
+ this.outputJson(false, errorInfo);
1028
+ }
1029
+ debug(_message, ..._args) {
1030
+ }
1031
+ };
1032
+
863
1033
  // src/lib/generate.ts
864
1034
  import { join as join138 } from "path";
865
1035
  import { intersection } from "es-toolkit";
@@ -9763,15 +9933,10 @@ function mapBashActionToDecision(action) {
9763
9933
 
9764
9934
  // src/features/permissions/geminicli-permissions.ts
9765
9935
  import { join as join64 } from "path";
9936
+ import * as smolToml5 from "smol-toml";
9766
9937
  import { z as z29 } from "zod/mini";
9767
- var GeminiCliSettingsSchema = z29.looseObject({
9768
- tools: z29.optional(
9769
- z29.looseObject({
9770
- allowed: z29.optional(z29.array(z29.string())),
9771
- exclude: z29.optional(z29.array(z29.string()))
9772
- })
9773
- )
9774
- });
9938
+ var GEMINICLI_POLICY_RELATIVE_DIR_PATH = join64(".gemini", "policies");
9939
+ var GEMINICLI_POLICY_FILE_NAME = "rulesync.toml";
9775
9940
  var RULESYNC_TO_GEMINICLI_TOOL_NAME = {
9776
9941
  bash: "run_shell_command",
9777
9942
  read: "read_file",
@@ -9779,16 +9944,32 @@ var RULESYNC_TO_GEMINICLI_TOOL_NAME = {
9779
9944
  write: "write_file",
9780
9945
  webfetch: "web_fetch"
9781
9946
  };
9947
+ var GEMINICLI_TO_RULESYNC_TOOL_NAME = Object.fromEntries(
9948
+ Object.entries(RULESYNC_TO_GEMINICLI_TOOL_NAME).map(([k, v]) => [v, k])
9949
+ );
9950
+ var PRIORITY_DENY = 1e6;
9951
+ var PRIORITY_ASK = 1e3;
9952
+ var PRIORITY_ALLOW = 1;
9953
+ var SINGLE_STAR_REGEX = '[^/\\"]*';
9954
+ var DOUBLE_STAR_REGEX = '[^\\"]*';
9955
+ var SINGLE_CHAR_REGEX = '[^/\\"]';
9956
+ var LEGACY_SINGLE_STAR_REGEX = '[^\\"]*';
9957
+ var LEGACY_DOUBLE_STAR_REGEX = ".*";
9958
+ var COMMAND_ARGS_ANCHOR = '"command":"';
9959
+ var VALUE_END_ANCHOR = '\\"';
9960
+ var RESERVED_OBJECT_KEYS = /* @__PURE__ */ new Set([
9961
+ "__proto__",
9962
+ "constructor",
9963
+ "prototype"
9964
+ ]);
9965
+ var moduleLogger = new ConsoleLogger();
9782
9966
  var GeminicliPermissions = class _GeminicliPermissions extends ToolPermissions {
9783
9967
  static getSettablePaths(_options = {}) {
9784
9968
  return {
9785
- relativeDirPath: ".gemini",
9786
- relativeFilePath: "settings.json"
9969
+ relativeDirPath: GEMINICLI_POLICY_RELATIVE_DIR_PATH,
9970
+ relativeFilePath: GEMINICLI_POLICY_FILE_NAME
9787
9971
  };
9788
9972
  }
9789
- isDeletable() {
9790
- return false;
9791
- }
9792
9973
  static async fromFile({
9793
9974
  baseDir = process.cwd(),
9794
9975
  validate = true,
@@ -9796,7 +9977,7 @@ var GeminicliPermissions = class _GeminicliPermissions extends ToolPermissions {
9796
9977
  }) {
9797
9978
  const paths = this.getSettablePaths({ global });
9798
9979
  const filePath = join64(baseDir, paths.relativeDirPath, paths.relativeFilePath);
9799
- const fileContent = await readFileContentOrNull(filePath) ?? JSON.stringify({}, null, 2);
9980
+ const fileContent = await readFileContentOrNull(filePath) ?? "";
9800
9981
  return new _GeminicliPermissions({
9801
9982
  baseDir,
9802
9983
  relativeDirPath: paths.relativeDirPath,
@@ -9805,63 +9986,72 @@ var GeminicliPermissions = class _GeminicliPermissions extends ToolPermissions {
9805
9986
  validate
9806
9987
  });
9807
9988
  }
9808
- static async fromRulesyncPermissions({
9989
+ static fromRulesyncPermissions({
9809
9990
  baseDir = process.cwd(),
9810
9991
  rulesyncPermissions,
9811
9992
  validate = true,
9812
- logger,
9813
- global = false
9993
+ global = false,
9994
+ logger = moduleLogger
9814
9995
  }) {
9815
9996
  const paths = this.getSettablePaths({ global });
9816
- const filePath = join64(baseDir, paths.relativeDirPath, paths.relativeFilePath);
9817
- const existingContent = await readFileContentOrNull(filePath) ?? JSON.stringify({}, null, 2);
9818
- const settingsResult = GeminiCliSettingsSchema.safeParse(JSON.parse(existingContent));
9819
- if (!settingsResult.success) {
9820
- throw new Error(
9821
- `Failed to parse existing Gemini CLI settings at ${filePath}: ${formatError(settingsResult.error)}`
9822
- );
9823
- }
9824
- const { allowed, exclude } = convertRulesyncToGeminicliTools({
9825
- config: rulesyncPermissions.getJson(),
9826
- logger
9827
- });
9828
- const merged = {
9829
- ...settingsResult.data,
9830
- tools: {
9831
- ...settingsResult.data.tools,
9832
- ...allowed.length > 0 ? { allowed } : {},
9833
- ...exclude.length > 0 ? { exclude } : {}
9834
- }
9835
- };
9997
+ const fileContent = buildGeminicliPolicyContent(rulesyncPermissions.getJson(), logger);
9836
9998
  return new _GeminicliPermissions({
9837
9999
  baseDir,
9838
10000
  relativeDirPath: paths.relativeDirPath,
9839
10001
  relativeFilePath: paths.relativeFilePath,
9840
- fileContent: JSON.stringify(merged, null, 2),
10002
+ fileContent,
9841
10003
  validate
9842
10004
  });
9843
10005
  }
9844
10006
  toRulesyncPermissions() {
9845
- let settings;
9846
- try {
9847
- const parsed = JSON.parse(this.getFileContent());
9848
- settings = GeminiCliSettingsSchema.parse(parsed);
9849
- } catch (error) {
9850
- throw new Error(
9851
- `Failed to parse Gemini CLI permissions content in ${join64(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`,
9852
- { cause: error }
9853
- );
9854
- }
9855
10007
  const permission = {};
9856
- for (const toolEntry of settings.tools?.allowed ?? []) {
9857
- const mapped = parseGeminicliToolEntry({ entry: toolEntry });
9858
- const rules = permission[mapped.category] ??= {};
9859
- rules[mapped.pattern] = "allow";
9860
- }
9861
- for (const toolEntry of settings.tools?.exclude ?? []) {
9862
- const mapped = parseGeminicliToolEntry({ entry: toolEntry });
9863
- const rules = permission[mapped.category] ??= {};
9864
- rules[mapped.pattern] = "deny";
10008
+ const fileContent = this.getFileContent();
10009
+ if (fileContent.trim().length > 0) {
10010
+ let parsed;
10011
+ try {
10012
+ parsed = smolToml5.parse(fileContent);
10013
+ } catch (error) {
10014
+ throw new Error(
10015
+ `Failed to parse Gemini CLI policy TOML in ${join64(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`,
10016
+ { cause: error }
10017
+ );
10018
+ }
10019
+ const rules = extractRules(parsed, moduleLogger);
10020
+ for (const [index, rule] of rules.entries()) {
10021
+ const mappedCategory = Object.hasOwn(GEMINICLI_TO_RULESYNC_TOOL_NAME, rule.toolName) ? GEMINICLI_TO_RULESYNC_TOOL_NAME[rule.toolName] : void 0;
10022
+ const category = mappedCategory ?? rule.toolName;
10023
+ if (RESERVED_OBJECT_KEYS.has(category)) {
10024
+ moduleLogger.warn(
10025
+ `Skipping rule #${index} in ${this.getRelativeFilePath()}: toolName "${rule.toolName}" maps to a reserved object key ("${category}") and would risk prototype pollution.`
10026
+ );
10027
+ continue;
10028
+ }
10029
+ const action = mapFromGeminicliDecision(rule.decision);
10030
+ if (!action) {
10031
+ moduleLogger.warn(
10032
+ `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)}`
10033
+ );
10034
+ continue;
10035
+ }
10036
+ if (rule.toolName === "run_shell_command" && rule.commandPrefix !== void 0 && rule.argsPattern !== void 0) {
10037
+ moduleLogger.warn(
10038
+ `Rule #${index} in ${this.getRelativeFilePath()} sets both commandPrefix and argsPattern; rulesync will honor argsPattern and ignore commandPrefix=${JSON.stringify(rule.commandPrefix)}.`
10039
+ );
10040
+ }
10041
+ const pattern = extractPattern(rule);
10042
+ if (RESERVED_OBJECT_KEYS.has(pattern)) {
10043
+ moduleLogger.warn(
10044
+ `Skipping rule #${index} in ${this.getRelativeFilePath()}: pattern "${pattern}" is a reserved object key.`
10045
+ );
10046
+ continue;
10047
+ }
10048
+ const existing = Object.hasOwn(permission, category) ? permission[category] : void 0;
10049
+ const target = existing ?? {};
10050
+ if (existing === void 0) {
10051
+ permission[category] = target;
10052
+ }
10053
+ target[pattern] = action;
10054
+ }
9865
10055
  }
9866
10056
  return this.toRulesyncPermissionsDefault({
9867
10057
  fileContent: JSON.stringify({ permission }, null, 2)
@@ -9879,50 +10069,238 @@ var GeminicliPermissions = class _GeminicliPermissions extends ToolPermissions {
9879
10069
  baseDir,
9880
10070
  relativeDirPath,
9881
10071
  relativeFilePath,
9882
- fileContent: JSON.stringify({}, null, 2),
10072
+ fileContent: "",
9883
10073
  validate: false
9884
10074
  });
9885
10075
  }
9886
10076
  };
9887
- function convertRulesyncToGeminicliTools({
9888
- config,
9889
- logger
9890
- }) {
9891
- const allowed = [];
9892
- const exclude = [];
9893
- for (const [toolName, rules] of Object.entries(config.permission)) {
10077
+ function buildGeminicliPolicyContent(config, logger) {
10078
+ const rules = [];
10079
+ let order = 0;
10080
+ for (const [toolName, entries] of Object.entries(config.permission)) {
9894
10081
  const mappedToolName = RULESYNC_TO_GEMINICLI_TOOL_NAME[toolName] ?? toolName;
9895
- if (!RULESYNC_TO_GEMINICLI_TOOL_NAME[toolName]) {
9896
- logger?.warn(`Gemini CLI permissions use direct tool names. Mapping as-is: ${toolName}`);
9897
- }
9898
- for (const [pattern, action] of Object.entries(rules)) {
9899
- if (action === "ask") {
9900
- logger?.warn(
9901
- `Gemini CLI does not support explicit "ask" rules in settings. Skipping ${toolName}:${pattern}`
10082
+ for (const [pattern, action] of Object.entries(entries)) {
10083
+ if (pattern === "") {
10084
+ logger.warn(
10085
+ `Skipping rule "${toolName}: "": empty pattern is not a valid permission target and would silently match every invocation (bash) or nothing (other tools).`
9902
10086
  );
9903
10087
  continue;
9904
10088
  }
9905
- const geminiEntry = pattern === "*" ? mappedToolName : `${mappedToolName}(${pattern})`;
9906
- if (action === "allow") {
9907
- allowed.push(geminiEntry);
9908
- } else {
9909
- exclude.push(geminiEntry);
10089
+ if (hasUnsafeAnchorChar(pattern)) {
10090
+ logger.warn(
10091
+ `Skipping rule "${toolName}: ${pattern}": pattern contains a character (" or \\) that would break JSON-anchor matching in the Gemini CLI Policy Engine.`
10092
+ );
10093
+ continue;
10094
+ }
10095
+ const decision = mapToGeminicliDecision(action);
10096
+ if (mappedToolName === "run_shell_command" && (pattern === "*" || pattern === "**") && decision !== "ask_user") {
10097
+ logger.warn(
10098
+ `Skipping rule "${toolName}: ${pattern}" with decision ${decision}: bash match-all patterns are only supported with "ask" because they would otherwise affect every shell command.`
10099
+ );
10100
+ continue;
10101
+ }
10102
+ const currentRule = {
10103
+ toolName: mappedToolName,
10104
+ decision,
10105
+ priority: priorityForDecision(decision)
10106
+ };
10107
+ if (mappedToolName === "run_shell_command") {
10108
+ applyShellPattern({ rule: currentRule, pattern, toolName, logger });
10109
+ } else if (pattern !== "*") {
10110
+ currentRule.argsPattern = buildNonShellArgsPattern(pattern);
9910
10111
  }
10112
+ rules.push({ rule: currentRule, order: order++ });
9911
10113
  }
9912
10114
  }
9913
- return { allowed, exclude };
10115
+ rules.sort((a, b) => {
10116
+ const diff = toNumber(b.rule.priority) - toNumber(a.rule.priority);
10117
+ return diff !== 0 ? diff : a.order - b.order;
10118
+ });
10119
+ return smolToml5.stringify({ rule: rules.map((entry) => entry.rule) });
9914
10120
  }
9915
- function parseGeminicliToolEntry({ entry }) {
9916
- const match = /^([^()]+?)(?:\((.*)\))?$/.exec(entry);
9917
- if (!match) return { category: entry, pattern: "*" };
9918
- const rawToolName = match[1]?.trim() ?? entry;
9919
- const mappedCategory = Object.entries(RULESYNC_TO_GEMINICLI_TOOL_NAME).find(
9920
- ([, value]) => value === rawToolName
9921
- )?.[0];
9922
- return {
9923
- category: mappedCategory ?? rawToolName,
9924
- pattern: (match[2] ?? "*").trim()
9925
- };
10121
+ function buildNonShellArgsPattern(pattern) {
10122
+ return `"${globPatternToRegex(pattern)}${VALUE_END_ANCHOR}`;
10123
+ }
10124
+ function hasUnsafeAnchorChar(pattern) {
10125
+ return pattern.includes('"') || pattern.includes("\\");
10126
+ }
10127
+ function toNumber(value) {
10128
+ return typeof value === "number" ? value : 0;
10129
+ }
10130
+ function applyShellPattern({
10131
+ rule,
10132
+ pattern,
10133
+ toolName,
10134
+ logger
10135
+ }) {
10136
+ if (pattern === "*") {
10137
+ return;
10138
+ }
10139
+ const trailingWildcardStripped = pattern.endsWith(" *") ? pattern.slice(0, -2) : pattern;
10140
+ if (hasGlobMetacharacter(trailingWildcardStripped)) {
10141
+ rule.argsPattern = `${COMMAND_ARGS_ANCHOR}${globPatternToRegex(pattern)}`;
10142
+ logger.warn(
10143
+ `Gemini CLI does not support glob metacharacters inside a bash command prefix; emitting argsPattern for rule "${toolName}: ${pattern}".`
10144
+ );
10145
+ return;
10146
+ }
10147
+ rule.commandPrefix = trailingWildcardStripped;
10148
+ }
10149
+ function hasGlobMetacharacter(pattern) {
10150
+ return /[*?[\]]/.test(pattern);
10151
+ }
10152
+ function priorityForDecision(decision) {
10153
+ if (decision === "deny") return PRIORITY_DENY;
10154
+ if (decision === "ask_user") return PRIORITY_ASK;
10155
+ return PRIORITY_ALLOW;
10156
+ }
10157
+ function mapToGeminicliDecision(action) {
10158
+ if (action === "ask") {
10159
+ return "ask_user";
10160
+ }
10161
+ return action;
10162
+ }
10163
+ function mapFromGeminicliDecision(decision) {
10164
+ if (decision === "allow") return "allow";
10165
+ if (decision === "deny") return "deny";
10166
+ if (decision === "ask_user") return "ask";
10167
+ return null;
10168
+ }
10169
+ function globPatternToRegex(pattern) {
10170
+ let regex = "";
10171
+ let i = 0;
10172
+ while (i < pattern.length) {
10173
+ const char = pattern[i];
10174
+ if (char === void 0) {
10175
+ break;
10176
+ }
10177
+ if (char === "*" && pattern[i + 1] === "*") {
10178
+ regex += DOUBLE_STAR_REGEX;
10179
+ i += 2;
10180
+ continue;
10181
+ }
10182
+ if (char === "*") {
10183
+ regex += SINGLE_STAR_REGEX;
10184
+ i += 1;
10185
+ continue;
10186
+ }
10187
+ if (char === "?") {
10188
+ regex += SINGLE_CHAR_REGEX;
10189
+ i += 1;
10190
+ continue;
10191
+ }
10192
+ if (char === "[") {
10193
+ regex += escapeRegexChar(char);
10194
+ i += 1;
10195
+ continue;
10196
+ }
10197
+ if (char === "]") {
10198
+ regex += escapeRegexChar(char);
10199
+ i += 1;
10200
+ continue;
10201
+ }
10202
+ if (isRegexMetacharacter(char)) {
10203
+ regex += `\\${char}`;
10204
+ i += 1;
10205
+ continue;
10206
+ }
10207
+ regex += char;
10208
+ i += 1;
10209
+ }
10210
+ return regex;
10211
+ }
10212
+ function escapeRegexChar(char) {
10213
+ return `\\${char}`;
10214
+ }
10215
+ function isRegexMetacharacter(char) {
10216
+ return /[.+^${}()|\\]/.test(char);
10217
+ }
10218
+ function regexToGlobPattern(regex) {
10219
+ let source = regex;
10220
+ if (source.endsWith(VALUE_END_ANCHOR)) {
10221
+ source = source.slice(0, -VALUE_END_ANCHOR.length);
10222
+ }
10223
+ let glob = "";
10224
+ let i = 0;
10225
+ while (i < source.length) {
10226
+ if (source.startsWith(DOUBLE_STAR_REGEX, i)) {
10227
+ glob += "**";
10228
+ i += DOUBLE_STAR_REGEX.length;
10229
+ continue;
10230
+ }
10231
+ if (source.startsWith(LEGACY_DOUBLE_STAR_REGEX, i)) {
10232
+ glob += "**";
10233
+ i += LEGACY_DOUBLE_STAR_REGEX.length;
10234
+ continue;
10235
+ }
10236
+ if (source.startsWith(SINGLE_STAR_REGEX, i)) {
10237
+ glob += "*";
10238
+ i += SINGLE_STAR_REGEX.length;
10239
+ continue;
10240
+ }
10241
+ if (source.startsWith(LEGACY_SINGLE_STAR_REGEX, i)) {
10242
+ glob += "*";
10243
+ i += LEGACY_SINGLE_STAR_REGEX.length;
10244
+ continue;
10245
+ }
10246
+ if (source.startsWith(SINGLE_CHAR_REGEX, i)) {
10247
+ glob += "?";
10248
+ i += SINGLE_CHAR_REGEX.length;
10249
+ continue;
10250
+ }
10251
+ const char = source[i];
10252
+ if (char === "\\") {
10253
+ const escaped = source[i + 1];
10254
+ if (escaped !== void 0) {
10255
+ glob += escaped;
10256
+ i += 2;
10257
+ continue;
10258
+ }
10259
+ }
10260
+ glob += char ?? "";
10261
+ i += 1;
10262
+ }
10263
+ return glob;
10264
+ }
10265
+ var GeminicliPolicyRuleSchema = z29.looseObject({
10266
+ toolName: z29.string(),
10267
+ decision: z29.optional(z29.unknown()),
10268
+ commandPrefix: z29.optional(z29.string()),
10269
+ argsPattern: z29.optional(z29.string())
10270
+ });
10271
+ var GeminicliPolicyFileSchema = z29.looseObject({
10272
+ rule: z29.optional(z29.array(z29.looseObject({})))
10273
+ });
10274
+ function extractRules(parsed, logger) {
10275
+ const parsedFile = GeminicliPolicyFileSchema.safeParse(parsed);
10276
+ if (!parsedFile.success || !parsedFile.data.rule) {
10277
+ return [];
10278
+ }
10279
+ const rules = [];
10280
+ for (const [index, entry] of parsedFile.data.rule.entries()) {
10281
+ const result = GeminicliPolicyRuleSchema.safeParse(entry);
10282
+ if (result.success) {
10283
+ rules.push(result.data);
10284
+ continue;
10285
+ }
10286
+ logger.warn(
10287
+ `Skipping malformed Gemini CLI policy rule at index ${index}: ${formatError(result.error)}`
10288
+ );
10289
+ }
10290
+ return rules;
10291
+ }
10292
+ function extractPattern(rule) {
10293
+ if (rule.toolName === "run_shell_command") {
10294
+ if (rule.argsPattern) {
10295
+ const stripped = rule.argsPattern.startsWith(COMMAND_ARGS_ANCHOR) ? rule.argsPattern.slice(COMMAND_ARGS_ANCHOR.length) : rule.argsPattern;
10296
+ return regexToGlobPattern(stripped);
10297
+ }
10298
+ if (!rule.commandPrefix) return "*";
10299
+ return rule.commandPrefix.endsWith(" *") || rule.commandPrefix.endsWith("*") ? rule.commandPrefix : `${rule.commandPrefix} *`;
10300
+ }
10301
+ if (!rule.argsPattern) return "*";
10302
+ const regex = rule.argsPattern.startsWith('"') ? rule.argsPattern.slice(1) : rule.argsPattern;
10303
+ return regexToGlobPattern(regex);
9926
10304
  }
9927
10305
 
9928
10306
  // src/features/permissions/kiro-permissions.ts
@@ -15180,7 +15558,7 @@ var ClaudecodeSubagent = class _ClaudecodeSubagent extends ToolSubagent {
15180
15558
 
15181
15559
  // src/features/subagents/codexcli-subagent.ts
15182
15560
  import { join as join101 } from "path";
15183
- import * as smolToml5 from "smol-toml";
15561
+ import * as smolToml6 from "smol-toml";
15184
15562
  import { z as z58 } from "zod/mini";
15185
15563
  var CodexCliSubagentTomlSchema = z58.looseObject({
15186
15564
  name: z58.string(),
@@ -15192,13 +15570,13 @@ var CodexCliSubagentTomlSchema = z58.looseObject({
15192
15570
  });
15193
15571
  function stringifyCodexCliSubagentToml(tomlObj) {
15194
15572
  const { developer_instructions, ...restFields } = tomlObj;
15195
- const restToml = smolToml5.stringify(restFields).trimEnd();
15573
+ const restToml = smolToml6.stringify(restFields).trimEnd();
15196
15574
  if (developer_instructions === void 0) {
15197
15575
  return restToml;
15198
15576
  }
15199
- const developerInstructionsToml = developer_instructions.includes("\n") ? developer_instructions.includes("'''") ? smolToml5.stringify({ developer_instructions }).trimEnd() : `developer_instructions = '''
15577
+ const developerInstructionsToml = developer_instructions.includes("\n") ? developer_instructions.includes("'''") ? smolToml6.stringify({ developer_instructions }).trimEnd() : `developer_instructions = '''
15200
15578
  ${developer_instructions}
15201
- '''` : smolToml5.stringify({ developer_instructions }).trimEnd();
15579
+ '''` : smolToml6.stringify({ developer_instructions }).trimEnd();
15202
15580
  return [restToml, developerInstructionsToml].filter((value) => value.length > 0).join("\n");
15203
15581
  }
15204
15582
  var CodexCliSubagent = class _CodexCliSubagent extends ToolSubagent {
@@ -15206,7 +15584,7 @@ var CodexCliSubagent = class _CodexCliSubagent extends ToolSubagent {
15206
15584
  constructor({ body, ...rest }) {
15207
15585
  if (rest.validate !== false) {
15208
15586
  try {
15209
- const parsed = smolToml5.parse(body);
15587
+ const parsed = smolToml6.parse(body);
15210
15588
  CodexCliSubagentTomlSchema.parse(parsed);
15211
15589
  } catch (error) {
15212
15590
  throw new Error(
@@ -15231,7 +15609,7 @@ var CodexCliSubagent = class _CodexCliSubagent extends ToolSubagent {
15231
15609
  toRulesyncSubagent() {
15232
15610
  let parsed;
15233
15611
  try {
15234
- parsed = CodexCliSubagentTomlSchema.parse(smolToml5.parse(this.body));
15612
+ parsed = CodexCliSubagentTomlSchema.parse(smolToml6.parse(this.body));
15235
15613
  } catch (error) {
15236
15614
  throw new Error(
15237
15615
  `Failed to parse TOML in ${join101(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${error instanceof Error ? error.message : String(error)}`,
@@ -15292,7 +15670,7 @@ var CodexCliSubagent = class _CodexCliSubagent extends ToolSubagent {
15292
15670
  }
15293
15671
  validate() {
15294
15672
  try {
15295
- const parsed = smolToml5.parse(this.body);
15673
+ const parsed = smolToml6.parse(this.body);
15296
15674
  CodexCliSubagentTomlSchema.parse(parsed);
15297
15675
  return { success: true, error: null };
15298
15676
  } catch (error) {
@@ -21616,176 +21994,6 @@ async function importPermissionsCore(params) {
21616
21994
  return writtenCount;
21617
21995
  }
21618
21996
 
21619
- // src/types/json-output.ts
21620
- var ErrorCodes = {
21621
- CONFIG_NOT_FOUND: "CONFIG_NOT_FOUND",
21622
- RULESYNC_DIR_NOT_FOUND: "RULESYNC_DIR_NOT_FOUND",
21623
- INVALID_TARGET: "INVALID_TARGET",
21624
- FETCH_FAILED: "FETCH_FAILED",
21625
- WRITE_FAILED: "WRITE_FAILED",
21626
- VALIDATION_FAILED: "VALIDATION_FAILED",
21627
- GENERATION_FAILED: "GENERATION_FAILED",
21628
- IMPORT_FAILED: "IMPORT_FAILED",
21629
- INSTALL_FAILED: "INSTALL_FAILED",
21630
- UPDATE_FAILED: "UPDATE_FAILED",
21631
- GITIGNORE_FAILED: "GITIGNORE_FAILED",
21632
- INIT_FAILED: "INIT_FAILED",
21633
- MCP_FAILED: "MCP_FAILED",
21634
- UNKNOWN_ERROR: "UNKNOWN_ERROR"
21635
- };
21636
- var CLIError = class extends Error {
21637
- constructor(message, code = ErrorCodes.UNKNOWN_ERROR, exitCode = 1) {
21638
- super(message);
21639
- this.code = code;
21640
- this.exitCode = exitCode;
21641
- this.name = "CLIError";
21642
- }
21643
- };
21644
-
21645
- // src/utils/logger.ts
21646
- var BaseLogger = class {
21647
- _verbose = false;
21648
- _silent = false;
21649
- constructor({ verbose = false, silent = false } = {}) {
21650
- this._silent = silent;
21651
- this._verbose = verbose && !silent;
21652
- }
21653
- get verbose() {
21654
- return this._verbose;
21655
- }
21656
- get silent() {
21657
- return this._silent;
21658
- }
21659
- configure({ verbose, silent }) {
21660
- if (verbose && silent) {
21661
- this._silent = false;
21662
- if (!isEnvTest()) {
21663
- this.onConflictingFlags();
21664
- }
21665
- }
21666
- this._silent = silent;
21667
- this._verbose = verbose && !silent;
21668
- }
21669
- onConflictingFlags() {
21670
- console.warn("Both --verbose and --silent specified; --silent takes precedence");
21671
- }
21672
- };
21673
- var ConsoleLogger = class extends BaseLogger {
21674
- isSuppressed() {
21675
- return isEnvTest() || this._silent;
21676
- }
21677
- get jsonMode() {
21678
- return false;
21679
- }
21680
- captureData(_key, _value) {
21681
- }
21682
- getJsonData() {
21683
- return {};
21684
- }
21685
- outputJson(_success, _error) {
21686
- }
21687
- info(message, ...args) {
21688
- if (this.isSuppressed()) return;
21689
- console.log(message, ...args);
21690
- }
21691
- success(message, ...args) {
21692
- if (this.isSuppressed()) return;
21693
- console.log(message, ...args);
21694
- }
21695
- warn(message, ...args) {
21696
- if (this.isSuppressed()) return;
21697
- console.warn(message, ...args);
21698
- }
21699
- // Errors are always emitted, even in silent mode
21700
- error(message, _code, ...args) {
21701
- if (isEnvTest()) return;
21702
- const errorMessage = message instanceof Error ? message.message : message;
21703
- console.error(errorMessage, ...args);
21704
- }
21705
- debug(message, ...args) {
21706
- if (!this._verbose || this.isSuppressed()) return;
21707
- console.log(message, ...args);
21708
- }
21709
- };
21710
- var JsonLogger = class extends BaseLogger {
21711
- _jsonOutputDone = false;
21712
- _jsonData = {};
21713
- _commandName;
21714
- _version;
21715
- constructor({
21716
- command,
21717
- version,
21718
- verbose = false,
21719
- silent = false
21720
- }) {
21721
- super({ verbose, silent });
21722
- this._commandName = command;
21723
- this._version = version;
21724
- }
21725
- // Suppress raw console.warn in JSON mode to avoid non-JSON text on stderr
21726
- onConflictingFlags() {
21727
- }
21728
- get jsonMode() {
21729
- return true;
21730
- }
21731
- captureData(key, value) {
21732
- this._jsonData[key] = value;
21733
- }
21734
- getJsonData() {
21735
- return { ...this._jsonData };
21736
- }
21737
- outputJson(success, error) {
21738
- if (this._jsonOutputDone) return;
21739
- this._jsonOutputDone = true;
21740
- const output = {
21741
- success,
21742
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
21743
- command: this._commandName,
21744
- version: this._version
21745
- };
21746
- if (success) {
21747
- output.data = this._jsonData;
21748
- } else if (error) {
21749
- output.error = {
21750
- code: error.code,
21751
- message: error.message
21752
- };
21753
- if (error.details) {
21754
- output.error.details = error.details;
21755
- }
21756
- if (error.stack) {
21757
- output.error.stack = error.stack;
21758
- }
21759
- }
21760
- const jsonStr = JSON.stringify(output, null, 2);
21761
- if (success) {
21762
- console.log(jsonStr);
21763
- } else {
21764
- console.error(jsonStr);
21765
- }
21766
- }
21767
- info(_message, ..._args) {
21768
- }
21769
- success(_message, ..._args) {
21770
- }
21771
- warn(_message, ..._args) {
21772
- }
21773
- error(message, code, ..._args) {
21774
- if (isEnvTest()) return;
21775
- const errorMessage = message instanceof Error ? message.message : message;
21776
- const errorInfo = {
21777
- code: code || ErrorCodes.UNKNOWN_ERROR,
21778
- message: errorMessage
21779
- };
21780
- if (this._verbose && message instanceof Error && message.stack) {
21781
- errorInfo.stack = message.stack;
21782
- }
21783
- this.outputJson(false, errorInfo);
21784
- }
21785
- debug(_message, ..._args) {
21786
- }
21787
- };
21788
-
21789
21997
  export {
21790
21998
  RULESYNC_CONFIG_RELATIVE_FILE_PATH,
21791
21999
  RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH,
@@ -21843,6 +22051,10 @@ export {
21843
22051
  IgnoreProcessor,
21844
22052
  RulesyncMcp,
21845
22053
  McpProcessor,
22054
+ ErrorCodes,
22055
+ CLIError,
22056
+ ConsoleLogger,
22057
+ JsonLogger,
21846
22058
  SKILL_FILE_NAME,
21847
22059
  RulesyncSkillFrontmatterSchema,
21848
22060
  RulesyncSkill,
@@ -21856,9 +22068,5 @@ export {
21856
22068
  RulesProcessor,
21857
22069
  checkRulesyncDirExists,
21858
22070
  generate,
21859
- importFromTool,
21860
- ErrorCodes,
21861
- CLIError,
21862
- ConsoleLogger,
21863
- JsonLogger
22071
+ importFromTool
21864
22072
  };