rulesync 35.0.0 → 36.0.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.
@@ -677,6 +677,7 @@ const commandsProcessorToolTargetTuple = [
677
677
  "hermesagent",
678
678
  "junie",
679
679
  "kilo",
680
+ "kimi-code",
680
681
  "kimi-code-plugin",
681
682
  "kiro",
682
683
  "kiro-cli",
@@ -8151,7 +8152,8 @@ const CursorPermissionsOverrideSchema = z.looseObject({
8151
8152
  * autonomy/sandbox controls with no canonical permission category — under
8152
8153
  * `tools` (`approvalMode` = plan/default/auto-edit/auto/yolo, `autoAccept`,
8153
8154
  * `sandbox`, `sandboxImage`, `disabled`, `visible`, `eager`, `listDirectory`,
8154
- * `todoWrite`, `workflowsEnabled`, `workflowNameOnly`, `executionSandbox`) and
8155
+ * `todoWrite`, `codeModeOnly`, `freeform`, `workflowsEnabled`, `workflowNameOnly`,
8156
+ * `executionSandbox`) and
8155
8157
  * `security` (`folderTrust`, `allowedHttpHookUrls`, `allowPrivateNetworkHooks`,
8156
8158
  * `allowedInsecureVoiceBaseUrls`) and `skills` (the skill kill-switches
8157
8159
  * `disabled`, `defaultDisabled`, `enabled`, `disabledLevels`, each a list Qwen
@@ -8170,7 +8172,8 @@ const CursorPermissionsOverrideSchema = z.looseObject({
8170
8172
  * changes what the file said is reported in either scope, naming what that key
8171
8173
  * decides there — the autonomy and containment
8172
8174
  * controls (`approvalMode`, `autoAccept`, `sandbox`, `sandboxImage`), the
8173
- * registry controls (`disabled`, `visible`, `listDirectory`, `todoWrite`, and `eager`, which
8175
+ * registry controls (`disabled`, `visible`, `listDirectory`, `todoWrite`,
8176
+ * `codeModeOnly`, `freeform`, and `eager`, which
8174
8177
  * demotes an omitted tool to deferred rather than removing it, replacing rather
8175
8178
  * than merging with the list a higher scope set), the Auto Mode
8176
8179
  * classifier config, the skill kill-switches, and, because these groups are
@@ -10863,13 +10866,15 @@ const RulesyncSkillFrontmatterSchema = z.looseObject({
10863
10866
  "allowed-tools": z.optional(z.array(z.string())),
10864
10867
  license: z.optional(z.unknown()),
10865
10868
  compatibility: z.optional(z.unknown()),
10866
- metadata: z.optional(z.unknown())
10869
+ metadata: z.optional(z.unknown()),
10870
+ "disable-model-invocation": z.optional(z.boolean())
10867
10871
  })),
10868
10872
  mimocode: z.optional(z.looseObject({
10869
10873
  "allowed-tools": z.optional(z.array(z.string())),
10870
10874
  license: z.optional(z.unknown()),
10871
10875
  compatibility: z.optional(z.unknown()),
10872
- metadata: z.optional(z.unknown())
10876
+ metadata: z.optional(z.unknown()),
10877
+ "disable-model-invocation": z.optional(z.boolean())
10873
10878
  })),
10874
10879
  kilo: z.optional(z.looseObject({
10875
10880
  "allowed-tools": z.optional(z.array(z.string())),
@@ -11322,7 +11327,7 @@ async function getLocalSkillDirNames(sourceTree) {
11322
11327
  * The rulesync skill frontmatter exposes a root-level `disable-model-invocation`
11323
11328
  * default that applies to every tool supporting the flag (claudecode, codebuddy, copilot,
11324
11329
  * copilotcli, crush, cursor, zed, pi, omp, qwencode, vibe, grokcli, factorydroid, dsh, reasonix,
11325
- * commandcode, lettacode). Each tool's own section may override that
11330
+ * commandcode, lettacode, opencode, mimocode). Each tool's own section may override that
11326
11331
  * default with a per-target value. A defined section value (including `false`)
11327
11332
  * always wins over the root default.
11328
11333
  *
@@ -13521,8 +13526,8 @@ function diffSharedConfigOwnedKeys({ fileKey, existingContent, newContent }) {
13521
13526
  return operations;
13522
13527
  }
13523
13528
  const READ_TOOL_NAME = "Read";
13524
- const isReadDenyEntry = (entry) => entry.startsWith(`${READ_TOOL_NAME}(`) && entry.endsWith(")");
13525
- const buildReadDenyEntry = (pattern) => `${READ_TOOL_NAME}(${pattern})`;
13529
+ const isReadDenyEntry = (entry, readToolName = READ_TOOL_NAME) => entry.startsWith(`${readToolName}(`) && entry.endsWith(")");
13530
+ const buildReadDenyEntry = (pattern, readToolName = READ_TOOL_NAME) => `${readToolName}(${pattern})`;
13526
13531
  const parsePermissionsBlock = (settings) => {
13527
13532
  const permissions = settings.permissions ?? {};
13528
13533
  return {
@@ -13546,9 +13551,9 @@ const withPermissions = (settings, next) => {
13546
13551
  };
13547
13552
  };
13548
13553
  const applyIgnoreReadDenies = (params) => {
13549
- const { settings, readDenies } = params;
13554
+ const { settings, readDenies, readToolName = READ_TOOL_NAME } = params;
13550
13555
  const current = parsePermissionsBlock(settings);
13551
- const preservedDeny = current.deny.filter((entry) => !isReadDenyEntry(entry) || readDenies.includes(entry));
13556
+ const preservedDeny = current.deny.filter((entry) => !isReadDenyEntry(entry, readToolName) || readDenies.includes(entry));
13552
13557
  return withPermissions(settings, {
13553
13558
  allow: current.allow,
13554
13559
  ask: current.ask,
@@ -17875,6 +17880,7 @@ const ANTIGRAVITY_PLUGIN_AGENTS_DIR = "agents";
17875
17880
  const ANTIGRAVITY_PLUGIN_MCP_FILE_NAME = "mcp_config.json";
17876
17881
  const ANTIGRAVITY_PLUGIN_HOOKS_FILE_NAME = "hooks.json";
17877
17882
  const ANTIGRAVITY_PLUGIN_MANIFEST_FILE_NAME = "plugin.json";
17883
+ const ANTIGRAVITY_PLUGIN_MANIFEST_FOLDERS = [".antigravity-plugin", ".gemini-plugin"];
17878
17884
  const AUGMENTCODE_PLUGIN_RULES_DIR = "rules";
17879
17885
  const AUGMENTCODE_PLUGIN_COMMANDS_DIR = "commands";
17880
17886
  const AUGMENTCODE_PLUGIN_AGENTS_DIR = "agents";
@@ -19521,9 +19527,36 @@ function commandSlug(relativeFilePath) {
19521
19527
  * skill. Once the command is removed from every source tree's `commands/`
19522
19528
  * directory, the directory stops matching and the skills feature cleans
19523
19529
  * it up as a regular orphan.
19530
+ *
19531
+ * When `toolTarget` is given, only commands whose `targets` include it (or
19532
+ * `*`) count: a command aimed at other tools never reaches this tool's skills
19533
+ * tree, so it must not shadow a same-named skill there. Commands that cannot
19534
+ * be parsed are skipped, as the commands feature reports them itself.
19524
19535
  */
19525
- async function rulesyncCommandSlugExists({ inputRoots, dirName }) {
19526
- return (await Promise.all(inputRoots.map((root) => findFilesByGlobs("**/*.md", { cwd: join(root, COMMANDS_FEATURE_SUBDIR) })))).flat().some((filePath) => commandSlug(basename(filePath)) === dirName);
19536
+ async function rulesyncCommandSlugExists({ inputRoots, dirName, toolTarget }) {
19537
+ const matching = (await Promise.all(inputRoots.map(async (root) => {
19538
+ const commandsDirPath = join(root, COMMANDS_FEATURE_SUBDIR);
19539
+ return (await findFilesByGlobs("**/*.md", { cwd: commandsDirPath })).map((filePath) => ({
19540
+ root,
19541
+ commandsDirPath,
19542
+ filePath
19543
+ }));
19544
+ }))).flat().filter(({ filePath }) => commandSlug(basename(filePath)) === dirName);
19545
+ if (toolTarget === void 0) return matching.length > 0;
19546
+ for (const { root, commandsDirPath, filePath } of matching) {
19547
+ let targets;
19548
+ try {
19549
+ targets = (await RulesyncCommand.fromFile({
19550
+ outputRoot: root,
19551
+ relativeDirPath: COMMANDS_FEATURE_SUBDIR,
19552
+ relativeFilePath: relative(commandsDirPath, filePath)
19553
+ })).getFrontmatter().targets;
19554
+ } catch {
19555
+ continue;
19556
+ }
19557
+ if (targets.includes("*") || targets.includes(toolTarget)) return true;
19558
+ }
19559
+ return false;
19527
19560
  }
19528
19561
  //#endregion
19529
19562
  //#region src/features/commands/devin-command.ts
@@ -19541,7 +19574,7 @@ const DEVIN_COMMAND_SECTION_KEYS = [
19541
19574
  "permissions",
19542
19575
  "triggers"
19543
19576
  ];
19544
- function commandSkillContent$1(rulesyncCommand) {
19577
+ function commandSkillContent$2(rulesyncCommand) {
19545
19578
  const slug = commandSlug(rulesyncCommand.getRelativeFilePath());
19546
19579
  const frontmatter = rulesyncCommand.getFrontmatter();
19547
19580
  const description = frontmatter.description ?? `${slug} command`;
@@ -19615,7 +19648,7 @@ var DevinCommand = class DevinCommand extends ToolCommand {
19615
19648
  relativeDirPath: paths.relativeDirPath,
19616
19649
  relativeFilePath: SKILL_FILE_NAME,
19617
19650
  slug: commandSlug(rulesyncCommand.getRelativeFilePath()),
19618
- fileContent: commandSkillContent$1(rulesyncCommand)
19651
+ fileContent: commandSkillContent$2(rulesyncCommand)
19619
19652
  });
19620
19653
  }
19621
19654
  getFileContent() {
@@ -21435,6 +21468,180 @@ var KiloCommand = class KiloCommand extends ToolCommand {
21435
21468
  }
21436
21469
  };
21437
21470
  //#endregion
21471
+ //#region src/constants/kimi-code-paths.ts
21472
+ const KIMI_CODE_DIR = ".kimi-code";
21473
+ const KIMI_CODE_RULE_FILE_NAME = "AGENTS.md";
21474
+ const KIMI_CODE_MCP_FILE_NAME = "mcp.json";
21475
+ const KIMI_CODE_CONFIG_FILE_NAME = "config.toml";
21476
+ const KIMI_CODE_SKILLS_DIR_NAME = "skills";
21477
+ const KIMI_CODE_AGENTS_DIR_NAME = "agents";
21478
+ const KIMI_CODE_SKILLS_DIR_PATH = join(KIMI_CODE_DIR, KIMI_CODE_SKILLS_DIR_NAME);
21479
+ join(KIMI_CODE_DIR, KIMI_CODE_AGENTS_DIR_NAME);
21480
+ const KIMI_CODE_SHARED_SKILLS_DIR_PATH = join(".agents", "skills");
21481
+ const KIMI_CODE_SHARED_AGENTS_DIR_PATH = join(".agents", "agents");
21482
+ //#endregion
21483
+ //#region src/utils/kimi-code.ts
21484
+ function getKimiCodeHome() {
21485
+ const configuredHome = process.env.KIMI_CODE_HOME?.trim();
21486
+ return configuredHome ? resolve(configuredHome) : void 0;
21487
+ }
21488
+ function getKimiCodeRelativeDirPath({ global, relativeDirPath = "." }) {
21489
+ return global && getKimiCodeHome() ? relativeDirPath : join(KIMI_CODE_DIR, relativeDirPath);
21490
+ }
21491
+ /**
21492
+ * Both spellings the shared user `config.toml` can take: under `.kimi-code/`,
21493
+ * or at the root of `KIMI_CODE_HOME` when that override names the profile dir.
21494
+ * Declared unconditionally so the derived shared-file keys — and the drift
21495
+ * guards checked against them — do not depend on the ambient environment.
21496
+ */
21497
+ function getKimiCodeSharedConfigWritePaths() {
21498
+ return [{
21499
+ relativeDirPath: KIMI_CODE_DIR,
21500
+ relativeFilePath: KIMI_CODE_CONFIG_FILE_NAME
21501
+ }, {
21502
+ relativeDirPath: ".",
21503
+ relativeFilePath: KIMI_CODE_CONFIG_FILE_NAME
21504
+ }];
21505
+ }
21506
+ /**
21507
+ * The `SHARED_CONFIG_OWNERSHIP` key of the `config.toml` actually being written.
21508
+ * Both spellings carry the same declaration, but passing the key of the file
21509
+ * being written keeps the write path and the drift guards on the same entry.
21510
+ */
21511
+ function getKimiCodeConfigSharedFileKey({ global }) {
21512
+ return sharedConfigFileKey({
21513
+ relativeDirPath: getKimiCodeRelativeDirPath({ global }),
21514
+ relativeFilePath: KIMI_CODE_CONFIG_FILE_NAME
21515
+ });
21516
+ }
21517
+ function getKimiCodeRulesyncOutputRoot({ nativeOutputRoot, global }) {
21518
+ return getToolRulesyncOutputRoot({
21519
+ nativeOutputRoot,
21520
+ global,
21521
+ toolHome: getKimiCodeHome
21522
+ });
21523
+ }
21524
+ //#endregion
21525
+ //#region src/features/commands/kimi-code-command.ts
21526
+ /**
21527
+ * Kimi Code SKILL.md frontmatter keys a command may author through its
21528
+ * `kimi-code:` section, each with the alias spellings Kimi also accepts. An
21529
+ * alias is normalized to the canonical key; the canonical spelling wins when
21530
+ * both are present.
21531
+ * @see https://moonshotai.github.io/kimi-code/en/customization/skills.html
21532
+ */
21533
+ const KIMI_CODE_COMMAND_SECTION_KEYS = [
21534
+ {
21535
+ key: "type",
21536
+ aliases: []
21537
+ },
21538
+ {
21539
+ key: "whenToUse",
21540
+ aliases: ["when-to-use", "when_to_use"]
21541
+ },
21542
+ {
21543
+ key: "disableModelInvocation",
21544
+ aliases: ["disable-model-invocation", "disable_model_invocation"]
21545
+ },
21546
+ {
21547
+ key: "arguments",
21548
+ aliases: []
21549
+ }
21550
+ ];
21551
+ function pickKimiCodeSectionKeys(section) {
21552
+ const picked = {};
21553
+ for (const { key, aliases } of KIMI_CODE_COMMAND_SECTION_KEYS) {
21554
+ const spelling = [key, ...aliases].find((candidate) => section[candidate] !== void 0);
21555
+ if (spelling !== void 0) picked[key] = section[spelling];
21556
+ }
21557
+ return picked;
21558
+ }
21559
+ function commandSkillContent$1(rulesyncCommand) {
21560
+ const slug = commandSlug(rulesyncCommand.getRelativeFilePath());
21561
+ const frontmatter = rulesyncCommand.getFrontmatter();
21562
+ const description = frontmatter.description?.trim() || `${slug} command`;
21563
+ return stringifyFrontmatter(rulesyncCommand.getBody().trim(), {
21564
+ name: slug,
21565
+ description,
21566
+ disableModelInvocation: true,
21567
+ ...pickKimiCodeSectionKeys(frontmatter["kimi-code"] ?? {})
21568
+ });
21569
+ }
21570
+ /**
21571
+ * Represents a Kimi Code slash command, emitted as a Kimi Code Skill.
21572
+ *
21573
+ * Kimi Code has no standalone custom-command directory outside plugins: every
21574
+ * external Skill is registered as a slash command (`/skill:<name>`, with a
21575
+ * `/<name>` shorthand when the bare name is free), and the Skill body supports
21576
+ * `$ARGUMENTS`, positional `$0` / `$1`, and named `$<name>` placeholders.
21577
+ * Commands are therefore emitted onto the native skills surface, one
21578
+ * `SKILL.md` per command: `.kimi-code/skills/<slug>/SKILL.md` (project) and
21579
+ * `~/.kimi-code/skills/<slug>/SKILL.md` (global; `$KIMI_CODE_HOME/skills/`
21580
+ * when `KIMI_CODE_HOME` is set). Directory-form `SKILL.md` requires both
21581
+ * `name` and `description`, so both are always written.
21582
+ *
21583
+ * Import and deletion are intentionally no-ops for this target: the skills
21584
+ * feature owns the `.kimi-code/skills/` tree, so importing it as commands
21585
+ * would double-import every skill (mirrors the Devin, Warp and Hermes Agent
21586
+ * commands targets).
21587
+ *
21588
+ * @see https://moonshotai.github.io/kimi-code/en/customization/skills.html
21589
+ * @see https://moonshotai.github.io/kimi-code/en/reference/slash-commands.html
21590
+ */
21591
+ var KimiCodeCommand = class KimiCodeCommand extends ToolCommand {
21592
+ static isTargetedByRulesyncCommand(rulesyncCommand) {
21593
+ return this.isTargetedByRulesyncCommandDefault({
21594
+ rulesyncCommand,
21595
+ toolTarget: "kimi-code"
21596
+ });
21597
+ }
21598
+ static getSettablePaths({ global = false } = {}) {
21599
+ return { relativeDirPath: getKimiCodeRelativeDirPath({
21600
+ global,
21601
+ relativeDirPath: KIMI_CODE_SKILLS_DIR_NAME
21602
+ }) };
21603
+ }
21604
+ constructor({ slug, ...params }) {
21605
+ super({
21606
+ ...params,
21607
+ ...slug !== void 0 && {
21608
+ relativeDirPath: join(params.relativeDirPath, slug),
21609
+ relativeFilePath: "SKILL.md"
21610
+ }
21611
+ });
21612
+ }
21613
+ validate() {
21614
+ return {
21615
+ success: true,
21616
+ error: null
21617
+ };
21618
+ }
21619
+ toRulesyncCommand() {
21620
+ const slug = basename(dirname(this.getRelativePathFromCwd()));
21621
+ const { frontmatter, body } = parseFrontmatter(this.getFileContent(), this.getFilePath());
21622
+ const description = typeof frontmatter.description === "string" ? frontmatter.description : void 0;
21623
+ return new RulesyncCommand({
21624
+ relativeDirPath: RULESYNC_COMMANDS_RELATIVE_DIR_PATH,
21625
+ relativeFilePath: `${slug}.md`,
21626
+ frontmatter: { description },
21627
+ body: body.trimStart()
21628
+ });
21629
+ }
21630
+ static fromRulesyncCommand({ outputRoot, rulesyncCommand, global = false }) {
21631
+ const paths = KimiCodeCommand.getSettablePaths({ global });
21632
+ return new KimiCodeCommand({
21633
+ outputRoot,
21634
+ relativeDirPath: paths.relativeDirPath,
21635
+ relativeFilePath: SKILL_FILE_NAME,
21636
+ slug: commandSlug(rulesyncCommand.getRelativeFilePath()),
21637
+ fileContent: commandSkillContent$1(rulesyncCommand)
21638
+ });
21639
+ }
21640
+ getFileContent() {
21641
+ return this.fileContent;
21642
+ }
21643
+ };
21644
+ //#endregion
21438
21645
  //#region src/features/commands/kimi-code-plugin-command.ts
21439
21646
  /**
21440
21647
  * Kimi Code reads only `name` and `description` from a plugin command. The
@@ -21589,6 +21796,14 @@ const KIRO_IDE_HOOKS_FILE_NAME = "rulesync.json";
21589
21796
  const KIRO_MCP_FILE_NAME = "mcp.json";
21590
21797
  const KIRO_IGNORE_FILE_NAME = ".kiroignore";
21591
21798
  const KIRO_GLOBAL_IGNORE_FILE_NAME = "kiroignore";
21799
+ /**
21800
+ * Kiro's capability-based permission rules (IDE 1.0, CLI V3). The user-level
21801
+ * file lives at `~/.kiro/settings/permissions.yaml`; the workspace-level file
21802
+ * sits under a hash-derived `~/.kiro/workspace-roots/<hash>/` directory outside
21803
+ * the repository, so only the user-level file is generated.
21804
+ * @see https://kiro.dev/docs/permissions/
21805
+ */
21806
+ const KIRO_PERMISSIONS_FILE_NAME = "permissions.yaml";
21592
21807
  //#endregion
21593
21808
  //#region src/features/commands/kiro-command.ts
21594
21809
  var KiroCommand = class KiroCommand extends ToolCommand {
@@ -23631,6 +23846,11 @@ const TraeCommandFrontmatterSchema = z.looseObject({
23631
23846
  name: z.optional(z.string())
23632
23847
  });
23633
23848
  /**
23849
+ * Trae reads project commands only up to three directory levels below
23850
+ * `.trae/commands/`; a command file nested deeper is never picked up.
23851
+ */
23852
+ const TRAE_MAX_PROJECT_COMMAND_DIR_DEPTH = 3;
23853
+ /**
23634
23854
  * Custom command for Trae.
23635
23855
  *
23636
23856
  * Trae reads Markdown command files under `.trae/commands/` (project, up to
@@ -23690,10 +23910,11 @@ var TraeCommand = class TraeCommand extends ToolCommand {
23690
23910
  }
23691
23911
  static fromRulesyncCommand({ outputRoot = process.cwd(), rulesyncCommand, validate = true, global = false }) {
23692
23912
  const rulesyncFrontmatter = rulesyncCommand.getFrontmatter();
23913
+ const { name: _name, ...traeFields } = rulesyncFrontmatter.trae ?? {};
23693
23914
  const traeFrontmatter = {
23694
- name: basename(rulesyncCommand.getRelativeFilePath(), ".md"),
23915
+ name: this.deriveName(rulesyncCommand),
23695
23916
  description: rulesyncFrontmatter.description,
23696
- ...rulesyncFrontmatter.trae
23917
+ ...traeFields
23697
23918
  };
23698
23919
  return new TraeCommand({
23699
23920
  outputRoot,
@@ -23719,6 +23940,36 @@ var TraeCommand = class TraeCommand extends ToolCommand {
23719
23940
  error: /* @__PURE__ */ new Error(`Invalid frontmatter in ${join(this.relativeDirPath, this.relativeFilePath)}: ${formatError(result.error)}`)
23720
23941
  };
23721
23942
  }
23943
+ /**
23944
+ * Warn about commands Trae will not load as written: Trae documents `name`
23945
+ * as the command's unique identifier, so two commands deriving the same
23946
+ * `name` (e.g. `a/review.md` and `b/review.md`) collide, and it reads
23947
+ * project commands only up to three nested directory levels. Both files are
23948
+ * still written, so this warns rather than failing the run.
23949
+ */
23950
+ static validateRulesyncCommands({ rulesyncCommands, logger, global = false }) {
23951
+ const pathsByName = /* @__PURE__ */ new Map();
23952
+ for (const rulesyncCommand of rulesyncCommands) {
23953
+ if (!this.isTargetedByRulesyncCommand(rulesyncCommand)) continue;
23954
+ const relativeFilePath = toPosixPath(rulesyncCommand.getRelativeFilePath());
23955
+ const name = this.deriveName(rulesyncCommand);
23956
+ if (typeof name === "string") pathsByName.set(name, [...pathsByName.get(name) ?? [], relativeFilePath]);
23957
+ const dirDepth = relativeFilePath.split("/").length - 1;
23958
+ if (!global && dirDepth > TRAE_MAX_PROJECT_COMMAND_DIR_DEPTH) logger.warn(`Trae reads project commands only up to ${TRAE_MAX_PROJECT_COMMAND_DIR_DEPTH} nested directory levels, so ${quoteForLog(relativeFilePath)} is written but never loaded. Move it to a shallower directory.`);
23959
+ }
23960
+ for (const [name, paths] of pathsByName) {
23961
+ if (paths.length < 2) continue;
23962
+ logger.warn(`Trae command name ${quoteForLog(name)} is shared by ${paths.map(quoteForLog).join(", ")}, but Trae treats \`name\` as a unique identifier. Rename a file or set \`trae.name\` to tell them apart.`);
23963
+ }
23964
+ }
23965
+ /**
23966
+ * The `name` Trae sees for a command: the `trae` section's `name` when set,
23967
+ * otherwise the file name stem. A non-string override is returned as is so
23968
+ * frontmatter validation still rejects it.
23969
+ */
23970
+ static deriveName(rulesyncCommand) {
23971
+ return rulesyncCommand.getFrontmatter().trae?.name ?? basename(rulesyncCommand.getRelativeFilePath(), ".md");
23972
+ }
23722
23973
  static isTargetedByRulesyncCommand(rulesyncCommand) {
23723
23974
  return this.isTargetedByRulesyncCommandDefault({
23724
23975
  rulesyncCommand,
@@ -24302,6 +24553,17 @@ const toolCommandFactories = /* @__PURE__ */ new Map([
24302
24553
  supportsSubdirectory: false
24303
24554
  }
24304
24555
  }],
24556
+ ["kimi-code", {
24557
+ class: KimiCodeCommand,
24558
+ meta: {
24559
+ extension: "md",
24560
+ supportsProject: true,
24561
+ supportsGlobal: true,
24562
+ isSimulated: false,
24563
+ supportsSubdirectory: false,
24564
+ skipToolFileScan: true
24565
+ }
24566
+ }],
24305
24567
  ["kimi-code-plugin", {
24306
24568
  class: KimiCodePluginCommand,
24307
24569
  meta: {
@@ -24555,7 +24817,8 @@ var CommandsProcessor = class extends FeatureProcessor {
24555
24817
  await factory.class.validateRulesyncCommands?.({
24556
24818
  inputRoots: this.inputRoots,
24557
24819
  rulesyncCommands,
24558
- logger: this.logger
24820
+ logger: this.logger,
24821
+ global: this.global
24559
24822
  });
24560
24823
  const flattenedPathOrigins = /* @__PURE__ */ new Map();
24561
24824
  const toolCommands = [];
@@ -26235,8 +26498,8 @@ function isSupportedHookType({ type, converterConfig }) {
26235
26498
  * side either way, so the value is dropped rather than carried into a config
26236
26499
  * the tool would ignore or refuse.
26237
26500
  */
26238
- function isKeywordFieldApplicable({ spec, hookType, eventName, direction, warn }) {
26239
- const { canonical, tool, commandOnly, events } = spec;
26501
+ function isKeywordFieldApplicable({ spec, hookType, eventName, flags, direction, warn }) {
26502
+ const { canonical, tool, commandOnly, hookTypes, events, ignoredEvents, ignoredWhenFlags } = spec;
26240
26503
  const field = direction === "generated" ? canonical : tool;
26241
26504
  if (!isFieldApplicable({
26242
26505
  commandOnly,
@@ -26245,10 +26508,23 @@ function isKeywordFieldApplicable({ spec, hookType, eventName, direction, warn }
26245
26508
  warn?.(`Dropping "${field}" from a "${hookType}" hook on "${eventName}": this tool documents "${tool}" on "command" hooks only, so it is not ${direction}.`);
26246
26509
  return false;
26247
26510
  }
26511
+ if (hookTypes !== void 0 && !hookTypes.has(hookType)) {
26512
+ warn?.(`Dropping "${field}" from a "${hookType}" hook on "${eventName}": this tool documents "${tool}" on ${[...hookTypes].map((type) => `"${type}"`).join(", ")} hooks only, so it is not ${direction}.`);
26513
+ return false;
26514
+ }
26248
26515
  if (events !== void 0 && !events.has(eventName)) {
26249
26516
  warn?.(`Dropping "${field}" from a "${hookType}" hook on "${eventName}": this tool honours "${tool}" on ${[...events].map((event) => `"${event}"`).join(", ")} only, so it is not ${direction}.`);
26250
26517
  return false;
26251
26518
  }
26519
+ if (ignoredEvents?.has(eventName)) {
26520
+ warn?.(`Dropping "${field}" from a "${hookType}" hook on "${eventName}": this tool ignores "${tool}" on "${eventName}", so it is not ${direction}.`);
26521
+ return false;
26522
+ }
26523
+ const setFlag = hookType === "command" ? ignoredWhenFlags?.find((flag) => flags[flag] === true) : void 0;
26524
+ if (setFlag !== void 0) {
26525
+ warn?.(`Dropping "${field}" from a "${hookType}" hook on "${eventName}": this tool ignores "${tool}" on a hook that sets "${setFlag}", so it is not ${direction}.`);
26526
+ return false;
26527
+ }
26252
26528
  return true;
26253
26529
  }
26254
26530
  /**
@@ -26265,6 +26541,7 @@ function emitKeywordFields({ def, hookType, eventName, fields, warn }) {
26265
26541
  spec,
26266
26542
  hookType,
26267
26543
  eventName,
26544
+ flags: def,
26268
26545
  direction: "generated",
26269
26546
  warn
26270
26547
  })) emitted[spec.tool] = spec.trueValue;
@@ -26286,6 +26563,7 @@ function importKeywordFields({ h, hookType, eventName, fields, warn }) {
26286
26563
  spec,
26287
26564
  hookType,
26288
26565
  eventName,
26566
+ flags: h,
26289
26567
  direction: "imported",
26290
26568
  warn
26291
26569
  })) continue;
@@ -28178,6 +28456,20 @@ const CLAUDE_SPEC = {
28178
28456
  tool: "continueOnBlock"
28179
28457
  }
28180
28458
  ],
28459
+ keywordPassthroughFields: [{
28460
+ canonical: "failClosed",
28461
+ tool: "onFailure",
28462
+ trueValue: "block",
28463
+ falseValue: "continue",
28464
+ hookTypes: /* @__PURE__ */ new Set(["command", "http"]),
28465
+ ignoredEvents: /* @__PURE__ */ new Set([
28466
+ "stop",
28467
+ "subagentStop",
28468
+ "taskCompleted",
28469
+ "teammateIdle"
28470
+ ]),
28471
+ ignoredWhenFlags: ["async", "asyncRewake"]
28472
+ }],
28181
28473
  arrayPassthroughFields: [{
28182
28474
  canonical: "args",
28183
28475
  tool: "args",
@@ -28900,18 +29192,20 @@ const CODEXCLI_CONVERTER_CONFIG = {
28900
29192
  canonicalToToolEventNames: CANONICAL_TO_CODEXCLI_EVENT_NAMES,
28901
29193
  toolToCanonicalEventNames: CODEXCLI_TO_CANONICAL_EVENT_NAMES,
28902
29194
  projectDirVar: "",
28903
- supportedHookTypes: /* @__PURE__ */ new Set(["command"]),
29195
+ supportedHookTypes: /* @__PURE__ */ new Set(["command", "mcp_tool"]),
28904
29196
  passthroughFields: ["name", "description"],
28905
29197
  stringPassthroughFields: [{
28906
29198
  canonical: "commandWindows",
28907
- tool: "commandWindows"
29199
+ tool: "commandWindows",
29200
+ commandOnly: true
28908
29201
  }, {
28909
29202
  canonical: "statusMessage",
28910
29203
  tool: "statusMessage"
28911
29204
  }],
28912
29205
  numberPassthroughFields: [{
28913
29206
  canonical: "additionalContextLimit",
28914
- tool: "additionalContextLimit"
29207
+ tool: "additionalContextLimit",
29208
+ commandOnly: true
28915
29209
  }],
28916
29210
  booleanPassthroughFields: [{
28917
29211
  canonical: "async",
@@ -28920,6 +29214,74 @@ const CODEXCLI_CONVERTER_CONFIG = {
28920
29214
  }]
28921
29215
  };
28922
29216
  /**
29217
+ * Whether a JSON value holds a `null` anywhere. Codex rejects an `mcp_tool`
29218
+ * hook `input` that cannot be represented as TOML (it hashes the handler as
29219
+ * TOML for trust review), and `null` is the one JSON value TOML lacks.
29220
+ */
29221
+ function containsNull(value) {
29222
+ if (value === null) return true;
29223
+ if (Array.isArray(value)) return value.some(containsNull);
29224
+ if (typeof value === "object") return Object.values(value).some(containsNull);
29225
+ return false;
29226
+ }
29227
+ const WHOLE_FILE_FAILURE = "Codex fails to load the whole hooks file on such a handler";
29228
+ /**
29229
+ * Why this generated handler must not be written, or `undefined` when Codex
29230
+ * can load and run it. Codex deserializes `timeout` as `Option<u64>` on every
29231
+ * handler type, and `server` / `tool` as required strings plus `input` as a
29232
+ * TOML-representable map on `mcp_tool`; a handler that fails that stops the
29233
+ * whole hooks file from loading rather than just that one hook. An empty
29234
+ * `server` / `tool` string still loads but can never resolve to a tool.
29235
+ * https://github.com/openai/codex/blob/main/codex-rs/config/src/hook_config.rs
29236
+ */
29237
+ function describeRejectedHandler(handler) {
29238
+ const { timeout } = handler;
29239
+ if (timeout !== void 0 && (typeof timeout !== "number" || !Number.isSafeInteger(timeout) || timeout < 0)) return `its "timeout" ${JSON.stringify(timeout) ?? String(timeout)} is not a non-negative integer, and ${WHOLE_FILE_FAILURE}`;
29240
+ if (handler.type !== "mcp_tool") return;
29241
+ for (const field of ["server", "tool"]) {
29242
+ const value = handler[field];
29243
+ if (typeof value !== "string") return `it has no "${field}", and ${WHOLE_FILE_FAILURE}`;
29244
+ if (value === "") return `its "${field}" is empty, so it can never resolve to an MCP tool`;
29245
+ }
29246
+ if (containsNull(handler.input)) return `its "input" holds a null, which Codex cannot represent as TOML, and ${WHOLE_FILE_FAILURE}`;
29247
+ }
29248
+ /**
29249
+ * Drop the generated handlers Codex would refuse to load (or could never
29250
+ * run), and warn about the `mcp_tool` ones Codex loads but skips (`SessionEnd`
29251
+ * MCP hooks are skipped with a startup warning). Matcher groups left without
29252
+ * handlers are removed. https://learn.chatgpt.com/docs/hooks
29253
+ */
29254
+ function filterCodexHandlers({ hooks, logger }) {
29255
+ const result = {};
29256
+ const skippedSessionEndTools = [];
29257
+ for (const [eventName, groups] of Object.entries(hooks)) {
29258
+ const kept = [];
29259
+ for (const group of groups) {
29260
+ const entry = group;
29261
+ if (!Array.isArray(entry.hooks)) {
29262
+ kept.push(group);
29263
+ continue;
29264
+ }
29265
+ const handlers = entry.hooks.filter((handler) => {
29266
+ const reason = describeRejectedHandler(handler);
29267
+ if (reason !== void 0) {
29268
+ logger?.warn(`Skipping a "${String(handler.type)}" hook on Codex CLI "${eventName}": ${reason}.`);
29269
+ return false;
29270
+ }
29271
+ if (eventName === "SessionEnd" && handler.type === "mcp_tool") skippedSessionEndTools.push(`${String(handler.server)}/${String(handler.tool)}`);
29272
+ return true;
29273
+ });
29274
+ if (handlers.length > 0) kept.push({
29275
+ ...entry,
29276
+ hooks: handlers
29277
+ });
29278
+ }
29279
+ if (kept.length > 0) result[eventName] = kept;
29280
+ }
29281
+ if (skippedSessionEndTools.length > 0) logger?.warn(`Codex CLI skips "mcp_tool" hooks on "SessionEnd" with a startup warning, so these are written but will not run: ${skippedSessionEndTools.join(", ")}.`);
29282
+ return result;
29283
+ }
29284
+ /**
28923
29285
  * Build the content for `.codex/config.toml`, cleaning up the deprecated `codex_hooks` key.
28924
29286
  * Reads the existing file (if any), parses TOML, and returns the content without writing to
28925
29287
  * disk. The caller is responsible for writing via the normal write phase.
@@ -29012,10 +29374,13 @@ var CodexcliHooks = class CodexcliHooks extends ToolHooks {
29012
29374
  const config = rulesyncHooks.getJson();
29013
29375
  const merged = mergeGeneratedHookLists({
29014
29376
  existingContent,
29015
- generatedHooks: canonicalToToolHooks({
29016
- config,
29017
- toolOverrideHooks: config.codexcli?.hooks,
29018
- converterConfig: CODEXCLI_CONVERTER_CONFIG,
29377
+ generatedHooks: filterCodexHandlers({
29378
+ hooks: canonicalToToolHooks({
29379
+ config,
29380
+ toolOverrideHooks: config.codexcli?.hooks,
29381
+ converterConfig: CODEXCLI_CONVERTER_CONFIG,
29382
+ logger
29383
+ }),
29019
29384
  logger
29020
29385
  }),
29021
29386
  shape: "matcher-groups",
@@ -32194,60 +32559,6 @@ var KiloHooks = class KiloHooks extends ToolHooks {
32194
32559
  }
32195
32560
  };
32196
32561
  //#endregion
32197
- //#region src/constants/kimi-code-paths.ts
32198
- const KIMI_CODE_DIR = ".kimi-code";
32199
- const KIMI_CODE_RULE_FILE_NAME = "AGENTS.md";
32200
- const KIMI_CODE_MCP_FILE_NAME = "mcp.json";
32201
- const KIMI_CODE_CONFIG_FILE_NAME = "config.toml";
32202
- const KIMI_CODE_SKILLS_DIR_NAME = "skills";
32203
- const KIMI_CODE_AGENTS_DIR_NAME = "agents";
32204
- const KIMI_CODE_SKILLS_DIR_PATH = join(KIMI_CODE_DIR, KIMI_CODE_SKILLS_DIR_NAME);
32205
- join(KIMI_CODE_DIR, KIMI_CODE_AGENTS_DIR_NAME);
32206
- const KIMI_CODE_SHARED_SKILLS_DIR_PATH = join(".agents", "skills");
32207
- const KIMI_CODE_SHARED_AGENTS_DIR_PATH = join(".agents", "agents");
32208
- //#endregion
32209
- //#region src/utils/kimi-code.ts
32210
- function getKimiCodeHome() {
32211
- const configuredHome = process.env.KIMI_CODE_HOME?.trim();
32212
- return configuredHome ? resolve(configuredHome) : void 0;
32213
- }
32214
- function getKimiCodeRelativeDirPath({ global, relativeDirPath = "." }) {
32215
- return global && getKimiCodeHome() ? relativeDirPath : join(KIMI_CODE_DIR, relativeDirPath);
32216
- }
32217
- /**
32218
- * Both spellings the shared user `config.toml` can take: under `.kimi-code/`,
32219
- * or at the root of `KIMI_CODE_HOME` when that override names the profile dir.
32220
- * Declared unconditionally so the derived shared-file keys — and the drift
32221
- * guards checked against them — do not depend on the ambient environment.
32222
- */
32223
- function getKimiCodeSharedConfigWritePaths() {
32224
- return [{
32225
- relativeDirPath: KIMI_CODE_DIR,
32226
- relativeFilePath: KIMI_CODE_CONFIG_FILE_NAME
32227
- }, {
32228
- relativeDirPath: ".",
32229
- relativeFilePath: KIMI_CODE_CONFIG_FILE_NAME
32230
- }];
32231
- }
32232
- /**
32233
- * The `SHARED_CONFIG_OWNERSHIP` key of the `config.toml` actually being written.
32234
- * Both spellings carry the same declaration, but passing the key of the file
32235
- * being written keeps the write path and the drift guards on the same entry.
32236
- */
32237
- function getKimiCodeConfigSharedFileKey({ global }) {
32238
- return sharedConfigFileKey({
32239
- relativeDirPath: getKimiCodeRelativeDirPath({ global }),
32240
- relativeFilePath: KIMI_CODE_CONFIG_FILE_NAME
32241
- });
32242
- }
32243
- function getKimiCodeRulesyncOutputRoot({ nativeOutputRoot, global }) {
32244
- return getToolRulesyncOutputRoot({
32245
- nativeOutputRoot,
32246
- global,
32247
- toolHome: getKimiCodeHome
32248
- });
32249
- }
32250
- //#endregion
32251
32562
  //#region src/features/hooks/kimi-code-hooks.ts
32252
32563
  function runFromTrustedDirectory({ command, trustedDirectory }) {
32253
32564
  if (process.platform === "win32") return `set "RULESYNC_KIMI_HOOK_CWD=1" && cd /d "${trustedDirectory.replaceAll("%", "%%").replaceAll("\"", "\"\"")}" && ${command}`;
@@ -35704,7 +36015,7 @@ const toolHooksFactories = /* @__PURE__ */ new Map([
35704
36015
  supportsImport: true
35705
36016
  },
35706
36017
  supportedEvents: CODEXCLI_HOOK_EVENTS,
35707
- supportedHookTypes: ["command"],
36018
+ supportedHookTypes: ["command", "mcp_tool"],
35708
36019
  supportsMatcher: true
35709
36020
  }],
35710
36021
  ["commandcode", {
@@ -37966,6 +38277,23 @@ function toReasonixTable(value) {
37966
38277
  if (!isPlainObject$1(value)) return {};
37967
38278
  return { ...value };
37968
38279
  }
38280
+ /**
38281
+ * The tool name a Reasonix `[permissions]` rule must use to reach file reads.
38282
+ * Reasonix matches a rule's tool exactly against its registered tools
38283
+ * (`read_file`, `write_file`, `grep`, ...); only `Bash`, `Edit`, `Browser` and
38284
+ * `Computer` are Claude-Code-style aliases (`canonicalRuleTool` in
38285
+ * `internal/safety/permission/permission.go`), so a `Read(...)` rule matches
38286
+ * nothing. Shared by the `ignore` and `permissions` adapters, which both write
38287
+ * read denies into the same table.
38288
+ * @see https://github.com/esengine/DeepSeek-Reasonix/blob/v2.41.0/internal/safety/permission/permission.go
38289
+ */
38290
+ const REASONIX_READ_TOOL_NAME = "read_file";
38291
+ /**
38292
+ * The spelling rulesync wrote for read rules before it followed the Reasonix
38293
+ * tool vocabulary. Still accepted on import, and an entry spelled this way is
38294
+ * treated as rulesync-owned on generate so the dormant copy is replaced.
38295
+ */
38296
+ const REASONIX_LEGACY_READ_TOOL_NAME = "Read";
37969
38297
  //#endregion
37970
38298
  //#region src/features/ignore/reasonix-ignore.ts
37971
38299
  const permissionsTableOf = (document) => toReasonixTable(document.permissions);
@@ -37984,10 +38312,15 @@ const asClaudeStyleSettings = (document) => {
37984
38312
  ...table,
37985
38313
  allow: toReasonixStringArray(table.allow),
37986
38314
  ask: toReasonixStringArray(table.ask),
37987
- deny: toReasonixStringArray(table.deny)
38315
+ deny: toReasonixStringArray(table.deny).filter((entry) => !isReadDenyEntry(entry, REASONIX_LEGACY_READ_TOOL_NAME))
37988
38316
  }
37989
38317
  };
37990
38318
  };
38319
+ /** The pattern of a `read_file(...)` (or legacy `Read(...)`) deny, else null. */
38320
+ const readDenyPatternOf = (entry) => {
38321
+ for (const toolName of [REASONIX_READ_TOOL_NAME, REASONIX_LEGACY_READ_TOOL_NAME]) if (isReadDenyEntry(entry, toolName)) return entry.slice(toolName.length + 1, -1);
38322
+ return null;
38323
+ };
37991
38324
  /**
37992
38325
  * Drop a `[permissions]` table that ended up with nothing in it, so an empty
37993
38326
  * `.rulesyncignore` does not add a bare table header to a file that never had
@@ -38000,13 +38333,16 @@ const withoutEmptyPermissions = (settings) => {
38000
38333
  return document;
38001
38334
  };
38002
38335
  /**
38003
- * Writes `.rulesyncignore` patterns as `Read(<pattern>)` entries in the
38336
+ * Writes `.rulesyncignore` patterns as `read_file(<pattern>)` entries in the
38004
38337
  * `[permissions] deny` table of `reasonix.toml` (project) /
38005
38338
  * `~/.reasonix/config.toml` (global).
38006
38339
  *
38007
38340
  * `deny` is the right target rather than `[sandbox] forbid_read`: deny rules
38008
38341
  * take glob specifiers (`Edit(docs/**)`) and are "a hard block in every mode",
38009
38342
  * while `forbid_read` is documented as absolute paths with no glob support.
38343
+ * The tool is spelled `read_file` because Reasonix matches rule tools against
38344
+ * its registered tool names (see {@link REASONIX_READ_TOOL_NAME}); a legacy
38345
+ * `Read(...)` deny is still read back on import.
38010
38346
  * @see https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/docs/SPEC.md
38011
38347
  */
38012
38348
  var ReasonixIgnore = class ReasonixIgnore extends ToolIgnore {
@@ -38033,7 +38369,7 @@ var ReasonixIgnore = class ReasonixIgnore extends ToolIgnore {
38033
38369
  return false;
38034
38370
  }
38035
38371
  toRulesyncIgnore() {
38036
- const rulesyncPatterns = this.patterns.filter((pattern) => isReadDenyEntry(pattern)).map((pattern) => pattern.slice(5, -1)).filter((pattern) => pattern.length > 0);
38372
+ const rulesyncPatterns = uniq(this.patterns.map((entry) => readDenyPatternOf(entry)).filter((pattern) => pattern !== null && pattern.length > 0));
38037
38373
  return new RulesyncIgnore({
38038
38374
  outputRoot: this.outputRoot,
38039
38375
  relativeDirPath: RulesyncIgnore.getSettablePaths().recommended.relativeDirPath,
@@ -38042,7 +38378,7 @@ var ReasonixIgnore = class ReasonixIgnore extends ToolIgnore {
38042
38378
  });
38043
38379
  }
38044
38380
  static async fromRulesyncIgnore({ outputRoot = process.cwd(), rulesyncIgnore, global = false }) {
38045
- const readDenies = rulesyncIgnore.getFileContent().split(/\r?\n|\r/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#")).map((pattern) => buildReadDenyEntry(pattern));
38381
+ const readDenies = rulesyncIgnore.getFileContent().split(/\r?\n|\r/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#")).map((pattern) => buildReadDenyEntry(pattern, REASONIX_READ_TOOL_NAME));
38046
38382
  const paths = this.getSettablePaths({ global });
38047
38383
  const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
38048
38384
  const existingDocument = parseSharedConfig({
@@ -38052,7 +38388,8 @@ var ReasonixIgnore = class ReasonixIgnore extends ToolIgnore {
38052
38388
  });
38053
38389
  const document = withoutEmptyPermissions(applyIgnoreReadDenies({
38054
38390
  settings: asClaudeStyleSettings(existingDocument),
38055
- readDenies
38391
+ readDenies,
38392
+ readToolName: REASONIX_READ_TOOL_NAME
38056
38393
  }));
38057
38394
  return new ReasonixIgnore({
38058
38395
  outputRoot,
@@ -45555,7 +45892,7 @@ var KiloMcp = class KiloMcp extends ToolMcp {
45555
45892
  feature: "mcp",
45556
45893
  existingContent: fileContent ?? "",
45557
45894
  patch: {
45558
- mcp: convertedMcp,
45895
+ mcp: undefinedWhenEmpty(convertedMcp),
45559
45896
  ...buildKiloMcpPermissionPatch({
45560
45897
  existing: existingDocument,
45561
45898
  managedServerNames: uniq([...Object.keys(mcpServers), ...Object.keys(isRecord$1(existingDocument.mcp) ? existingDocument.mcp : {})]),
@@ -46415,7 +46752,7 @@ var OpencodeMcp = class extends ToolMcp {
46415
46752
  feature: "mcp",
46416
46753
  existingContent: fileContent ?? "",
46417
46754
  patch: {
46418
- mcp: convertedMcp,
46755
+ mcp: undefinedWhenEmpty(convertedMcp),
46419
46756
  tools: Object.keys(mcpTools).length > 0 ? mcpTools : void 0
46420
46757
  },
46421
46758
  filePath: join(jsonDir, relativeFilePath),
@@ -46763,9 +47100,12 @@ function convertFromMusecodeFormat(musecodeMcp) {
46763
47100
  /**
46764
47101
  * Meta Muse Code MCP servers.
46765
47102
  *
46766
- * Muse Code reads MCP servers only from the `mcp_servers` (or `mcpServers`)
46767
- * block of the GLOBAL user settings file `~/.config/muse/settings.json`; no
46768
- * project-scoped MCP location is documented. The settings file must carry
47103
+ * rulesync writes Muse Code MCP servers only to the `mcp_servers` (or
47104
+ * `mcpServers`) block of the GLOBAL user settings file
47105
+ * `~/.config/muse/settings.json`, the location extending.md documents. Muse
47106
+ * Code 1.4.0+ also loads a trusted project `.mcp.json` (changelog only), which
47107
+ * is not emitted here because the `claudecode` target already writes that
47108
+ * file. The settings file must carry
46769
47109
  * `"schema_version": 1` — a file that omits that key fails every command at
46770
47110
  * startup with `malformed settings file` — so the key is bootstrapped when the
46771
47111
  * file is created and preserved when it already exists. Other settings keys are
@@ -46773,6 +47113,7 @@ function convertFromMusecodeFormat(musecodeMcp) {
46773
47113
  *
46774
47114
  * @see https://dev.meta.ai/docs/muse-code/configuration.md
46775
47115
  * @see https://dev.meta.ai/docs/muse-code/extending.md
47116
+ * @see https://dev.meta.ai/docs/muse-code/changelog
46776
47117
  */
46777
47118
  var MusecodeMcp = class MusecodeMcp extends ToolMcp {
46778
47119
  json;
@@ -48005,7 +48346,7 @@ var QwencodeMcp = class QwencodeMcp extends ToolMcp {
48005
48346
  feature: "mcp",
48006
48347
  existingContent,
48007
48348
  patch: {
48008
- mcpServers: convertToQwencodeFormat(rulesyncMcp.getMcpServers()),
48349
+ mcpServers: undefinedWhenEmpty(convertToQwencodeFormat(rulesyncMcp.getMcpServers())),
48009
48350
  ...mcpPatch !== void 0 && { mcp: mcpPatch }
48010
48351
  },
48011
48352
  filePath
@@ -55417,6 +55758,7 @@ const CLAUDECODE_USER_SCOPE_ONLY_KEYS = /* @__PURE__ */ new Set([
55417
55758
  "dialogExpiry",
55418
55759
  "feedbackDrafts",
55419
55760
  "footerLinksRegexes",
55761
+ "forceLoginGatewayUrl",
55420
55762
  "modelPicker",
55421
55763
  "pluginConfigs",
55422
55764
  "prependPlugins",
@@ -55430,6 +55772,39 @@ const CLAUDECODE_USER_SCOPE_ONLY_KEYS = /* @__PURE__ */ new Set([
55430
55772
  "vimInsertModeRemaps"
55431
55773
  ]);
55432
55774
  /**
55775
+ * Nested settings paths under an otherwise project-honored top-level key that
55776
+ * Claude Code reads only from user settings — the nested counterpart of
55777
+ * {@link CLAUDECODE_USER_SCOPE_ONLY_KEYS}. `worktree` is `Any file`, but
55778
+ * `worktree.location` (where Desktop SSH sessions create worktrees on the
55779
+ * remote machine) is documented with a `User` scope, so it is dropped from a
55780
+ * project `.claude/settings.json` and written only under `--global`, while its
55781
+ * siblings are kept.
55782
+ *
55783
+ * @see https://code.claude.com/docs/en/settings-reference#worktree-location
55784
+ */
55785
+ const CLAUDECODE_USER_SCOPE_ONLY_NESTED_PATHS = [["worktree", "location"]];
55786
+ /**
55787
+ * Drops {@link CLAUDECODE_USER_SCOPE_ONLY_NESTED_PATHS} from the top-level
55788
+ * passthrough at project scope, in place, warning once per dropped path. The
55789
+ * authored subtree is copied before the deletion so the caller's config is not
55790
+ * mutated, and a parent the removal emptied is dropped with it.
55791
+ */
55792
+ function stripUserScopeOnlyNestedPaths({ overrides, global, relativeFilePath, logger }) {
55793
+ if (global) return;
55794
+ for (const path of CLAUDECODE_USER_SCOPE_ONLY_NESTED_PATHS) {
55795
+ const [topLevelKey] = path;
55796
+ if (topLevelKey === void 0 || !isRecord$1(overrides[topLevelKey])) continue;
55797
+ const copy = { [topLevelKey]: structuredClone(overrides[topLevelKey]) };
55798
+ if (!deleteSandboxPath({
55799
+ target: copy,
55800
+ path
55801
+ })) continue;
55802
+ if (copy[topLevelKey] === void 0) delete overrides[topLevelKey];
55803
+ else overrides[topLevelKey] = copy[topLevelKey];
55804
+ logger?.warn(`Claude Code permissions: '${path.map((segment) => displayKey(segment)).join(".")}' is not honored in the project-scoped ${relativeFilePath}, so it is not written there — Claude Code reads it from user settings only. Author it in the global scope instead, and check that file for a stale value an earlier generate may have left there.`);
55805
+ }
55806
+ }
55807
+ /**
55433
55808
  * Top-level settings keys neither file rulesync writes can honor, with the file
55434
55809
  * that does. `Managed` keys are read only from the settings file an organization
55435
55810
  * deploys, and `Global config` keys only from `~/.claude.json` — rulesync writes
@@ -55467,7 +55842,6 @@ const CLAUDECODE_UNHONORED_KEY_SOURCES = {
55467
55842
  disableMobileSimulatorTools: "managed settings",
55468
55843
  disableSideloadFlags: "managed settings",
55469
55844
  externalEditorContext: "~/.claude.json",
55470
- forceLoginGatewayUrl: "managed settings",
55471
55845
  forceRemoteSettingsRefresh: "managed settings",
55472
55846
  gatewayInternalNetworks: "managed settings",
55473
55847
  leftArrowOpensAgents: "~/.claude.json",
@@ -55626,8 +56000,9 @@ const CLAUDECODE_ENV_IGNORED_IN_EVERY_FILE = /* @__PURE__ */ new Set([
55626
56000
  /**
55627
56001
  * `env` variables a project or local settings file cannot set, because a
55628
56002
  * checked-out repository should not control them: where Claude Code stores its
55629
- * own files, what session content it exports, where telemetry goes, and how it
55630
- * starts or syncs. Claude Code drops them there, so they are skipped at project scope and emitted only under `--global`.
56003
+ * own files, what session content it exports, where telemetry goes, how it
56004
+ * starts or syncs, the timers on an unanswered dialog, and whether it processes
56005
+ * attachments or expands `@` file mentions. Claude Code drops them there, so they are skipped at project scope and emitted only under `--global`.
55631
56006
  *
55632
56007
  * Only the names the settings reference lists are covered; its directory and
55633
56008
  * Windows groups are introduced with "such as", so an unlisted member of those
@@ -55638,6 +56013,10 @@ const CLAUDECODE_ENV_IGNORED_IN_EVERY_FILE = /* @__PURE__ */ new Set([
55638
56013
  */
55639
56014
  const CLAUDECODE_ENV_PROJECT_IGNORED_NAMES = /* @__PURE__ */ new Set([
55640
56015
  "BETA_TRACING_ENDPOINT",
56016
+ "CLAUDE_AFK_COUNTDOWN_MS",
56017
+ "CLAUDE_AFK_TIMEOUT_MS",
56018
+ "CLAUDE_CODE_DISABLE_ATTACHMENTS",
56019
+ "CLAUDE_CODE_DISABLE_FILE_MENTIONS",
55641
56020
  "CLAUDE_CODE_ENABLE_TELEMETRY",
55642
56021
  "CLAUDE_CODE_ENHANCED_TELEMETRY_BETA",
55643
56022
  "CLAUDE_CODE_PLUGIN_CACHE_DIR",
@@ -55646,6 +56025,7 @@ const CLAUDECODE_ENV_PROJECT_IGNORED_NAMES = /* @__PURE__ */ new Set([
55646
56025
  "CLAUDE_CODE_SYNC_PLUGINS",
55647
56026
  "CLAUDE_CODE_SYNC_SKILLS",
55648
56027
  "CLAUDE_CODE_TMPDIR",
56028
+ "CLAUDE_CODE_USER_DIALOG_TIMEOUT_MS",
55649
56029
  "CLAUDE_CONFIG_DIR",
55650
56030
  "ENABLE_BETA_TRACING_DETAILED",
55651
56031
  "ENABLE_ENHANCED_TELEMETRY_BETA",
@@ -55691,6 +56071,13 @@ const CLAUDECODE_ENV_CONTENT_OFF_VALUES = /* @__PURE__ */ new Set([
55691
56071
  * writing a value Claude Code ignores is the old behavior, while dropping an
55692
56072
  * opt-out it honors would remove a privacy setting. `OTEL_LOG_ASSISTANT_RESPONSES`
55693
56073
  * has no such exception and sits in the plain name table above.
56074
+ *
56075
+ * `CLAUDE_CODE_ENABLE_CFC` follows the same shape for a different reason: a
56076
+ * project cannot turn Claude in Chrome on, since a checked-out repository must
56077
+ * not be able to connect Claude to the user's browser, so only an off value is
56078
+ * kept at project scope.
56079
+ *
56080
+ * @see https://code.claude.com/docs/en/chrome#project-settings-cant-turn-on-chrome
55694
56081
  */
55695
56082
  const CLAUDECODE_ENV_PROJECT_HONORED_OFF_VALUES = {
55696
56083
  OTEL_LOGS_EXPORTER: CLAUDECODE_ENV_EXPORTER_OFF_VALUES,
@@ -55698,7 +56085,8 @@ const CLAUDECODE_ENV_PROJECT_HONORED_OFF_VALUES = {
55698
56085
  OTEL_TRACES_EXPORTER: CLAUDECODE_ENV_EXPORTER_OFF_VALUES,
55699
56086
  OTEL_LOG_USER_PROMPTS: CLAUDECODE_ENV_CONTENT_OFF_VALUES,
55700
56087
  OTEL_LOG_TOOL_CONTENT: CLAUDECODE_ENV_CONTENT_OFF_VALUES,
55701
- OTEL_LOG_TOOL_DETAILS: CLAUDECODE_ENV_CONTENT_OFF_VALUES
56088
+ OTEL_LOG_TOOL_DETAILS: CLAUDECODE_ENV_CONTENT_OFF_VALUES,
56089
+ CLAUDE_CODE_ENABLE_CFC: CLAUDECODE_ENV_CONTENT_OFF_VALUES
55702
56090
  };
55703
56091
  function isProjectIgnoredEnvVariable(name, value) {
55704
56092
  if (CLAUDECODE_ENV_PROJECT_IGNORED_NAMES.has(name) || name.startsWith("XDG_")) return true;
@@ -55735,7 +56123,7 @@ function stripIgnoredEnvVariables({ env, global, relativeFilePath, logger }) {
55735
56123
  kept[name] = value;
55736
56124
  }
55737
56125
  if (ignoredEverywhere.length > 0) logger?.warn(`Claude Code permissions: Claude Code ignores ${ignoredEverywhere.map((name) => `'env.${name}'`).join(", ")} in every settings file, so ${ignoredEverywhere.length === 1 ? "it is" : "they are"} not written to ${relativeFilePath}. Set ${ignoredEverywhere.length === 1 ? "it" : "them"} in the environment Claude Code starts from instead, and check ${relativeFilePath} for a stale value an earlier generate may have left there.`);
55738
- if (ignoredAtProjectScope.length > 0) logger?.warn(`Claude Code permissions: ${ignoredAtProjectScope.map((name) => `'env.${name}'`).join(", ")} ${ignoredAtProjectScope.length === 1 ? "is" : "are"} not honored in the project-scoped ${relativeFilePath}, so ${ignoredAtProjectScope.length === 1 ? "it is" : "they are"} not written there — Claude Code ignores variables there that choose where it writes its files, export session content or telemetry, or change how it starts or syncs (a value that turns telemetry off, such as "none" or "0", is still written). Author ${ignoredAtProjectScope.length === 1 ? "it" : "them"} in the global scope instead, and check that file for a stale value an earlier generate may have left there.`);
56126
+ if (ignoredAtProjectScope.length > 0) logger?.warn(`Claude Code permissions: ${ignoredAtProjectScope.map((name) => `'env.${name}'`).join(", ")} ${ignoredAtProjectScope.length === 1 ? "is" : "are"} not honored in the project-scoped ${relativeFilePath}, so ${ignoredAtProjectScope.length === 1 ? "it is" : "they are"} not written there — Claude Code ignores variables there that choose where it writes its files, export session content or telemetry, change how it starts or syncs, set dialog timers, turn attachments or file mentions off, or turn Claude in Chrome on (a value that turns telemetry or Chrome off, such as "none" or "0", is still written). Author ${ignoredAtProjectScope.length === 1 ? "it" : "them"} in the global scope instead, and check that file for a stale value an earlier generate may have left there.`);
55739
56127
  return kept;
55740
56128
  }
55741
56129
  /**
@@ -55966,6 +56354,12 @@ var ClaudecodePermissions = class ClaudecodePermissions extends ToolPermissions
55966
56354
  relativeFilePath: paths.relativeFilePath,
55967
56355
  logger
55968
56356
  });
56357
+ stripUserScopeOnlyNestedPaths({
56358
+ overrides: overrideTopLevel,
56359
+ global,
56360
+ relativeFilePath: paths.relativeFilePath,
56361
+ logger
56362
+ });
55969
56363
  const { filtered: scopedTopLevel, trustAffecting: trustAffectingTopLevel } = stripUnhonoredTopLevelKeys({
55970
56364
  overrides: overrideTopLevel,
55971
56365
  global,
@@ -56383,7 +56777,7 @@ const CODEBUDDY_RULE_LISTS = [
56383
56777
  "ask",
56384
56778
  "deny"
56385
56779
  ];
56386
- const CATCH_ALL_PATTERN$11 = "*";
56780
+ const CATCH_ALL_PATTERN$12 = "*";
56387
56781
  const MCP_CATEGORY = "mcp";
56388
56782
  const MCP_RULE_PREFIX = "mcp__";
56389
56783
  const ALL_MCP_RULE = "mcp__*";
@@ -56456,7 +56850,7 @@ function categoryOfRule$2(rule) {
56456
56850
  * and reach the `Bash` rules through `honorAllToolsOnBash`.
56457
56851
  */
56458
56852
  function buildCodebuddyRule({ category, pattern, action, logger }) {
56459
- const catchAll = pattern === CATCH_ALL_PATTERN$11;
56853
+ const catchAll = pattern === CATCH_ALL_PATTERN$12;
56460
56854
  if (category === MCP_CATEGORY) {
56461
56855
  if (catchAll && action === "allow") {
56462
56856
  logger?.warn(`CodeBuddy permissions: skipping the 'mcp' allow rule '*' — CodeBuddy honors the all-MCP wildcard 'mcp__*' in deny and ask only. Allow servers by name instead (for example 'mcp__github').`);
@@ -56465,7 +56859,7 @@ function buildCodebuddyRule({ category, pattern, action, logger }) {
56465
56859
  return catchAll ? ALL_MCP_RULE : `${MCP_RULE_PREFIX}${pattern}`;
56466
56860
  }
56467
56861
  if (category === "*") {
56468
- if (catchAll) return CATCH_ALL_PATTERN$11;
56862
+ if (catchAll) return CATCH_ALL_PATTERN$12;
56469
56863
  logger?.warn(`CodeBuddy permissions: skipping the all-tools '*' rule '${pattern}' — CodeBuddy's standalone '*' rule takes no specifier. Write it under the category it is meant for (for example 'bash' or 'read').`);
56470
56864
  return null;
56471
56865
  }
@@ -56483,7 +56877,7 @@ function parseCodebuddyRule(rule) {
56483
56877
  const { specifier } = splitCodebuddyRule(rule);
56484
56878
  if (specifier === void 0) return {
56485
56879
  category,
56486
- pattern: CATCH_ALL_PATTERN$11
56880
+ pattern: CATCH_ALL_PATTERN$12
56487
56881
  };
56488
56882
  const trimmed = specifier.trim();
56489
56883
  return trimmed === "" ? null : {
@@ -58734,7 +59128,7 @@ function mapBashActionToDecision(action) {
58734
59128
  //#region src/features/permissions/commandcode-permissions.ts
58735
59129
  /** Top-level key of `.commandcode/settings.json` that rulesync owns. */
58736
59130
  const COMMANDCODE_PERMISSIONS_KEY = "permissions";
58737
- const CATCH_ALL_PATTERN$10 = "*";
59131
+ const CATCH_ALL_PATTERN$11 = "*";
58738
59132
  const MCP_CANONICAL_PREFIX$6 = "mcp__";
58739
59133
  const COMMANDCODE_ALL_MCP_RULE = "mcp__*";
58740
59134
  const CATEGORY_TO_COMMANDCODE_TOOL = {
@@ -58759,14 +59153,14 @@ const COMMANDCODE_TOOL_TO_CATEGORY = {
58759
59153
  web_fetch: "webfetch",
58760
59154
  web_search: "websearch"
58761
59155
  };
58762
- const ACTION_RANK$4 = {
59156
+ const ACTION_RANK$5 = {
58763
59157
  allow: 0,
58764
59158
  ask: 1,
58765
59159
  deny: 2
58766
59160
  };
58767
59161
  /** Whether `action` outranks `existing` (deny > ask > allow). */
58768
59162
  function isStricterAction({ action, existing }) {
58769
- return existing === void 0 || ACTION_RANK$4[action] > ACTION_RANK$4[existing];
59163
+ return existing === void 0 || ACTION_RANK$5[action] > ACTION_RANK$5[existing];
58770
59164
  }
58771
59165
  /**
58772
59166
  * Why a padded `Shell( * )` cannot stand in for the bare `Shell` in `allow`:
@@ -58789,10 +59183,10 @@ const PADDED_SHELL_WILDCARD_REASON = "a pattern narrower than the bare 'Shell' (
58789
59183
  * restrictions and reach the `Shell` entries through `honorAllToolsOnBash`.
58790
59184
  */
58791
59185
  function buildCommandcodeRule(category, pattern) {
58792
- const catchAll = pattern === CATCH_ALL_PATTERN$10 || pattern === "";
59186
+ const catchAll = pattern === CATCH_ALL_PATTERN$11 || pattern === "";
58793
59187
  if (category.startsWith(MCP_CANONICAL_PREFIX$6)) return catchAll ? category : `${category}(${pattern})`;
58794
59188
  if (category === "mcp") return catchAll ? COMMANDCODE_ALL_MCP_RULE : `${MCP_CANONICAL_PREFIX$6}${pattern}`;
58795
- if (category === "*") return catchAll ? CATCH_ALL_PATTERN$10 : null;
59189
+ if (category === "*") return catchAll ? CATCH_ALL_PATTERN$11 : null;
58796
59190
  const tool = Object.hasOwn(CATEGORY_TO_COMMANDCODE_TOOL, category) ? CATEGORY_TO_COMMANDCODE_TOOL[category] : void 0;
58797
59191
  if (tool === void 0) return null;
58798
59192
  return catchAll ? tool : `${tool}(${pattern})`;
@@ -58828,7 +59222,7 @@ function splitCommandcodeRule(rule) {
58828
59222
  }
58829
59223
  /** Whether a specifier means "no specifier" to Command Code: only `` and `*` do. */
58830
59224
  function isCatchAllSpecifier(inner) {
58831
- return inner === "" || inner === CATCH_ALL_PATTERN$10;
59225
+ return inner === "" || inner === CATCH_ALL_PATTERN$11;
58832
59226
  }
58833
59227
  /**
58834
59228
  * Read an MCP-shaped rule the way Command Code does (`command-code` 1.54.0,
@@ -58862,34 +59256,34 @@ function parseMcpCommandcodeRule({ tool, inner }) {
58862
59256
  if (server.length === 0) return null;
58863
59257
  const everyServer = {
58864
59258
  category: "mcp",
58865
- pattern: CATCH_ALL_PATTERN$10,
59259
+ pattern: CATCH_ALL_PATTERN$11,
58866
59260
  notInAllow: true
58867
59261
  };
58868
59262
  if (tool.startsWith(MCP_CANONICAL_PREFIX$6)) {
58869
- if (server === CATCH_ALL_PATTERN$10) return everyServer;
58870
- if (server.includes(CATCH_ALL_PATTERN$10)) return null;
59263
+ if (server === CATCH_ALL_PATTERN$11) return everyServer;
59264
+ if (server.includes(CATCH_ALL_PATTERN$11)) return null;
58871
59265
  return {
58872
- category: rest === "" || rest === CATCH_ALL_PATTERN$10 ? `${MCP_CANONICAL_PREFIX$6}${server}` : `${MCP_CANONICAL_PREFIX$6}${remainder}`,
58873
- pattern: CATCH_ALL_PATTERN$10,
59266
+ category: rest === "" || rest === CATCH_ALL_PATTERN$11 ? `${MCP_CANONICAL_PREFIX$6}${server}` : `${MCP_CANONICAL_PREFIX$6}${remainder}`,
59267
+ pattern: CATCH_ALL_PATTERN$11,
58874
59268
  notInAllow: false
58875
59269
  };
58876
59270
  }
58877
59271
  const category = `${MCP_CANONICAL_PREFIX$6}${remainder}`;
58878
- const namesTool = separator !== -1 && rest.length > 0 && !remainder.includes(CATCH_ALL_PATTERN$10);
59272
+ const namesTool = separator !== -1 && rest.length > 0 && !remainder.includes(CATCH_ALL_PATTERN$11);
58879
59273
  if (!isCatchAllSpecifier(inner)) return namesTool ? {
58880
59274
  category,
58881
59275
  pattern: inner,
58882
59276
  notInAllow: true
58883
59277
  } : null;
58884
- if (remainder === CATCH_ALL_PATTERN$10) return everyServer;
58885
- if (rest === CATCH_ALL_PATTERN$10 && !server.includes(CATCH_ALL_PATTERN$10)) return {
59278
+ if (remainder === CATCH_ALL_PATTERN$11) return everyServer;
59279
+ if (rest === CATCH_ALL_PATTERN$11 && !server.includes(CATCH_ALL_PATTERN$11)) return {
58886
59280
  category: `${MCP_CANONICAL_PREFIX$6}${server}`,
58887
- pattern: CATCH_ALL_PATTERN$10,
59281
+ pattern: CATCH_ALL_PATTERN$11,
58888
59282
  notInAllow: true
58889
59283
  };
58890
59284
  return namesTool ? {
58891
59285
  category,
58892
- pattern: CATCH_ALL_PATTERN$10,
59286
+ pattern: CATCH_ALL_PATTERN$11,
58893
59287
  notInAllow: false
58894
59288
  } : null;
58895
59289
  }
@@ -58913,14 +59307,14 @@ function friendlyToolRule({ category, inner }) {
58913
59307
  if (category === "bash") {
58914
59308
  if (isCatchAllSpecifier(inner)) return {
58915
59309
  category,
58916
- pattern: CATCH_ALL_PATTERN$10,
59310
+ pattern: CATCH_ALL_PATTERN$11,
58917
59311
  notInAllow: false
58918
59312
  };
58919
59313
  const normalized = inner.trim().replace(/\s+/g, " ");
58920
59314
  if (normalized === "" || normalized === ":*") return null;
58921
59315
  return isCatchAllSpecifier(normalized) ? {
58922
59316
  category,
58923
- pattern: CATCH_ALL_PATTERN$10,
59317
+ pattern: CATCH_ALL_PATTERN$11,
58924
59318
  notInAllow: true
58925
59319
  } : {
58926
59320
  category,
@@ -58930,7 +59324,7 @@ function friendlyToolRule({ category, inner }) {
58930
59324
  }
58931
59325
  if (isCatchAllSpecifier(inner)) return {
58932
59326
  category,
58933
- pattern: CATCH_ALL_PATTERN$10,
59327
+ pattern: CATCH_ALL_PATTERN$11,
58934
59328
  notInAllow: false
58935
59329
  };
58936
59330
  return isCatchAllSpecifier(inner.trim()) ? null : {
@@ -58949,9 +59343,9 @@ function friendlyToolRule({ category, inner }) {
58949
59343
  */
58950
59344
  function parseCommandcodeRule(rule) {
58951
59345
  const { tool, inner } = splitCommandcodeRule(rule);
58952
- if (tool === CATCH_ALL_PATTERN$10) return isCatchAllSpecifier(inner) ? {
59346
+ if (tool === CATCH_ALL_PATTERN$11) return isCatchAllSpecifier(inner) ? {
58953
59347
  category: "*",
58954
- pattern: CATCH_ALL_PATTERN$10,
59348
+ pattern: CATCH_ALL_PATTERN$11,
58955
59349
  notInAllow: true
58956
59350
  } : null;
58957
59351
  const lowered = tool.toLowerCase();
@@ -59021,7 +59415,7 @@ function preservedRules({ existingPermissions, key, managedCategories, written,
59021
59415
  }).filter((rule) => {
59022
59416
  const parsed = parseCommandcodeRule(rule);
59023
59417
  if (parsed === null || !managedCategories.has(parsed.category)) return true;
59024
- const replacement = written.get(`${parsed.category}(${parsed.pattern})`) ?? written.get(`${parsed.category}(${CATCH_ALL_PATTERN$10})`);
59418
+ const replacement = written.get(`${parsed.category}(${parsed.pattern})`) ?? written.get(`${parsed.category}(${CATCH_ALL_PATTERN$11})`);
59025
59419
  if (key !== "allow" && isStricterAction({
59026
59420
  action: key,
59027
59421
  existing: replacement
@@ -59078,7 +59472,7 @@ function buildCommandcodeRuleLists({ config, existingPermissions, logger }) {
59078
59472
  const ranked = /* @__PURE__ */ new Map();
59079
59473
  const managedCategories = /* @__PURE__ */ new Set();
59080
59474
  for (const [category, rules] of Object.entries(permission)) {
59081
- const categoryRule = buildCommandcodeRule(category, CATCH_ALL_PATTERN$10);
59475
+ const categoryRule = buildCommandcodeRule(category, CATCH_ALL_PATTERN$11);
59082
59476
  const managedCategory = categoryRule === null ? null : parseCommandcodeRule(categoryRule);
59083
59477
  if (managedCategory !== null) managedCategories.add(managedCategory.category);
59084
59478
  for (const [pattern, action] of Object.entries(rules)) {
@@ -59349,7 +59743,7 @@ function parseCommandcodeSettings({ fileContent, relativePath }) {
59349
59743
  //#endregion
59350
59744
  //#region src/features/permissions/continue-permissions.ts
59351
59745
  const CONTINUE_GLOBAL_ONLY_MESSAGE = "Continue permissions are global-only; use --global to sync ~/.continue/permissions.yaml";
59352
- const CATCH_ALL_PATTERN$9 = "*";
59746
+ const CATCH_ALL_PATTERN$10 = "*";
59353
59747
  /**
59354
59748
  * A run of `*` (`**`, `***`) is the catch-all spelled another way: Continue
59355
59749
  * would match `Tool(**)` against every argument just as the bare tool name
@@ -59357,7 +59751,7 @@ const CATCH_ALL_PATTERN$9 = "*";
59357
59751
  * the bare entry instead of being skipped as a pattern-specific rule.
59358
59752
  */
59359
59753
  function normalizeCatchAllPattern$1(pattern) {
59360
- return /^\*+$/.test(pattern) ? CATCH_ALL_PATTERN$9 : pattern;
59754
+ return /^\*+$/.test(pattern) ? CATCH_ALL_PATTERN$10 : pattern;
59361
59755
  }
59362
59756
  const CANONICAL_TO_CONTINUE_TOOL_NAMES = {
59363
59757
  bash: "Bash",
@@ -59394,7 +59788,7 @@ function toCanonicalToolName$6(continueName) {
59394
59788
  * `Tool(pattern)`.
59395
59789
  */
59396
59790
  function buildContinuePermissionEntry(toolName, pattern) {
59397
- return pattern === CATCH_ALL_PATTERN$9 ? toolName : `${toolName}(${pattern})`;
59791
+ return pattern === CATCH_ALL_PATTERN$10 ? toolName : `${toolName}(${pattern})`;
59398
59792
  }
59399
59793
  /**
59400
59794
  * Split a permissions.yaml entry into its tool name and argument pattern, or
@@ -59408,7 +59802,7 @@ function parseContinuePermissionEntry(entry) {
59408
59802
  if (toolName === "") return;
59409
59803
  return {
59410
59804
  toolName,
59411
- pattern: pattern === "" ? CATCH_ALL_PATTERN$9 : pattern
59805
+ pattern: pattern === "" ? CATCH_ALL_PATTERN$10 : pattern
59412
59806
  };
59413
59807
  }
59414
59808
  /**
@@ -59582,7 +59976,7 @@ function convertRulesyncToContinuePermissions({ config, logger }) {
59582
59976
  logger?.warn(`Continue permissions.yaml cannot hold a parenthesis inside a pattern, so the "${action}" rule for "${category}" (pattern ${quoteValueForWarning(pattern)}) was skipped.`);
59583
59977
  continue;
59584
59978
  }
59585
- if (toolName === "*" && pattern !== CATCH_ALL_PATTERN$9) {
59979
+ if (toolName === "*" && pattern !== CATCH_ALL_PATTERN$10) {
59586
59980
  logger?.warn(`Continue permissions.yaml cannot scope a pattern to every tool, so the "${action}" rule for "*" (pattern ${quoteValueForWarning(pattern)}) was skipped.`);
59587
59981
  continue;
59588
59982
  }
@@ -59950,14 +60344,14 @@ var CopilotcliPermissions = class CopilotcliPermissions extends ToolPermissions
59950
60344
  };
59951
60345
  //#endregion
59952
60346
  //#region src/features/permissions/crush-permissions.ts
59953
- const CATCH_ALL_PATTERN$8 = "*";
60347
+ const CATCH_ALL_PATTERN$9 = "*";
59954
60348
  /**
59955
60349
  * A run of `*` (`**`, `***`) is the catch-all spelled another way: Crush has no
59956
60350
  * argument patterns at all, so it is the only pattern a rule can carry and
59957
60351
  * still be written.
59958
60352
  */
59959
60353
  function normalizeCatchAllPattern(pattern) {
59960
- return /^\*+$/.test(pattern) ? CATCH_ALL_PATTERN$8 : pattern;
60354
+ return /^\*+$/.test(pattern) ? CATCH_ALL_PATTERN$9 : pattern;
59961
60355
  }
59962
60356
  const CANONICAL_TO_CRUSH_TOOL_NAMES = {
59963
60357
  bash: "bash",
@@ -60156,7 +60550,7 @@ function resolveCategoryAction({ category, toolName, rules, logger }) {
60156
60550
  for (const [rawPattern, action] of Object.entries(rules)) {
60157
60551
  if (isPrototypePollutionKey(rawPattern)) continue;
60158
60552
  const pattern = normalizeCatchAllPattern(rawPattern);
60159
- if (pattern === CATCH_ALL_PATTERN$8) {
60553
+ if (pattern === CATCH_ALL_PATTERN$9) {
60160
60554
  catchAllAction = action;
60161
60555
  continue;
60162
60556
  }
@@ -60229,11 +60623,11 @@ function convertCrushListsToRulesync({ allowed, disabled }) {
60229
60623
  };
60230
60624
  for (const entry of disabled) {
60231
60625
  if (entry === "" || isPrototypePollutionKey(entry)) continue;
60232
- bucketFor(toCanonicalToolName$5(entry))[CATCH_ALL_PATTERN$8] = "deny";
60626
+ bucketFor(toCanonicalToolName$5(entry))[CATCH_ALL_PATTERN$9] = "deny";
60233
60627
  }
60234
60628
  for (const entry of allowed) {
60235
60629
  if (entry === "" || entry.includes(":") || isPrototypePollutionKey(entry)) continue;
60236
- bucketFor(toCanonicalToolName$5(entry))[CATCH_ALL_PATTERN$8] ??= "allow";
60630
+ bucketFor(toCanonicalToolName$5(entry))[CATCH_ALL_PATTERN$9] ??= "allow";
60237
60631
  }
60238
60632
  return { permission };
60239
60633
  }
@@ -62098,7 +62492,7 @@ function convertFactorydroidToRulesyncPermissions({ allow, deny }) {
62098
62492
  //#region src/features/permissions/goose-permissions.ts
62099
62493
  const GOOSE_GLOBAL_ONLY_MESSAGE = "Goose permissions are global-only; use --global to sync ~/.config/goose/permission.yaml";
62100
62494
  const GOOSE_USER_KEY = "user";
62101
- const CATCH_ALL_PATTERN$7 = "*";
62495
+ const CATCH_ALL_PATTERN$8 = "*";
62102
62496
  const RULESYNC_TO_GOOSE_TOOL_NAME = { bash: "shell" };
62103
62497
  const GOOSE_TO_RULESYNC_TOOL_NAMES = {
62104
62498
  shell: ["bash"],
@@ -62110,7 +62504,7 @@ const ACTION_TO_GOOSE_LIST = {
62110
62504
  ask: "ask_before",
62111
62505
  deny: "never_allow"
62112
62506
  };
62113
- const ACTION_RANK$3 = {
62507
+ const ACTION_RANK$4 = {
62114
62508
  allow: 0,
62115
62509
  ask: 1,
62116
62510
  deny: 2
@@ -62262,14 +62656,14 @@ function convertRulesyncToGoosePermissionConfig({ config, logger }) {
62262
62656
  for (const [category, rules] of Object.entries(honorAllToolsOnBash(config.permission))) {
62263
62657
  const toolName = RULESYNC_TO_GOOSE_TOOL_NAME[category] ?? category;
62264
62658
  for (const [pattern, action] of Object.entries(rules)) {
62265
- if (pattern !== CATCH_ALL_PATTERN$7) {
62659
+ if (pattern !== CATCH_ALL_PATTERN$8) {
62266
62660
  logger?.warn(`Goose permission.yaml lists whole tool names, so the per-pattern "${action}" rule for "${category}" (pattern "${pattern}") cannot be expressed and was skipped.`);
62267
62661
  continue;
62268
62662
  }
62269
62663
  const previous = assigned.get(toolName);
62270
62664
  if (previous === action) continue;
62271
62665
  if (previous !== void 0) {
62272
- const stricter = ACTION_RANK$3[action] > ACTION_RANK$3[previous] ? action : previous;
62666
+ const stricter = ACTION_RANK$4[action] > ACTION_RANK$4[previous] ? action : previous;
62273
62667
  logger?.warn(`Goose maps "${category}" onto the "${toolName}" tool, which already has a conflicting permission ("${previous}"). Keeping the stricter "${stricter}" (deny > ask > allow).`);
62274
62668
  if (stricter === previous) continue;
62275
62669
  const previousList = lists[ACTION_TO_GOOSE_LIST[previous]];
@@ -62293,7 +62687,7 @@ function convertGoosePermissionConfigToRulesync(userPermission) {
62293
62687
  const action = GOOSE_LIST_TO_ACTION[key];
62294
62688
  for (const toolName of toolNames) for (const category of GOOSE_TO_RULESYNC_TOOL_NAMES[toolName] ?? [toolName]) {
62295
62689
  permission[category] ??= {};
62296
- permission[category][CATCH_ALL_PATTERN$7] = action;
62690
+ permission[category][CATCH_ALL_PATTERN$8] = action;
62297
62691
  }
62298
62692
  }
62299
62693
  return { permission };
@@ -62304,7 +62698,7 @@ const GROKCLI_UI_KEY = "ui";
62304
62698
  const GROKCLI_PERMISSION_MODE_KEY = "permission_mode";
62305
62699
  const GROKCLI_PERMISSION_KEY = "permission";
62306
62700
  const GROKCLI_AUTO_PERMISSION_MODE = "auto";
62307
- const CATCH_ALL_PATTERN$6 = "*";
62701
+ const CATCH_ALL_PATTERN$7 = "*";
62308
62702
  const MCP_CANONICAL_PREFIX$3 = "mcp__";
62309
62703
  const CATEGORY_TO_GROK_TOOL = {
62310
62704
  bash: "Bash",
@@ -62329,6 +62723,8 @@ const GROK_TOOL_TO_CATEGORY = {
62329
62723
  ...Object.fromEntries(["SendSubagentMessage", "SendAgentMessage"].map((tool) => [tool, "agent"]))
62330
62724
  };
62331
62725
  const GROK_MCP_TOOL = "MCPTool";
62726
+ const GROK_ALL_TOOLS_ENTRY = "*";
62727
+ const GROK_VERBOSE_ANY_TOOL = "any";
62332
62728
  const GROK_TOOL_TO_CATEGORY_LOWER = {
62333
62729
  ...Object.fromEntries(Object.entries(GROK_TOOL_TO_CATEGORY).map(([tool, category]) => [tool.toLowerCase(), category])),
62334
62730
  agent_message: "agent"
@@ -62343,22 +62739,26 @@ const GROKCLI_PERMISSION_RULES_KEY = "rules";
62343
62739
  * Scoped MCP categories (`mcp__<remainder>`) fold their address into the
62344
62740
  * parentheses (`MCPTool(<remainder>)`); the bare `mcp` category becomes
62345
62741
  * `MCPTool`. For every other tool, a `*` pattern emits the bare tool name and a
62346
- * concrete pattern emits `Tool(pattern)`.
62742
+ * concrete pattern emits `Tool(pattern)`. The all-tools `*` category becomes
62743
+ * Grok's bare `*` rule for its catch-all pattern only; a narrower pattern
62744
+ * under it has no Grok spelling and returns `null`.
62347
62745
  */
62348
62746
  function buildGrokEntry(category, pattern) {
62747
+ if (category === "*") return pattern === CATCH_ALL_PATTERN$7 ? GROK_ALL_TOOLS_ENTRY : null;
62349
62748
  if (category.startsWith(MCP_CANONICAL_PREFIX$3)) {
62350
62749
  const remainder = category.slice(5);
62351
62750
  return remainder.length > 0 ? `${GROK_MCP_TOOL}(${remainder})` : GROK_MCP_TOOL;
62352
62751
  }
62353
- if (category === "mcp") return pattern === CATCH_ALL_PATTERN$6 || pattern === "" ? GROK_MCP_TOOL : `${GROK_MCP_TOOL}(${pattern})`;
62752
+ if (category === "mcp") return pattern === CATCH_ALL_PATTERN$7 || pattern === "" ? GROK_MCP_TOOL : `${GROK_MCP_TOOL}(${pattern})`;
62354
62753
  const tool = CATEGORY_TO_GROK_TOOL[category];
62355
62754
  if (tool === void 0) return null;
62356
- return pattern === CATCH_ALL_PATTERN$6 || pattern === "" ? tool : `${tool}(${pattern})`;
62755
+ return pattern === CATCH_ALL_PATTERN$7 || pattern === "" ? tool : `${tool}(${pattern})`;
62357
62756
  }
62358
62757
  /**
62359
62758
  * Parse a Grok Claude-style permission entry back into a canonical category +
62360
- * pattern. Entries without parentheses are treated as catch-all (`*`). Returns
62361
- * `null` for tool prefixes with no canonical equivalent (e.g. `any`).
62759
+ * pattern. Entries without parentheses are treated as catch-all (`*`), and the
62760
+ * bare `*` rule becomes the all-tools `*` category. Returns `null` for tool
62761
+ * prefixes with no canonical equivalent.
62362
62762
  */
62363
62763
  function parseGrokEntry(entry) {
62364
62764
  const trimmed = entry.trim();
@@ -62372,19 +62772,23 @@ function parseGrokEntry(entry) {
62372
62772
  tool = trimmed.slice(0, parenIndex);
62373
62773
  inner = trimmed.slice(parenIndex + 1, -1).trim();
62374
62774
  }
62775
+ if (parenIndex === -1 && tool === GROK_ALL_TOOLS_ENTRY) return {
62776
+ category: "*",
62777
+ pattern: CATCH_ALL_PATTERN$7
62778
+ };
62375
62779
  if (parenIndex === -1 && tool.startsWith(MCP_CANONICAL_PREFIX$3)) return parseClaudeStyleMcpEntry(tool);
62376
62780
  if (tool === GROK_MCP_TOOL) return inner.length > 0 ? {
62377
62781
  category: `${MCP_CANONICAL_PREFIX$3}${inner}`,
62378
- pattern: CATCH_ALL_PATTERN$6
62782
+ pattern: CATCH_ALL_PATTERN$7
62379
62783
  } : {
62380
62784
  category: "mcp",
62381
- pattern: CATCH_ALL_PATTERN$6
62785
+ pattern: CATCH_ALL_PATTERN$7
62382
62786
  };
62383
62787
  const category = GROK_TOOL_TO_CATEGORY[tool];
62384
62788
  if (category === void 0) return null;
62385
62789
  return {
62386
62790
  category,
62387
- pattern: inner.length > 0 ? inner : CATCH_ALL_PATTERN$6
62791
+ pattern: inner.length > 0 ? inner : CATCH_ALL_PATTERN$7
62388
62792
  };
62389
62793
  }
62390
62794
  /**
@@ -62401,14 +62805,14 @@ function parseGrokEntry(entry) {
62401
62805
  function parseClaudeStyleMcpEntry(entry) {
62402
62806
  const rest = entry.slice(5);
62403
62807
  if (rest.length === 0) return null;
62404
- if (rest === CATCH_ALL_PATTERN$6) return {
62808
+ if (rest === CATCH_ALL_PATTERN$7) return {
62405
62809
  category: "mcp",
62406
- pattern: CATCH_ALL_PATTERN$6
62810
+ pattern: CATCH_ALL_PATTERN$7
62407
62811
  };
62408
62812
  const address = rest.includes("__") ? rest : `${rest}__*`;
62409
62813
  return {
62410
62814
  category: `${MCP_CANONICAL_PREFIX$3}${address}`,
62411
- pattern: CATCH_ALL_PATTERN$6
62815
+ pattern: CATCH_ALL_PATTERN$7
62412
62816
  };
62413
62817
  }
62414
62818
  /**
@@ -62429,15 +62833,20 @@ function parseGrokRule(rule) {
62429
62833
  if (typeof tool !== "string") return null;
62430
62834
  if (pattern !== void 0 && typeof pattern !== "string") return null;
62431
62835
  const trimmedPattern = pattern?.trim() ?? "";
62432
- const resolvedPattern = trimmedPattern.length > 0 ? trimmedPattern : CATCH_ALL_PATTERN$6;
62836
+ const resolvedPattern = trimmedPattern.length > 0 ? trimmedPattern : CATCH_ALL_PATTERN$7;
62433
62837
  const lowerTool = tool.trim().toLowerCase();
62434
- if (GROK_MCP_TOOL_ALIASES.has(lowerTool)) return resolvedPattern === CATCH_ALL_PATTERN$6 ? {
62838
+ if (lowerTool === GROK_VERBOSE_ANY_TOOL) return resolvedPattern === CATCH_ALL_PATTERN$7 ? {
62839
+ category: "*",
62840
+ pattern: CATCH_ALL_PATTERN$7,
62841
+ action
62842
+ } : null;
62843
+ if (GROK_MCP_TOOL_ALIASES.has(lowerTool)) return resolvedPattern === CATCH_ALL_PATTERN$7 ? {
62435
62844
  category: "mcp",
62436
- pattern: CATCH_ALL_PATTERN$6,
62845
+ pattern: CATCH_ALL_PATTERN$7,
62437
62846
  action
62438
62847
  } : {
62439
62848
  category: `${MCP_CANONICAL_PREFIX$3}${resolvedPattern}`,
62440
- pattern: CATCH_ALL_PATTERN$6,
62849
+ pattern: CATCH_ALL_PATTERN$7,
62441
62850
  action
62442
62851
  };
62443
62852
  if (!Object.hasOwn(GROK_TOOL_TO_CATEGORY_LOWER, lowerTool)) return null;
@@ -62467,7 +62876,10 @@ function parseGrokRule(rule) {
62467
62876
  * subagent rather than its launch, and whose pattern is a subagent id
62468
62877
  * rather than a subagent type); `mcp__*` maps to `MCPTool(...)` (a scoped
62469
62878
  * MCP category folds its address into the parentheses, so a non-`*`
62470
- * argument pattern on it is not represented). The one category with no Grok tool (`notebookedit`) is
62879
+ * argument pattern on it is not represented); the all-tools `*` category
62880
+ * maps to Grok's bare `*` rule (which matches every tool) for its
62881
+ * catch-all pattern, while a narrower `*` pattern is skipped with a
62882
+ * warning. The one category with no Grok tool (`notebookedit`) is
62471
62883
  * skipped (with a warning when it carries a `deny` rule, to surface the
62472
62884
  * gap). When two canonical rules collapse onto the same Grok
62473
62885
  * entry with different actions (e.g. `edit` allow + `write` deny → `Edit`),
@@ -62478,7 +62890,9 @@ function parseGrokRule(rule) {
62478
62890
  * verbose `[[permission.rules]]` tables
62479
62891
  * (`{ action = "allow", tool = "bash", pattern = "git *" }`), whose `tool`
62480
62892
  * field is matched case-insensitively against the same tool table, plus
62481
- * the documented `mcp` alias for the compact form's `MCPTool`. Rules
62893
+ * the documented `mcp` alias for the compact form's `MCPTool`. A bare `*`
62894
+ * entry and a tool-wide verbose `any` rule both import as the all-tools
62895
+ * `*` category. Rules
62482
62896
  * from the two forms are merged with the same `deny > ask > allow`
62483
62897
  * precedence. When neither form carries a rule (older configs), the coarse
62484
62898
  * `[ui] permission_mode` is used as a fallback. Generate always emits the
@@ -62503,7 +62917,7 @@ function parseGrokRule(rule) {
62503
62917
  * tools it models and (in global scope) the `[ui] permission_mode` value, while
62504
62918
  * every other key
62505
62919
  * (e.g. `[mcp_servers]`, `[permission] rules`, `[sandbox]`) and any user-authored
62506
- * entries for tool prefixes rulesync cannot model (e.g. `any`) are preserved.
62920
+ * entries for tool prefixes rulesync cannot model (e.g. a pattern-only entry such as `src/**`) are preserved.
62507
62921
  * The file is never deleted.
62508
62922
  */
62509
62923
  var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
@@ -62585,7 +62999,7 @@ var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
62585
62999
  throw new Error(`Failed to parse Grok config.toml content in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
62586
63000
  }
62587
63001
  const fineGrained = parseGrokPermissionArrays(isRecord$1(parsed[GROKCLI_PERMISSION_KEY]) ? parsed[GROKCLI_PERMISSION_KEY] : {});
62588
- const rulesyncConfig = fineGrained ? { permission: fineGrained } : { permission: { bash: { [CATCH_ALL_PATTERN$6]: legacyModeAction(parsed) } } };
63002
+ const rulesyncConfig = fineGrained ? { permission: fineGrained } : { permission: { bash: { [CATCH_ALL_PATTERN$7]: legacyModeAction(parsed) } } };
62589
63003
  return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(rulesyncConfig, null, 2) });
62590
63004
  }
62591
63005
  validate() {
@@ -62605,14 +63019,14 @@ var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
62605
63019
  });
62606
63020
  }
62607
63021
  };
62608
- const ACTION_RANK$2 = {
63022
+ const ACTION_RANK$3 = {
62609
63023
  allow: 0,
62610
63024
  ask: 1,
62611
63025
  deny: 2
62612
63026
  };
62613
63027
  /**
62614
63028
  * Collect user-authored entries from an existing `[permission]` array whose
62615
- * tool prefix rulesync cannot model (e.g. `any`). Such entries are
63029
+ * tool prefix rulesync cannot model (e.g. a pattern-only entry such as `src/**`). Such entries are
62616
63030
  * preserved verbatim so replacing the arrays does not silently drop them
62617
63031
  * (mirrors the Cursor adapter's preservation of unmanaged types).
62618
63032
  */
@@ -62631,13 +63045,26 @@ function buildGrokPermissionArrays(config, existingPermission, logger) {
62631
63045
  for (const [category, rules] of Object.entries(honorAllToolsOnBash(config.permission))) for (const [pattern, action] of Object.entries(rules)) {
62632
63046
  const entry = buildGrokEntry(category, pattern);
62633
63047
  if (entry === null) {
63048
+ if (category === "*") {
63049
+ logger?.warn(`Grok CLI permissions: skipping the all-tools '*' rule '${pattern}' — Grok's bare '*' rule takes no pattern. Write it under the categories it is meant for (for example 'bash' or 'read').`);
63050
+ continue;
63051
+ }
62634
63052
  if (action === "deny" && logger) logger.warn(`Grok CLI has no permission tool for the '${category}' category; its 'deny' rule could not be represented and was skipped.`);
62635
63053
  continue;
62636
63054
  }
62637
63055
  const existing = ranked.get(entry);
62638
63056
  if (existing !== void 0 && existing !== action) logger?.warn(`Grok permission entry '${entry}' received conflicting actions ('${existing}' and '${action}'); keeping the stricter one (deny > ask > allow).`);
62639
- if (existing === void 0 || ACTION_RANK$2[action] > ACTION_RANK$2[existing]) ranked.set(entry, action);
63057
+ if (existing === void 0 || ACTION_RANK$3[action] > ACTION_RANK$3[existing]) ranked.set(entry, action);
62640
63058
  }
63059
+ warnRemovedAllToolsRestrictions({
63060
+ existingPermission,
63061
+ ranked,
63062
+ logger
63063
+ });
63064
+ warnEntriesShadowedByAllTools({
63065
+ ranked,
63066
+ logger
63067
+ });
62641
63068
  const allow = unmanagedEntries(existingPermission, "allow");
62642
63069
  const deny = unmanagedEntries(existingPermission, "deny");
62643
63070
  const ask = unmanagedEntries(existingPermission, "ask");
@@ -62651,6 +63078,38 @@ function buildGrokPermissionArrays(config, existingPermission, logger) {
62651
63078
  };
62652
63079
  }
62653
63080
  /**
63081
+ * Warn when generate drops an existing bare `*` `deny`/`ask` entry without
63082
+ * writing an equally strict one back. Such an entry is Rulesync-owned (it maps
63083
+ * to the all-tools `*` category), so a config without a matching canonical
63084
+ * rule removes it — which silently lifts a restriction on every tool.
63085
+ */
63086
+ function warnRemovedAllToolsRestrictions({ existingPermission, ranked, logger }) {
63087
+ if (!logger) return;
63088
+ const generated = ranked.get(GROK_ALL_TOOLS_ENTRY);
63089
+ for (const action of ["deny", "ask"]) {
63090
+ const entries = existingPermission[action];
63091
+ if (!isStringArray$2(entries)) continue;
63092
+ if (!entries.some((entry) => parseGrokEntry(entry)?.category === "*")) continue;
63093
+ if (generated !== void 0 && ACTION_RANK$3[generated] >= ACTION_RANK$3[action]) continue;
63094
+ logger.warn(`Grok CLI permissions: removing the existing bare '*' entry from the '${action}' array — Rulesync now manages it through the all-tools '*' category, and the canonical config has no '*' '${action}' rule. Add "*": { "*": "${action}" } to keep restricting every tool.`);
63095
+ }
63096
+ }
63097
+ /**
63098
+ * Warn about entries that a bare `*` `deny`/`ask` makes ineffective: Grok
63099
+ * evaluates by severity (`deny > ask > allow`), so a specific `allow` (and,
63100
+ * under a `*` deny, a specific `ask`) never takes effect next to it. Shell
63101
+ * rules are already folded by `honorAllToolsOnBash`, so this covers the other
63102
+ * tools.
63103
+ */
63104
+ function warnEntriesShadowedByAllTools({ ranked, logger }) {
63105
+ if (!logger) return;
63106
+ const allTools = ranked.get(GROK_ALL_TOOLS_ENTRY);
63107
+ if (allTools === void 0 || allTools === "allow") return;
63108
+ const shadowed = [...ranked].filter(([entry, action]) => entry !== GROK_ALL_TOOLS_ENTRY && ACTION_RANK$3[action] < ACTION_RANK$3[allTools]).map(([entry]) => entry).toSorted();
63109
+ if (shadowed.length === 0) return;
63110
+ logger.warn(`Grok CLI permissions: the bare '*' '${allTools}' rule applies to every tool, and Grok evaluates deny > ask > allow, so these less strict entries never take effect: ${shadowed.join(", ")}.`);
63111
+ }
63112
+ /**
62654
63113
  * Parse Grok's `[permission]` section back into a canonical permission map,
62655
63114
  * reading both supported forms: the compact `allow`/`deny`/`ask` arrays of
62656
63115
  * Claude-style entries and the verbose `[[permission.rules]]` tables
@@ -62669,7 +63128,7 @@ function parseGrokPermissionArrays(permission) {
62669
63128
  const record = ({ category, pattern, action }) => {
62670
63129
  const bucket = result[category] ??= {};
62671
63130
  const existing = bucket[pattern];
62672
- if (existing === void 0 || ACTION_RANK$2[action] > ACTION_RANK$2[existing]) bucket[pattern] = action;
63131
+ if (existing === void 0 || ACTION_RANK$3[action] > ACTION_RANK$3[existing]) bucket[pattern] = action;
62673
63132
  };
62674
63133
  const applyEntries = (entries, action) => {
62675
63134
  for (const entry of entries ?? []) {
@@ -63073,7 +63532,7 @@ const JUNIE_GROUP_TO_CANONICAL = {
63073
63532
  mcpTools: "mcp",
63074
63533
  readOutsideProject: "read"
63075
63534
  };
63076
- function isPermissionAction$1(value) {
63535
+ function isPermissionAction$2(value) {
63077
63536
  return PermissionActionSchema.safeParse(value).success;
63078
63537
  }
63079
63538
  /**
@@ -63259,7 +63718,7 @@ function convertJunieToRulesyncPermissions({ allowlist }) {
63259
63718
  for (const rule of Array.isArray(ruleSet.rules) ? ruleSet.rules : []) {
63260
63719
  if (!rule || typeof rule !== "object") continue;
63261
63720
  const pattern = typeof rule.pattern === "string" ? rule.pattern : typeof rule.prefix === "string" ? rule.prefix : void 0;
63262
- if (pattern === void 0 || !isPermissionAction$1(rule.action)) continue;
63721
+ if (pattern === void 0 || !isPermissionAction$2(rule.action)) continue;
63263
63722
  (permission[category] ??= {})[pattern] = rule.action;
63264
63723
  }
63265
63724
  }
@@ -64218,6 +64677,367 @@ function rulesFromArrays(settings, allowKey, denyKey) {
64218
64677
  return rules;
64219
64678
  }
64220
64679
  //#endregion
64680
+ //#region src/features/permissions/kiro-capability-permissions.ts
64681
+ const CATCH_ALL_PATTERN$6 = "*";
64682
+ const CANONICAL_TO_KIRO_CAPABILITY = {
64683
+ bash: "shell",
64684
+ read: "fs_read",
64685
+ edit: "fs_write",
64686
+ write: "fs_write",
64687
+ webfetch: "web_fetch",
64688
+ websearch: "web_search"
64689
+ };
64690
+ const KIRO_CAPABILITY_TO_CANONICAL = {
64691
+ shell: "bash",
64692
+ fs_read: "read",
64693
+ fs_write: "write",
64694
+ web_fetch: "webfetch",
64695
+ web_search: "websearch"
64696
+ };
64697
+ const MANAGED_CAPABILITIES = new Set(Object.keys(KIRO_CAPABILITY_TO_CANONICAL));
64698
+ const ACTION_RANK$2 = {
64699
+ allow: 0,
64700
+ ask: 1,
64701
+ deny: 2
64702
+ };
64703
+ function isPermissionAction$1(value) {
64704
+ return value === "allow" || value === "ask" || value === "deny";
64705
+ }
64706
+ /**
64707
+ * Whether an existing `permissions.yaml` rule can be read back into the
64708
+ * canonical model: a rule for a capability rulesync maps, with no `exclude`
64709
+ * list (the canonical model has no exclusion). Used on import only.
64710
+ */
64711
+ function isImportableRule(rule) {
64712
+ return isRecord$1(rule) && typeof rule.capability === "string" && MANAGED_CAPABILITIES.has(rule.capability) && rule.exclude === void 0;
64713
+ }
64714
+ /**
64715
+ * Key identifying one (capability, pattern) pair. The catch-all `*` stands for
64716
+ * a match-less rule.
64717
+ */
64718
+ function rulePatternKey(capability, pattern) {
64719
+ return `${capability}\u0000${pattern}`;
64720
+ }
64721
+ /**
64722
+ * Classify one existing (capability, pattern, effect) entry against the
64723
+ * generated rules: an exact duplicate (same effect) is replaced by the
64724
+ * generated rule; a different effect is a conflict and is kept; anything
64725
+ * rulesync does not emit is unrelated and kept.
64726
+ */
64727
+ function classifyExistingEntry({ emitted, capability, pattern, effect }) {
64728
+ const effects = emitted.get(rulePatternKey(capability, pattern));
64729
+ if (effects === void 0) return "unrelated";
64730
+ return isPermissionAction$1(effect) && effects.has(effect) ? "duplicate" : "conflict";
64731
+ }
64732
+ /**
64733
+ * Merge one existing rule against the generated entries (see
64734
+ * {@link mergeKiroCapabilityRules}). Returns the rule to keep (possibly with a
64735
+ * narrowed `match`), or `undefined` when every entry is an exact duplicate, and
64736
+ * how many entries rulesync does not generate.
64737
+ */
64738
+ function mergeExistingRule({ rule, emitted, logger }) {
64739
+ if (!isRecord$1(rule) || typeof rule.capability !== "string" || rule.exclude !== void 0 || !MANAGED_CAPABILITIES.has(rule.capability) || rule.match !== void 0 && !isStringArray$2(rule.match)) return {
64740
+ rule,
64741
+ unrelatedEntries: 0
64742
+ };
64743
+ const capability = rule.capability;
64744
+ const isCatchAll = rule.match === void 0;
64745
+ const patterns = isStringArray$2(rule.match) ? rule.match : [CATCH_ALL_PATTERN$6];
64746
+ if (patterns.length === 0) return {
64747
+ rule,
64748
+ unrelatedEntries: 0
64749
+ };
64750
+ const remaining = [];
64751
+ let unrelatedEntries = 0;
64752
+ for (const pattern of patterns) {
64753
+ const outcome = classifyExistingEntry({
64754
+ emitted,
64755
+ capability,
64756
+ pattern,
64757
+ effect: rule.effect
64758
+ });
64759
+ if (outcome === "duplicate") continue;
64760
+ remaining.push(pattern);
64761
+ if (outcome === "unrelated") {
64762
+ unrelatedEntries++;
64763
+ continue;
64764
+ }
64765
+ logger?.warn(`Kiro permissions.yaml already has a ${capability} rule for "${pattern}" with effect "${String(rule.effect)}"; keeping it alongside the generated rule. Kiro applies the stricter effect (deny > ask > allow).`);
64766
+ }
64767
+ if (remaining.length === 0) return {
64768
+ rule: void 0,
64769
+ unrelatedEntries
64770
+ };
64771
+ if (remaining.length === patterns.length || isCatchAll) return {
64772
+ rule,
64773
+ unrelatedEntries
64774
+ };
64775
+ return {
64776
+ rule: {
64777
+ ...rule,
64778
+ match: remaining
64779
+ },
64780
+ unrelatedEntries
64781
+ };
64782
+ }
64783
+ /**
64784
+ * Merge the existing rules with the generated ones. Rulesync keeps no
64785
+ * ownership record in the file, so it only removes what it is about to write
64786
+ * again: an existing rule loses the `match` entries that exactly duplicate a
64787
+ * generated (capability, pattern, effect) — a match-less rule counts as the `*`
64788
+ * pattern — and is dropped only when no entry is left.
64789
+ *
64790
+ * Everything else is kept verbatim: rules Kiro saved ("Always allow/deny"),
64791
+ * hand-written rules, rules with `exclude`, rules for capabilities rulesync
64792
+ * does not map, malformed entries. An existing rule with the same pattern but a
64793
+ * different effect is kept too, with a warning: Kiro resolves deny > ask >
64794
+ * allow, so the stricter rule still wins and a user's `deny` is never loosened.
64795
+ * Rerunning generate is therefore idempotent.
64796
+ */
64797
+ function mergeKiroCapabilityRules({ existingRules, generatedRules, logger }) {
64798
+ const emitted = /* @__PURE__ */ new Map();
64799
+ for (const rule of generatedRules) for (const pattern of rule.match ?? [CATCH_ALL_PATTERN$6]) {
64800
+ const key = rulePatternKey(rule.capability, pattern);
64801
+ const effects = emitted.get(key) ?? /* @__PURE__ */ new Set();
64802
+ effects.add(rule.effect);
64803
+ emitted.set(key, effects);
64804
+ }
64805
+ const kept = [];
64806
+ let unrelatedManagedEntries = 0;
64807
+ for (const rule of existingRules) {
64808
+ const result = mergeExistingRule({
64809
+ rule,
64810
+ emitted,
64811
+ logger
64812
+ });
64813
+ if (result.rule !== void 0) kept.push(result.rule);
64814
+ unrelatedManagedEntries += result.unrelatedEntries;
64815
+ }
64816
+ if (unrelatedManagedEntries > 0) logger?.warn(`Kiro permissions.yaml keeps ${unrelatedManagedEntries} existing shell/fs_read/fs_write/web_fetch/web_search rule ${unrelatedManagedEntries === 1 ? "entry" : "entries"} that .rulesync/permissions.json does not generate. Rulesync never removes rules from this file and cannot tell which of these it wrote earlier; delete any stale ones by hand.`);
64817
+ return [...kept, ...generatedRules];
64818
+ }
64819
+ const LOCATION_INDEPENDENT_FS_PATTERN = /^(?:\/|~|\*\*|[A-Za-z]:[\\/])/;
64820
+ /**
64821
+ * Permissions adapter for Kiro's capability-based `permissions.yaml` (Kiro IDE
64822
+ * 1.0, and the same engine in Kiro CLI V3). Used for the `kiro-cli` and
64823
+ * `kiro-ide` targets in **global** scope, where it writes the user-level
64824
+ * `~/.kiro/settings/permissions.yaml`. Project scope keeps writing the agent
64825
+ * config `.kiro/agents/default.json` through {@link KiroPermissions}: the
64826
+ * workspace-level `permissions.yaml` lives under a hash-derived directory in the
64827
+ * home directory, not in the repository, so it cannot be generated.
64828
+ *
64829
+ * Mapping (rulesync canonical -> Kiro rule):
64830
+ * - Category -> `capability`: `bash` -> `shell`, `read` -> `fs_read`,
64831
+ * `edit`/`write` -> `fs_write`, `webfetch` -> `web_fetch`, `websearch` ->
64832
+ * `web_search`. Other categories are skipped with a warning.
64833
+ * - Action -> `effect` 1:1, including `ask`.
64834
+ * - Pattern -> `match` entry. The catch-all `*` becomes a rule with no `match`
64835
+ * (the whole capability), because a filesystem `*` only matches one path
64836
+ * component in Kiro.
64837
+ *
64838
+ * Kiro also writes into this file (consent decisions persisted globally), so it
64839
+ * is merged, never replaced or deleted: every top-level key other than `rules`
64840
+ * is preserved, and existing rules are kept except for entries that exactly
64841
+ * repeat a generated rule (see {@link mergeKiroCapabilityRules}). Rulesync
64842
+ * never retracts a rule an earlier run wrote; it warns instead. A run that
64843
+ * changes nothing returns the existing content verbatim; otherwise the file is
64844
+ * re-serialized and its YAML comments are lost.
64845
+ *
64846
+ * @see https://kiro.dev/docs/permissions/
64847
+ */
64848
+ var KiroCapabilityPermissions = class KiroCapabilityPermissions extends ToolPermissions {
64849
+ constructor(params) {
64850
+ super({
64851
+ ...params,
64852
+ fileContent: params.fileContent ?? ""
64853
+ });
64854
+ }
64855
+ isDeletable() {
64856
+ return false;
64857
+ }
64858
+ shouldSkipCreationWhenPayloadEmpty() {
64859
+ return true;
64860
+ }
64861
+ static getSettablePaths(_options) {
64862
+ return {
64863
+ relativeDirPath: KIRO_SETTINGS_DIR_PATH,
64864
+ relativeFilePath: KIRO_PERMISSIONS_FILE_NAME
64865
+ };
64866
+ }
64867
+ static async fromFile({ outputRoot = process.cwd(), validate = true }) {
64868
+ const paths = this.getSettablePaths({ global: true });
64869
+ const fileContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "";
64870
+ return new KiroCapabilityPermissions({
64871
+ outputRoot,
64872
+ relativeDirPath: paths.relativeDirPath,
64873
+ relativeFilePath: paths.relativeFilePath,
64874
+ fileContent,
64875
+ validate,
64876
+ global: true
64877
+ });
64878
+ }
64879
+ static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, logger }) {
64880
+ const paths = this.getSettablePaths({ global: true });
64881
+ const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
64882
+ const existingContent = await readFileContentOrNull(filePath) ?? "";
64883
+ let parsed;
64884
+ try {
64885
+ parsed = loadYaml(existingContent) ?? {};
64886
+ } catch (error) {
64887
+ throw new Error(`Failed to parse existing Kiro permissions.yaml at ${filePath}: ${formatError(error)}`, { cause: error });
64888
+ }
64889
+ if (!isRecord$1(parsed)) throw new Error(`Failed to parse existing Kiro permissions.yaml at ${filePath}: not a map`);
64890
+ if (parsed.rules !== void 0 && !Array.isArray(parsed.rules)) throw new Error(`Failed to parse existing Kiro permissions.yaml at ${filePath}: 'rules' is not a list`);
64891
+ const config = rulesyncPermissions.getJson();
64892
+ if (config.kiro?.toolsSettings !== void 0) logger?.warn("Kiro permissions: 'kiro.toolsSettings' applies only to the project-scope agent config (.kiro/agents/default.json); ignoring it for ~/.kiro/settings/permissions.yaml.");
64893
+ const generatedRules = buildKiroCapabilityRules({
64894
+ config,
64895
+ logger
64896
+ });
64897
+ const rules = mergeKiroCapabilityRules({
64898
+ existingRules: parsed.rules ?? [],
64899
+ generatedRules,
64900
+ logger
64901
+ });
64902
+ const next = { ...parsed };
64903
+ if (rules.length > 0 || parsed.rules !== void 0) next.rules = rules;
64904
+ return new KiroCapabilityPermissions({
64905
+ outputRoot,
64906
+ relativeDirPath: paths.relativeDirPath,
64907
+ relativeFilePath: paths.relativeFilePath,
64908
+ fileContent: serializeKiroPermissions({
64909
+ next,
64910
+ parsed,
64911
+ existingContent
64912
+ }),
64913
+ validate: true,
64914
+ global: true
64915
+ });
64916
+ }
64917
+ toRulesyncPermissions() {
64918
+ let parsed;
64919
+ try {
64920
+ parsed = loadYaml(this.getFileContent()) ?? {};
64921
+ } catch (error) {
64922
+ throw new Error(`Failed to parse Kiro permissions content in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
64923
+ }
64924
+ const rules = isRecord$1(parsed) && Array.isArray(parsed.rules) ? parsed.rules : [];
64925
+ const permission = {};
64926
+ for (const rule of rules) {
64927
+ if (!isImportableRule(rule) || !isRecord$1(rule) || !isPermissionAction$1(rule.effect)) continue;
64928
+ const category = KIRO_CAPABILITY_TO_CANONICAL[rule.capability];
64929
+ const patterns = rule.match === void 0 ? [CATCH_ALL_PATTERN$6] : isStringArray$2(rule.match) ? rule.match : [];
64930
+ const categoryRules = permission[category] ??= {};
64931
+ for (const pattern of patterns) {
64932
+ const previous = categoryRules[pattern];
64933
+ if (previous === void 0 || ACTION_RANK$2[rule.effect] > ACTION_RANK$2[previous]) categoryRules[pattern] = rule.effect;
64934
+ }
64935
+ }
64936
+ return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify({ permission }, null, 2) });
64937
+ }
64938
+ validate() {
64939
+ return {
64940
+ success: true,
64941
+ error: null
64942
+ };
64943
+ }
64944
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
64945
+ return new KiroCapabilityPermissions({
64946
+ outputRoot,
64947
+ relativeDirPath,
64948
+ relativeFilePath,
64949
+ fileContent: "",
64950
+ validate: false,
64951
+ global: true
64952
+ });
64953
+ }
64954
+ };
64955
+ /**
64956
+ * Convert the canonical `permission` block into Kiro capability rules: one rule
64957
+ * per (capability, effect) carrying every non-catch-all pattern as `match`, plus
64958
+ * a match-less rule when the catch-all `*` is set.
64959
+ */
64960
+ function buildKiroCapabilityRules({ config, logger }) {
64961
+ const buckets = /* @__PURE__ */ new Map();
64962
+ for (const [category, rules] of Object.entries(honorAllToolsOnBash(config.permission))) {
64963
+ const capability = CANONICAL_TO_KIRO_CAPABILITY[category];
64964
+ if (capability === void 0) {
64965
+ logger?.warn(`Kiro permissions.yaml has no capability for category: ${category}. Skipping.`);
64966
+ continue;
64967
+ }
64968
+ for (const [pattern, effect] of Object.entries(rules)) {
64969
+ warnOnWorkspaceRelativeFsPattern({
64970
+ capability,
64971
+ category,
64972
+ pattern,
64973
+ logger
64974
+ });
64975
+ const key = `${capability}\u0000${effect}`;
64976
+ const bucket = buckets.get(key) ?? {
64977
+ capability,
64978
+ effect,
64979
+ patterns: /* @__PURE__ */ new Set()
64980
+ };
64981
+ bucket.patterns.add(pattern);
64982
+ buckets.set(key, bucket);
64983
+ }
64984
+ }
64985
+ const result = [];
64986
+ for (const { capability, effect, patterns } of buckets.values()) {
64987
+ if (patterns.has(CATCH_ALL_PATTERN$6)) result.push({
64988
+ capability,
64989
+ effect
64990
+ });
64991
+ const match = [...patterns].filter((pattern) => pattern !== CATCH_ALL_PATTERN$6);
64992
+ if (match.length > 0) result.push({
64993
+ capability,
64994
+ match,
64995
+ effect
64996
+ });
64997
+ }
64998
+ return result;
64999
+ }
65000
+ /**
65001
+ * Serialize the merged config. When the merge changes nothing, the existing
65002
+ * content is returned verbatim so a run with nothing to change never rewrites
65003
+ * the file (and never drops its YAML comments).
65004
+ */
65005
+ function serializeKiroPermissions({ next, parsed, existingContent }) {
65006
+ if (isDeepStrictEqual(next, parsed)) return existingContent;
65007
+ return Object.keys(next).length === 0 ? "" : dump(next);
65008
+ }
65009
+ /**
65010
+ * `~/.kiro/settings/permissions.yaml` applies to every workspace, so a relative
65011
+ * filesystem pattern such as `src/**` is not tied to the project it was written
65012
+ * for. Warn so the author can make it absolute, `~`-relative or `**`-prefixed.
65013
+ */
65014
+ function warnOnWorkspaceRelativeFsPattern({ capability, category, pattern, logger }) {
65015
+ if (capability !== "fs_read" && capability !== "fs_write") return;
65016
+ if (pattern === CATCH_ALL_PATTERN$6 || LOCATION_INDEPENDENT_FS_PATTERN.test(pattern)) return;
65017
+ logger?.warn(`Kiro permissions.yaml is user-level and applies to every workspace, but the ${category} pattern "${pattern}" is relative. Use an absolute, ~-relative or **-prefixed pattern to target a specific location.`);
65018
+ }
65019
+ /**
65020
+ * Permissions factory for the `kiro-cli` / `kiro-ide` targets: project scope
65021
+ * writes the agent config `.kiro/agents/default.json` ({@link KiroPermissions}),
65022
+ * global scope writes `~/.kiro/settings/permissions.yaml`
65023
+ * ({@link KiroCapabilityPermissions}). The deprecated `kiro` alias stays on
65024
+ * {@link KiroPermissions} only.
65025
+ */
65026
+ const KiroScopedPermissions = {
65027
+ getSettablePaths(options = {}) {
65028
+ return options.global ? KiroCapabilityPermissions.getSettablePaths(options) : KiroPermissions.getSettablePaths(options);
65029
+ },
65030
+ fromFile(params) {
65031
+ return params.global ? KiroCapabilityPermissions.fromFile(params) : KiroPermissions.fromFile(params);
65032
+ },
65033
+ fromRulesyncPermissions(params) {
65034
+ return params.global ? KiroCapabilityPermissions.fromRulesyncPermissions(params) : KiroPermissions.fromRulesyncPermissions(params);
65035
+ },
65036
+ forDeletion(params) {
65037
+ return params.global ? KiroCapabilityPermissions.forDeletion(params) : KiroPermissions.forDeletion(params);
65038
+ }
65039
+ };
65040
+ //#endregion
64221
65041
  //#region src/features/permissions/lettacode-permissions.ts
64222
65042
  /** Top-level key of `.letta/settings.json` that rulesync owns. */
64223
65043
  const LETTACODE_PERMISSIONS_KEY = "permissions";
@@ -66245,6 +67065,8 @@ const QWEN_OVERRIDE_TOOLS_KEYS = [
66245
67065
  "eager",
66246
67066
  "listDirectory",
66247
67067
  "todoWrite",
67068
+ "codeModeOnly",
67069
+ "freeform",
66248
67070
  "workflowsEnabled",
66249
67071
  "workflowNameOnly",
66250
67072
  "executionSandbox"
@@ -66371,6 +67193,16 @@ const QWEN_SCOPED_TOOLS_KEYS = {
66371
67193
  rule: "global-machine-wide",
66372
67194
  projectNote: ({ qualifiedKey, quotedValue, filePath }) => `${qualifiedKey} = ${quotedValue} was written to the project-scoped ${filePath}, so it decides whether the built-in \`todo_write\` tool is registered in this repository.`,
66373
67195
  globalNote: "Qwen Code honors this key wherever it is written, so in the global scope this decides whether the built-in `todo_write` tool is registered for every project on this machine."
67196
+ },
67197
+ codeModeOnly: {
67198
+ rule: "global-machine-wide",
67199
+ projectNote: ({ qualifiedKey, quotedValue, filePath }) => `${qualifiedKey} = ${quotedValue} was written to the project-scoped ${filePath}, so it decides whether ordinary tools are reached only through Code Mode's \`exec\` tool in this repository.`,
67200
+ globalNote: "Qwen Code honors this key wherever it is written, so in the global scope this decides whether ordinary tools are reached only through Code Mode's `exec` tool for every project on this machine."
67201
+ },
67202
+ freeform: {
67203
+ rule: "global-machine-wide",
67204
+ projectNote: ({ qualifiedKey, quotedValue, filePath }) => `${qualifiedKey} = ${quotedValue} was written to the project-scoped ${filePath}, so it decides whether Code Mode's \`exec\` tool takes raw text input on OpenAI Responses models in this repository.`,
67205
+ globalNote: "Qwen Code honors this key wherever it is written, so in the global scope this decides whether Code Mode's `exec` tool takes raw text input on OpenAI Responses models for every project on this machine."
66374
67206
  }
66375
67207
  };
66376
67208
  /**
@@ -66834,22 +67666,42 @@ function convertQwenToRulesyncPermissions(params) {
66834
67666
  //#endregion
66835
67667
  //#region src/features/permissions/reasonix-permissions.ts
66836
67668
  /**
66837
- * Mapping from rulesync canonical tool category names (lowercase) to Reasonix
66838
- * permission-rule tool families (PascalCase).
66839
- *
66840
- * Reasonix's `[permissions]` rule syntax (SPEC.md §3.7) is explicitly
66841
- * documented as "Claude Code-style": "Bash and file mutation approvals use
66842
- * Claude Code-style families such as `Bash(npm run build)`, `Bash(npm run
66843
- * test:*)`, and `Edit(docs/**)`." Reasonix also accepts legacy lowercase tool
66844
- * IDs for compatibility, but new rules are saved using these PascalCase
66845
- * families, so rulesync reuses the same mapping `claudecode-permissions.ts`
66846
- * uses (the closest documented precedent for this syntax).
66847
- * @see https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/docs/SPEC.md
67669
+ * Mapping from rulesync canonical tool category names (lowercase) to the tool
67670
+ * names a Reasonix `[permissions]` rule must use.
67671
+ *
67672
+ * Reasonix matches a rule's tool exactly against its registered tools, which
67673
+ * are snake_case (`read_file`, `write_file`, `grep`, `glob`, `web_fetch`,
67674
+ * `web_search`, `notebook_edit`, `task`). Only `Bash`, `Edit` (every file
67675
+ * writer: `write_file`, `edit_file`, `multi_edit`, `notebook_edit`, ...),
67676
+ * `Browser` and `Computer` are Claude-Code-style family aliases
67677
+ * (`canonicalRuleTool` / `groupOf`), so a `Read(...)` or `Write(...)` rule
67678
+ * matches nothing and Reasonix v2.34.0+ reports it as dormant. Checked on both
67679
+ * release lines: v1.39.8 (`internal/permission/permission.go`) and v2.41.0
67680
+ * (`internal/safety/permission/permission.go`, `rule_group.go`).
67681
+ * @see https://github.com/esengine/DeepSeek-Reasonix/blob/v2.41.0/internal/safety/permission/permission.go
67682
+ * @see https://github.com/esengine/DeepSeek-Reasonix/blob/v2.41.0/internal/safety/permission/rule_group.go
66848
67683
  */
66849
67684
  const CANONICAL_TO_REASONIX_TOOL_NAMES = {
66850
67685
  bash: "Bash",
66851
- read: "Read",
67686
+ read: REASONIX_READ_TOOL_NAME,
66852
67687
  edit: "Edit",
67688
+ write: "write_file",
67689
+ webfetch: "web_fetch",
67690
+ websearch: "web_search",
67691
+ grep: "grep",
67692
+ glob: "glob",
67693
+ notebookedit: "notebook_edit",
67694
+ agent: "task"
67695
+ };
67696
+ /**
67697
+ * The Claude-Code-style spellings rulesync emitted before it followed the
67698
+ * Reasonix tool vocabulary. None of them reaches a Reasonix tool, so they are
67699
+ * read back on import (a file generated by an older rulesync still imports)
67700
+ * and counted as managed on generate (the dormant copy is replaced rather than
67701
+ * kept beside the working rule).
67702
+ */
67703
+ const CANONICAL_TO_LEGACY_REASONIX_TOOL_NAMES = {
67704
+ read: REASONIX_LEGACY_READ_TOOL_NAME,
66853
67705
  write: "Write",
66854
67706
  webfetch: "WebFetch",
66855
67707
  websearch: "WebSearch",
@@ -66859,9 +67711,16 @@ const CANONICAL_TO_REASONIX_TOOL_NAMES = {
66859
67711
  agent: "Agent"
66860
67712
  };
66861
67713
  /**
66862
- * Reverse mapping from Reasonix tool names to rulesync canonical names.
67714
+ * Reverse mapping from Reasonix tool names (current and legacy spellings) to
67715
+ * rulesync canonical names.
66863
67716
  */
66864
- const REASONIX_TO_CANONICAL_TOOL_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_REASONIX_TOOL_NAMES).map(([k, v]) => [v, k]));
67717
+ const REASONIX_TO_CANONICAL_TOOL_NAMES = Object.fromEntries([...Object.entries(CANONICAL_TO_LEGACY_REASONIX_TOOL_NAMES).map(([k, v]) => [v, k]), ...Object.entries(CANONICAL_TO_REASONIX_TOOL_NAMES).map(([k, v]) => [v, k])]);
67718
+ /** Every Reasonix tool name a canonical category owns on generate. */
67719
+ function reasonixToolNamesOf(canonical) {
67720
+ const legacy = CANONICAL_TO_LEGACY_REASONIX_TOOL_NAMES[canonical];
67721
+ const current = toReasonixToolName(canonical);
67722
+ return legacy === void 0 ? [current] : [current, legacy];
67723
+ }
66865
67724
  function toReasonixToolName(canonical) {
66866
67725
  return CANONICAL_TO_REASONIX_TOOL_NAMES[canonical] ?? canonical;
66867
67726
  }
@@ -67134,7 +67993,7 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
67134
67993
  const parsed = parseReasonixConfig(existingContent);
67135
67994
  const config = rulesyncPermissions.getJson();
67136
67995
  const { allow, ask, deny } = convertRulesyncToReasonixPermissions(config);
67137
- const managedToolNames = new Set(Object.keys(config.permission).map((category) => toReasonixToolName(category)));
67996
+ const managedToolNames = new Set(Object.keys(config.permission).flatMap((category) => reasonixToolNamesOf(category)));
67138
67997
  const override = config.reasonix;
67139
67998
  const rawAllow = toReasonixStringArray(override?.rawAllow);
67140
67999
  const rawAsk = toReasonixStringArray(override?.rawAsk);
@@ -67151,12 +68010,13 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
67151
68010
  rawOwnedEntries,
67152
68011
  logger
67153
68012
  });
67154
- if (logger && managedToolNames.has("Read")) {
68013
+ if (logger && managedToolNames.has("read_file")) {
68014
+ const readToolNames = new Set(reasonixToolNamesOf("read"));
67155
68015
  const droppedReadDenyEntries = toReasonixStringArray(existingPermissions.deny).filter((entry) => {
67156
68016
  const { toolName } = parseReasonixPermissionEntry(entry);
67157
- return toolName === "Read";
68017
+ return readToolNames.has(toolName);
67158
68018
  });
67159
- if (droppedReadDenyEntries.length > 0) logger.warn(`Permissions feature manages 'Read' tool and will overwrite ${droppedReadDenyEntries.length} existing Read deny entries (possibly from ignore feature). Permissions take precedence.`);
68019
+ if (droppedReadDenyEntries.length > 0) logger.warn(`Permissions feature manages '${REASONIX_READ_TOOL_NAME}' tool and will overwrite ${droppedReadDenyEntries.length} existing read deny entries (possibly from ignore feature). Permissions take precedence.`);
67160
68020
  }
67161
68021
  const mergedPermissions = { ...existingPermissions };
67162
68022
  setOrDeleteEntries(mergedPermissions, "allow", [
@@ -72502,18 +73362,18 @@ const toolPermissionsFactories = /* @__PURE__ */ new Map([
72502
73362
  }
72503
73363
  }],
72504
73364
  ["kiro-cli", {
72505
- class: KiroPermissions,
73365
+ class: KiroScopedPermissions,
72506
73366
  meta: {
72507
73367
  supportsProject: true,
72508
- supportsGlobal: false,
73368
+ supportsGlobal: true,
72509
73369
  supportsImport: true
72510
73370
  }
72511
73371
  }],
72512
73372
  ["kiro-ide", {
72513
- class: KiroPermissions,
73373
+ class: KiroScopedPermissions,
72514
73374
  meta: {
72515
73375
  supportsProject: true,
72516
- supportsGlobal: false,
73376
+ supportsGlobal: true,
72517
73377
  supportsImport: true
72518
73378
  }
72519
73379
  }],
@@ -78500,6 +79360,24 @@ var KimiCodeSkill = class extends ToolSkill {
78500
79360
  const targets = rulesyncSkill.getFrontmatter().targets;
78501
79361
  return targets.includes("*") || targets.includes("kimi-code");
78502
79362
  }
79363
+ /**
79364
+ * Commands are emitted into the Kimi-specific skills tree as
79365
+ * `<slug>/SKILL.md` (see `KimiCodeCommand`), so a directory there matching a
79366
+ * current rulesync command slug (of a command that targets `kimi-code`) is
79367
+ * owned by the commands feature: it must not be imported as a skill nor
79368
+ * deleted as an orphan skill. A command aimed only at other tools never
79369
+ * reaches this tree, so it leaves a same-named skill alone. The import-only
79370
+ * `.agents/skills/` root never receives Kimi Code commands, so a same-named
79371
+ * skill in it stays a skill.
79372
+ */
79373
+ static async isDirOwned({ relativeDirPath, dirName, inputRoots, global }) {
79374
+ if (relativeDirPath !== this.getSettablePaths({ global }).relativeDirPath) return true;
79375
+ return !await rulesyncCommandSlugExists({
79376
+ inputRoots,
79377
+ dirName,
79378
+ toolTarget: "kimi-code"
79379
+ });
79380
+ }
78503
79381
  static async fromDir(params) {
78504
79382
  const loaded = await this.loadSkillDirContent({
78505
79383
  ...params,
@@ -78573,6 +79451,14 @@ var KimiCodePluginSkill = class extends KimiCodeSkill {
78573
79451
  return { relativeDirPath: KIMI_CODE_PLUGIN_SKILLS_DIR };
78574
79452
  }
78575
79453
  /**
79454
+ * Unlike `.kimi-code/skills/`, the plugin `skills/` tree is not shared with
79455
+ * the commands feature (`kimi-code-plugin` writes commands to `commands/`),
79456
+ * so every directory in it is a skill.
79457
+ */
79458
+ static async isDirOwned() {
79459
+ return true;
79460
+ }
79461
+ /**
78576
79462
  * The bundle is read from `--output-root <plugin>`, but the imported rulesync
78577
79463
  * file belongs to the project's `.rulesync/`, not to the plugin root.
78578
79464
  */
@@ -78924,7 +79810,8 @@ const OpenCodeSkillFrontmatterSchema = z.looseObject({
78924
79810
  license: z.optional(z.unknown()),
78925
79811
  compatibility: z.optional(z.unknown()),
78926
79812
  metadata: z.optional(z.unknown()),
78927
- "allowed-tools": z.optional(z.array(z.string()))
79813
+ "allowed-tools": z.optional(z.array(z.string())),
79814
+ "disable-model-invocation": z.optional(z.boolean())
78928
79815
  });
78929
79816
  var OpenCodeSkill = class extends ToolSkill {
78930
79817
  /** Directory layout; OpenCode forks (MiMo Code) override it. */
@@ -79041,7 +79928,8 @@ var OpenCodeSkill = class extends ToolSkill {
79041
79928
  ...frontmatter["allowed-tools"] !== void 0 && { "allowed-tools": frontmatter["allowed-tools"] },
79042
79929
  ...frontmatter.license !== void 0 && { license: frontmatter.license },
79043
79930
  ...frontmatter.compatibility !== void 0 && { compatibility: frontmatter.compatibility },
79044
- ...frontmatter.metadata !== void 0 && { metadata: frontmatter.metadata }
79931
+ ...frontmatter.metadata !== void 0 && { metadata: frontmatter.metadata },
79932
+ ...frontmatter["disable-model-invocation"] !== void 0 && { "disable-model-invocation": frontmatter["disable-model-invocation"] }
79045
79933
  };
79046
79934
  const { toolTarget } = this.constructor.layout;
79047
79935
  const rulesyncFrontmatter = {
@@ -79076,13 +79964,18 @@ var OpenCodeSkill = class extends ToolSkill {
79076
79964
  rootFrontmatter: rulesyncFrontmatter,
79077
79965
  section: opencodeSection
79078
79966
  });
79967
+ const disableModelInvocation = resolveDisableModelInvocation({
79968
+ rootFrontmatter: rulesyncFrontmatter,
79969
+ section: opencodeSection
79970
+ });
79079
79971
  const opencodeFrontmatter = {
79080
79972
  name: rulesyncFrontmatter.name,
79081
79973
  description: rulesyncFrontmatter.description,
79082
79974
  ...license !== void 0 && { license },
79083
79975
  ...compatibility !== void 0 && { compatibility },
79084
79976
  ...metadata !== void 0 && { metadata },
79085
- ...opencodeSection?.["allowed-tools"] !== void 0 && { "allowed-tools": opencodeSection["allowed-tools"] }
79977
+ ...opencodeSection?.["allowed-tools"] !== void 0 && { "allowed-tools": opencodeSection["allowed-tools"] },
79978
+ ...disableModelInvocation !== void 0 && { "disable-model-invocation": disableModelInvocation }
79086
79979
  };
79087
79980
  const settablePaths = this.getSettablePaths({ global });
79088
79981
  return new this({
@@ -79105,11 +79998,13 @@ var OpenCodeSkill = class extends ToolSkill {
79105
79998
  ...params,
79106
79999
  getSettablePaths: (options) => this.getSettablePaths(options)
79107
80000
  });
79108
- const result = OpenCodeSkillFrontmatterSchema.safeParse(loaded.frontmatter);
79109
- if (!result.success) {
79110
- const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
79111
- throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
79112
- }
80001
+ const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
80002
+ const frontmatter = normalizeDisableModelInvocation({
80003
+ frontmatter: loaded.frontmatter,
80004
+ skillFilePath: join(skillDirPath, SKILL_FILE_NAME)
80005
+ });
80006
+ const result = OpenCodeSkillFrontmatterSchema.safeParse(frontmatter);
80007
+ if (!result.success) throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
79113
80008
  return new this({
79114
80009
  outputRoot: loaded.outputRoot,
79115
80010
  relativeDirPath: loaded.relativeDirPath,
@@ -79137,6 +80032,52 @@ var OpenCodeSkill = class extends ToolSkill {
79137
80032
  });
79138
80033
  }
79139
80034
  };
80035
+ const DISABLE_MODEL_INVOCATION_KEY = "disable-model-invocation";
80036
+ /**
80037
+ * Reads a `disable-model-invocation` value the way OpenCode V2 does
80038
+ * (`booleanValue` in `packages/core/src/config/plugin/skill-file.ts`): a
80039
+ * boolean, the numbers `1` / `0`, or a `true`/`yes`/`on`/`1` /
80040
+ * `false`/`no`/`off`/`0` string compared after trimming and case-folding.
80041
+ * Anything else is `undefined`, which OpenCode ignores.
80042
+ */
80043
+ function parseOpencodeBoolean(value) {
80044
+ if (typeof value === "boolean") return value;
80045
+ if (typeof value === "number") return value === 1 ? true : value === 0 ? false : void 0;
80046
+ if (typeof value !== "string") return;
80047
+ const normalized = value.trim().toLowerCase();
80048
+ if ([
80049
+ "true",
80050
+ "yes",
80051
+ "on",
80052
+ "1"
80053
+ ].includes(normalized)) return true;
80054
+ if ([
80055
+ "false",
80056
+ "no",
80057
+ "off",
80058
+ "0"
80059
+ ].includes(normalized)) return false;
80060
+ }
80061
+ /**
80062
+ * Coerces an imported `disable-model-invocation` onto the boolean the schema
80063
+ * types. A spelling OpenCode cannot read is dropped with a warning instead of
80064
+ * failing the import, since OpenCode itself ignores it.
80065
+ */
80066
+ function normalizeDisableModelInvocation({ frontmatter, skillFilePath }) {
80067
+ if (!Object.hasOwn(frontmatter, DISABLE_MODEL_INVOCATION_KEY)) return frontmatter;
80068
+ const raw = frontmatter[DISABLE_MODEL_INVOCATION_KEY];
80069
+ const parsed = parseOpencodeBoolean(raw);
80070
+ if (parsed === raw) return frontmatter;
80071
+ const { [DISABLE_MODEL_INVOCATION_KEY]: _dropped, ...rest } = frontmatter;
80072
+ if (parsed === void 0) {
80073
+ warnOnceWithFallback(void 0, `${stripControlCharacters(toPosixPath(skillFilePath))}: ignoring unreadable "${DISABLE_MODEL_INVOCATION_KEY}" value ${stripControlCharacters(JSON.stringify(raw) ?? String(raw))}; OpenCode ignores it too.`);
80074
+ return rest;
80075
+ }
80076
+ return {
80077
+ ...rest,
80078
+ [DISABLE_MODEL_INVOCATION_KEY]: parsed
80079
+ };
80080
+ }
79140
80081
  //#endregion
79141
80082
  //#region src/features/skills/mimocode-skill.ts
79142
80083
  /**
@@ -100642,13 +101583,15 @@ const ANTIGRAVITY_PLUGIN_NAME_PATTERN = /^[a-zA-Z0-9-_]+$/;
100642
101583
  * The manifest declares only `name` and `description`, and rejects anything
100643
101584
  * else, so the one required field is derived from the plugin directory's name.
100644
101585
  * An existing manifest is never read or rewritten: it may carry a
100645
- * hand-authored `name` or `description` that rulesync has no source for.
101586
+ * hand-authored `name` or `description` that rulesync has no source for. A
101587
+ * manifest kept in one of the manifest folders counts as present too, so no
101588
+ * root stub is written next to it.
100646
101589
  */
100647
101590
  async function ensureAntigravityPluginManifests({ outputRoots, dryRun, logger }) {
100648
101591
  const paths = [];
100649
101592
  for (const outputRoot of outputRoots) {
100650
101593
  const filePath = join(outputRoot, ANTIGRAVITY_PLUGIN_MANIFEST_FILE_NAME);
100651
- if (await fileExists(filePath)) continue;
101594
+ if (await hasAntigravityPluginManifest(outputRoot)) continue;
100652
101595
  if (await refusesWriteOutsideRoot({
100653
101596
  logger,
100654
101597
  rootPath: outputRoot,
@@ -100671,6 +101614,11 @@ async function ensureAntigravityPluginManifests({ outputRoots, dryRun, logger })
100671
101614
  sourceLoadFailed: false
100672
101615
  };
100673
101616
  }
101617
+ async function hasAntigravityPluginManifest(outputRoot) {
101618
+ const candidates = [join(outputRoot, ANTIGRAVITY_PLUGIN_MANIFEST_FILE_NAME), ...ANTIGRAVITY_PLUGIN_MANIFEST_FOLDERS.map((folder) => join(outputRoot, folder, ANTIGRAVITY_PLUGIN_MANIFEST_FILE_NAME))];
101619
+ for (const candidate of candidates) if (await fileExists(candidate)) return true;
101620
+ return false;
101621
+ }
100674
101622
  //#endregion
100675
101623
  //#region src/features/shared/hermes-project-plugin-activation.ts
100676
101624
  function mergeEnabledPlugins({ existingContent, filePath, pluginNames }) {
@@ -102968,6 +103916,6 @@ async function importChecksCore(params) {
102968
103916
  return writtenCount;
102969
103917
  }
102970
103918
  //#endregion
102971
- export { RulesyncHooks as $, removeDirectoryStrict as $t, groupSpellingsByCaseFoldedIdentity as A, RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH as An, applyFileMode as At, QWENCODE_LOCAL_RULE_FILE_NAME as B, ALL_FEATURES as Bn, getFileSizeAndExecutableBit as Bt, COMMANDCODE_SETTINGS_LOCAL_FILE_NAME as C, RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH as Cn, fallbackLogger as Ct, FACTORYDROID_DIR as D, RULESYNC_MCP_RELATIVE_FILE_PATH as Dn, withWarnOnceScope as Dt, ChecksProcessor as E, RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH as En, resetRunWarningState as Et, CLAUDECODE_LOCAL_RULE_FILE_NAME as F, RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH as Fn, createTempDirectory as Ft, RulesyncSubagentFrontmatterSchema as G, hasDeceptiveHiddenCharacters as Gn, listDirectoryEntryNames as Gt, AUGMENTCODE_SETTINGS_LOCAL_FILE_NAME as H, DEPRECATED_FEATURE_REPLACEMENTS as Hn, isFileNotFoundError as Ht, CLAUDECODE_MEMORIES_DIR_NAME as I, RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH as In, directoryExists as It, RulesyncRule as J, stripControlCharacters as Jn, pathEscapesRoot as Jt, RulesyncSkill as K, hasEnclosingMarkOutsideKeycap as Kn, listFilePathsRecursively as Kt, CLAUDECODE_SETTINGS_LOCAL_FILE_NAME as L, RULESYNC_USER_CONFIG_DIR_NAME as Ln, ensureDir as Lt, CODEXCLI_DIR as M, RULESYNC_RELATIVE_DIR_PATH as Mn, assertTreeContainsNoSymlinks as Mt, CLAUDECODE_AGENT_MEMORY_LOCAL_DIR_NAME as N, RULESYNC_RULES_RELATIVE_DIR_PATH as Nn, assertWritablePathInsideRoot as Nt, FACTORYDROID_SETTINGS_LOCAL_FILE_NAME as O, RULESYNC_MCP_SCHEMA_URL as On, CLIError as Ot, CLAUDECODE_DIR as P, RULESYNC_SKILLS_RELATIVE_DIR_PATH as Pn, checkPathTraversal as Pt, RulesyncIgnore as Q, removeDirectory as Qt, CLAUDECODE_SKILLS_DIR_PATH as R, RULESYNC_XDG_CONFIG_HOME_DEFAULT_DIR_NAME as Rn, fileExists as Rt, COMMANDCODE_DIR as S, RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH as Sn, WarningCollectingLogger as St, CODEBUDDY_LOCAL_RULE_FILE_NAME as T, RULESYNC_IGNORE_RELATIVE_FILE_PATH as Tn, withFallbackLoggerTarget as Tt, getLocalSkillDirNames as U, formatError as Un, isFileSystemError as Ut, AUGMENTCODE_DIR as V, ALL_FEATURES_WITH_WILDCARD as Vn, getHomeDirectory as Vt, RulesyncSubagent as W, truncateText as Wn, isSymlink as Wt, RulesyncPermissions as X, stripHiddenCharacters as Xn, readFileContent as Xt, RulesyncRuleFrontmatterSchema as Y, stripControlCharactersKeepingLineFeeds as Yn, posixRelativePathEscapesRoot as Yt, RulesyncMcp as Z, readFileContentOrNull as Zt, LETTACODE_DIR as _, RULESYNC_AIIGNORE_RELATIVE_FILE_PATH as _n, SourceEntrySchema as _t, getProcessorRegistryEntry as a, runWithDirectoryRollback as an, RulesyncCheck as at, CommandsProcessor as b, RULESYNC_CONFIG_RELATIVE_FILE_PATH as bn, ConsoleLogger as bt, RulesProcessor as c, writeFileBuffer as cn, loadYaml as ct, ELLIPSIS_WIDTH as d, ALL_TOOL_TARGETS_WITH_WILDCARD as dn, ConfigResolver as dt, removeFile as en, getRulesyncSourceCandidates as et, displayWidthOf as f, PACKAGING_TOOL_TARGETS as fn, mergeInputRootConfigs as ft, HooksProcessor as g, MAX_FILE_SIZE as gn, GITIGNORE_DESTINATION_KEY as gt, IgnoreProcessor as h, FEATURE_SOURCE_TREE_ENTRIES as hn, ConfigFileSchema as ht, inspectInputRoots as i, resolvedRelativePath as in, RulesyncCommandFrontmatterSchema as it, CODEXCLI_BASH_RULES_FILE_NAME as j, RULESYNC_PERMISSIONS_SCHEMA_URL as jn, assertDirectoryIfExists as jt, caseFoldIdentity as k, RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH as kn, ErrorCodes as kt, SubagentsProcessor as l, writeFileContent as ln, SHARED_USER_MANAGED_CONFIG_PATHS as lt, McpProcessor as m, CURATED_RULES_FEATURE_SUBDIR as mn, CONFLICTING_TARGET_PAIRS as mt, formatSourceLoadFailure as n, removeTempDirectory as nn, parseJsonc as nt, convertFromTool as o, splitPathSegments as on, RulesyncCheckFrontmatterSchema as ot, shortenToWidth as p, ToolTargetSchema as pn, resolveEffectiveInputRoots as pt, RulesyncSkillFrontmatterSchema as q, quoteForLog as qn, listSubdirectoryNames as qt, generate as r, resolvePath as rn, RulesyncCommand as rt, isPackagingToolTarget as s, toPosixPath as sn, stringifyFrontmatter as st, importFromTool as t, removeFileStrict as tn, resolveRulesyncSourceWritePath as tt, SkillsProcessor as u, ALL_TOOL_TARGETS as un, SKILL_FILE_NAME as ut, LETTACODE_SETTINGS_LOCAL_FILE_NAME as v, RULESYNC_CHECKS_RELATIVE_DIR_PATH as vn, assertTargetsFeaturesExclusive as vt, CODEBUDDY_DIR as w, RULESYNC_HOOKS_RELATIVE_FILE_PATH as wn, warnOnConflictingFlags as wt, REASONIX_LOCAL_RULE_FILE_NAME as x, RULESYNC_CONFIG_SCHEMA_URL as xn, JsonLogger as xt, CRUSH_LOCAL_RULE_FILE_NAME as y, RULESYNC_COMMANDS_RELATIVE_DIR_PATH as yn, findControlCharacter as yt, QWENCODE_DIR as z, parseCommaSeparatedList as zn, findFilesByGlobs as zt };
103919
+ export { RulesyncHooks as $, removeDirectoryStrict as $t, groupSpellingsByCaseFoldedIdentity as A, RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH as An, applyFileMode as At, QWENCODE_LOCAL_RULE_FILE_NAME as B, parseCommaSeparatedList as Bn, getFileSizeAndExecutableBit as Bt, COMMANDCODE_SETTINGS_LOCAL_FILE_NAME as C, RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH as Cn, fallbackLogger as Ct, FACTORYDROID_DIR as D, RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH as Dn, withWarnOnceScope as Dt, ChecksProcessor as E, RULESYNC_IGNORE_RELATIVE_FILE_PATH as En, resetRunWarningState as Et, CLAUDECODE_LOCAL_RULE_FILE_NAME as F, RULESYNC_SKILLS_RELATIVE_DIR_PATH as Fn, createTempDirectory as Ft, RulesyncSubagentFrontmatterSchema as G, truncateText as Gn, listDirectoryEntryNames as Gt, AUGMENTCODE_SETTINGS_LOCAL_FILE_NAME as H, ALL_FEATURES_WITH_WILDCARD as Hn, isFileNotFoundError as Ht, CLAUDECODE_MEMORIES_DIR_NAME as I, RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH as In, directoryExists as It, RulesyncRule as J, quoteForLog as Jn, pathEscapesRoot as Jt, RulesyncSkill as K, hasDeceptiveHiddenCharacters as Kn, listFilePathsRecursively as Kt, CLAUDECODE_SETTINGS_LOCAL_FILE_NAME as L, RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH as Ln, ensureDir as Lt, CODEXCLI_DIR as M, RULESYNC_PERMISSIONS_SCHEMA_URL as Mn, assertTreeContainsNoSymlinks as Mt, CLAUDECODE_AGENT_MEMORY_LOCAL_DIR_NAME as N, RULESYNC_RELATIVE_DIR_PATH as Nn, assertWritablePathInsideRoot as Nt, FACTORYDROID_SETTINGS_LOCAL_FILE_NAME as O, RULESYNC_MCP_RELATIVE_FILE_PATH as On, CLIError as Ot, CLAUDECODE_DIR as P, RULESYNC_RULES_RELATIVE_DIR_PATH as Pn, checkPathTraversal as Pt, RulesyncIgnore as Q, removeDirectory as Qt, CLAUDECODE_SKILLS_DIR_PATH as R, RULESYNC_USER_CONFIG_DIR_NAME as Rn, fileExists as Rt, COMMANDCODE_DIR as S, RULESYNC_CONFIG_SCHEMA_URL as Sn, WarningCollectingLogger as St, CODEBUDDY_LOCAL_RULE_FILE_NAME as T, RULESYNC_HOOKS_RELATIVE_FILE_PATH as Tn, withFallbackLoggerTarget as Tt, getLocalSkillDirNames as U, DEPRECATED_FEATURE_REPLACEMENTS as Un, isFileSystemError as Ut, AUGMENTCODE_DIR as V, ALL_FEATURES as Vn, getHomeDirectory as Vt, RulesyncSubagent as W, formatError as Wn, isSymlink as Wt, RulesyncPermissions as X, stripControlCharactersKeepingLineFeeds as Xn, readFileContent as Xt, RulesyncRuleFrontmatterSchema as Y, stripControlCharacters as Yn, posixRelativePathEscapesRoot as Yt, RulesyncMcp as Z, stripHiddenCharacters as Zn, readFileContentOrNull as Zt, LETTACODE_DIR as _, MAX_FILE_SIZE as _n, SourceEntrySchema as _t, getProcessorRegistryEntry as a, runWithDirectoryRollback as an, RulesyncCheck as at, CommandsProcessor as b, RULESYNC_COMMANDS_RELATIVE_DIR_PATH as bn, ConsoleLogger as bt, RulesProcessor as c, writeFileBuffer as cn, loadYaml as ct, ELLIPSIS_WIDTH as d, ALL_TOOL_TARGETS as dn, ConfigResolver as dt, removeFile as en, getRulesyncSourceCandidates as et, displayWidthOf as f, ALL_TOOL_TARGETS_WITH_WILDCARD as fn, mergeInputRootConfigs as ft, HooksProcessor as g, FEATURE_SOURCE_TREE_ENTRIES as gn, GITIGNORE_DESTINATION_KEY as gt, IgnoreProcessor as h, CURATED_RULES_FEATURE_SUBDIR as hn, ConfigFileSchema as ht, inspectInputRoots as i, resolvedRelativePath as in, RulesyncCommandFrontmatterSchema as it, CODEXCLI_BASH_RULES_FILE_NAME as j, RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH as jn, assertDirectoryIfExists as jt, caseFoldIdentity as k, RULESYNC_MCP_SCHEMA_URL as kn, ErrorCodes as kt, SubagentsProcessor as l, writeFileContent as ln, SHARED_USER_MANAGED_CONFIG_PATHS as lt, McpProcessor as m, ToolTargetSchema as mn, CONFLICTING_TARGET_PAIRS as mt, formatSourceLoadFailure as n, removeTempDirectory as nn, parseJsonc as nt, convertFromTool as o, splitPathSegments as on, RulesyncCheckFrontmatterSchema as ot, shortenToWidth as p, PACKAGING_TOOL_TARGETS as pn, resolveEffectiveInputRoots as pt, RulesyncSkillFrontmatterSchema as q, hasEnclosingMarkOutsideKeycap as qn, listSubdirectoryNames as qt, generate as r, resolvePath as rn, RulesyncCommand as rt, isPackagingToolTarget as s, toPosixPath as sn, stringifyFrontmatter as st, importFromTool as t, removeFileStrict as tn, resolveRulesyncSourceWritePath as tt, SkillsProcessor as u, mapWithConcurrency as un, SKILL_FILE_NAME as ut, LETTACODE_SETTINGS_LOCAL_FILE_NAME as v, RULESYNC_AIIGNORE_RELATIVE_FILE_PATH as vn, assertTargetsFeaturesExclusive as vt, CODEBUDDY_DIR as w, RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH as wn, warnOnConflictingFlags as wt, REASONIX_LOCAL_RULE_FILE_NAME as x, RULESYNC_CONFIG_RELATIVE_FILE_PATH as xn, JsonLogger as xt, CRUSH_LOCAL_RULE_FILE_NAME as y, RULESYNC_CHECKS_RELATIVE_DIR_PATH as yn, findControlCharacter as yt, QWENCODE_DIR as z, RULESYNC_XDG_CONFIG_HOME_DEFAULT_DIR_NAME as zn, findFilesByGlobs as zt };
102972
103920
 
102973
- //# sourceMappingURL=import-BbF_R5Qx.js.map
103921
+ //# sourceMappingURL=import-Tya9qZRK.js.map