rulesync 16.23.0 → 16.24.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.
@@ -6633,10 +6633,25 @@ const CursorPermissionsOverrideSchema = z.looseObject({
6633
6633
  * Tool-scoped override block for Qwen Code. Qwen's `settings.json` exposes
6634
6634
  * autonomy/sandbox controls with no canonical permission category — under
6635
6635
  * `tools` (`approvalMode` = plan/default/auto-edit/auto/yolo, `autoAccept`,
6636
- * `sandbox`, `sandboxImage`, `disabled`) and `security` (`folderTrust`,
6637
- * `allowedHttpHookUrls`, `allowPrivateNetworkHooks` the latter is honored by
6638
- * Qwen Code only in user/system settings, so generate skips it in project scope).
6639
- * It also
6636
+ * `sandbox`, `sandboxImage`, `disabled`, `visible`, `listDirectory`,
6637
+ * `workflowsEnabled`) and `security` (`folderTrust`, `allowedHttpHookUrls`,
6638
+ * `allowPrivateNetworkHooks`, `allowedInsecureVoiceBaseUrls`). Qwen Code strips
6639
+ * `tools.workflowsEnabled`, `security.allowPrivateNetworkHooks` and
6640
+ * `security.allowedInsecureVoiceBaseUrls` out of workspace settings, so generate
6641
+ * skips those three in project scope and announces a granting value in global
6642
+ * scope. `security.allowedHttpHookUrls` (honored in a workspace only while no
6643
+ * higher scope sets it) and `security.folderTrust` (the initial trust decision
6644
+ * is made from user/system settings alone, before the workspace merge) are
6645
+ * written in both scopes, with a note in project scope and an announcement of
6646
+ * any global change. Every other key is honored in either scope, so a write that
6647
+ * changes what the file said is reported in either scope, naming what that key
6648
+ * decides there — the autonomy and containment
6649
+ * controls (`approvalMode`, `autoAccept`, `sandbox`, `sandboxImage`), the
6650
+ * registry controls (`disabled`, `visible`, `listDirectory`), the Auto Mode
6651
+ * classifier config, and, because these groups are loose objects, any key
6652
+ * rulesync does not model. Import flags a scope-dependent key only when it read
6653
+ * the project file, since that is the value a `--global` regenerate would
6654
+ * promote. It also
6640
6655
  * exposes `permissions.autoMode` (the Auto Mode classifier config:
6641
6656
  * `hints.{allow,softDeny,hardDeny}`, `environment`, `classifyAllShell` — see
6642
6657
  * https://qwenlm.github.io/qwen-code-docs/en/users/features/auto-mode/), which
@@ -42479,15 +42494,129 @@ const QWEN_OVERRIDE_TOOLS_KEYS = [
42479
42494
  "sandbox",
42480
42495
  "sandboxImage",
42481
42496
  "disabled",
42482
- "visible"
42497
+ "visible",
42498
+ "listDirectory",
42499
+ "workflowsEnabled"
42483
42500
  ];
42484
42501
  const QWEN_OVERRIDE_SECURITY_KEYS = [
42485
42502
  "folderTrust",
42486
42503
  "allowedHttpHookUrls",
42487
- "allowPrivateNetworkHooks"
42504
+ "allowPrivateNetworkHooks",
42505
+ "allowedInsecureVoiceBaseUrls"
42488
42506
  ];
42489
- const QWEN_GLOBAL_ONLY_SECURITY_KEYS = ["allowPrivateNetworkHooks"];
42507
+ /**
42508
+ * What each rule means for the two directions, so a key's behavior is declared
42509
+ * once beside its rule rather than spelled out at every branch.
42510
+ *
42511
+ * `stripInProjectScope` separates the keys a project file cannot use at all from
42512
+ * the ones whose project value still does something, just not unconditionally —
42513
+ * only the former are dropped. `announceOnlyGrants` marks the rule whose risk is
42514
+ * a value that turns something on; for the others the dangerous value is often
42515
+ * the falsy one (an empty allow-all list, folder trust switched off, a sandbox
42516
+ * turned off), so every change is announced instead. `announceOnImport` marks
42517
+ * the keys whose meaning depends on the scope they are written in — importing
42518
+ * one and regenerating globally turns a value a workspace could not decide for
42519
+ * itself into one Qwen Code enforces everywhere. `global-machine-wide` keys are
42520
+ * honored identically in either scope, so import neither promotes nor weakens
42521
+ * them and the announcement on the generate side is the control point.
42522
+ * `projectNote` is `null` for a rule that has nothing to say about the project
42523
+ * scope. A `globalNote` here is the rule's default; a key whose meaning the
42524
+ * default does not describe carries its own.
42525
+ */
42526
+ const QWEN_SCOPE_RULES = {
42527
+ "workspace-stripped": {
42528
+ stripInProjectScope: true,
42529
+ announceOnlyGrants: true,
42530
+ announceOnImport: true,
42531
+ projectNote: ({ qualifiedKey, quotedValue, filePath }) => `${qualifiedKey} = ${quotedValue} is only honored in user/system settings, so it is skipped for the project-scoped ${filePath} (a value already in that file is left as it is). Check it if the override arrived with a repository you cloned, and author it in the global scope only if that is a value you want every project on this machine to run under.`,
42532
+ globalNote: "Qwen Code ignores this key in workspace settings so a repository cannot grant it per project; in the global scope it applies to every project on this machine."
42533
+ },
42534
+ "workspace-non-overriding": {
42535
+ stripInProjectScope: false,
42536
+ announceOnlyGrants: false,
42537
+ announceOnImport: true,
42538
+ projectNote: ({ qualifiedKey, quotedValue, filePath }) => `${qualifiedKey} = ${quotedValue} was written to the project-scoped ${filePath}, but Qwen Code honors a workspace value for it only while no user, system, or system-defaults scope sets the key — a repository cannot replace the list a user configured in a higher scope. It does replace one written by hand in this same file, and an empty list is Qwen Code's allow-all, so check what it now says. Author it in the global scope if it has to apply unconditionally.`,
42539
+ globalNote: "A global value outranks every repository's own list for this key, and an empty list means allow-all, so this decides where HTTP hooks may send data for every project on this machine."
42540
+ },
42541
+ "user-scope-trust-check": {
42542
+ stripInProjectScope: false,
42543
+ announceOnlyGrants: false,
42544
+ announceOnImport: true,
42545
+ projectNote: ({ qualifiedKey, quotedValue, filePath }) => `${qualifiedKey} = ${quotedValue} was written to the project-scoped ${filePath}, but Qwen Code makes the initial trust decision from user and system settings alone, before a workspace file is merged, so a project value cannot decide whether this workspace is trusted (it still drives folder trust once the workspace is trusted). Check it if the override arrived with a repository you cloned, and author it in the global scope only if that is a value you want every project on this machine to run under.`,
42546
+ globalNote: "Qwen Code reads this key from the global scope when it decides whether a workspace is trusted, so this changes which projects on this machine it trusts."
42547
+ },
42548
+ "global-machine-wide": {
42549
+ stripInProjectScope: false,
42550
+ announceOnlyGrants: false,
42551
+ announceOnImport: false,
42552
+ projectNote: ({ qualifiedKey, quotedValue, filePath }) => `${qualifiedKey} = ${quotedValue} was written to the project-scoped ${filePath}. Qwen Code honors this key in a workspace file, so it takes effect while you work in this repository. Check it if the override arrived with a repository you cloned.`,
42553
+ globalNote: "Qwen Code honors this key wherever it is written, so in the global scope it settles how much the agent may do unattended — how far approvals are skipped, and whether tool calls are contained at all — for every project on this machine."
42554
+ },
42555
+ unmodeled: {
42556
+ stripInProjectScope: false,
42557
+ announceOnlyGrants: false,
42558
+ announceOnImport: false,
42559
+ projectNote: ({ qualifiedKey, quotedValue, filePath }) => `${qualifiedKey} = ${quotedValue} was written to the project-scoped ${filePath}. This is not a key rulesync models, so it cannot say what Qwen Code does with it — and some settings in these groups are potent (\`tools.discoveryCommand\` and \`tools.callCommand\` are spawned as commands when Qwen Code starts). Check it if the override arrived with a repository you cloned.`,
42560
+ globalNote: "This is not a key rulesync models, so it cannot say what Qwen Code does with it — and some settings in these groups are potent (`tools.discoveryCommand` and `tools.callCommand` are spawned as commands when Qwen Code starts). Check what it now says for every project on this machine."
42561
+ }
42562
+ };
42563
+ const QWEN_OVERRIDE_GROUPS = [{
42564
+ groupName: "tools",
42565
+ overrideKeys: QWEN_OVERRIDE_TOOLS_KEYS,
42566
+ scopedKeys: {
42567
+ workflowsEnabled: { rule: "workspace-stripped" },
42568
+ approvalMode: {
42569
+ rule: "global-machine-wide",
42570
+ projectNote: autonomyProjectNote
42571
+ },
42572
+ autoAccept: {
42573
+ rule: "global-machine-wide",
42574
+ projectNote: autonomyProjectNote
42575
+ },
42576
+ sandbox: {
42577
+ rule: "global-machine-wide",
42578
+ projectNote: autonomyProjectNote
42579
+ },
42580
+ sandboxImage: {
42581
+ rule: "global-machine-wide",
42582
+ projectNote: ({ qualifiedKey, quotedValue, filePath }) => `${qualifiedKey} = ${quotedValue} was written to the project-scoped ${filePath}, so a sandboxed run in this repository executes inside the named image — check that it is one you trust to run your code.`,
42583
+ globalNote: "Qwen Code honors this key wherever it is written, so in the global scope every sandboxed run on this machine executes inside the named image — check that it is one you trust to run your code."
42584
+ },
42585
+ disabled: {
42586
+ rule: "global-machine-wide",
42587
+ projectNote: ({ qualifiedKey, quotedValue, filePath }) => `${qualifiedKey} = ${quotedValue} was written to the project-scoped ${filePath}, replacing the list in that file rather than adding to it, so a name that is gone is a tool the model can call again in this repository unless another scope still disables it.`,
42588
+ globalNote: "Qwen Code honors this key wherever it is written, and the override replaces the list in this file rather than adding to it, so in the global scope this decides which tools stay out of the registry for every project on this machine — dropping a name hands that tool back to the model unless another scope still disables it."
42589
+ },
42590
+ visible: {
42591
+ rule: "global-machine-wide",
42592
+ projectNote: ({ qualifiedKey, quotedValue, filePath }) => `${qualifiedKey} = ${quotedValue} was written to the project-scoped ${filePath}, so it decides which deferred tools are visible at startup in this repository.`,
42593
+ globalNote: "Qwen Code honors this key wherever it is written, so in the global scope this decides which deferred tools are visible at startup for every project on this machine."
42594
+ },
42595
+ listDirectory: {
42596
+ rule: "global-machine-wide",
42597
+ projectNote: ({ qualifiedKey, quotedValue, filePath }) => `${qualifiedKey} = ${quotedValue} was written to the project-scoped ${filePath}, so it decides whether the built-in \`list_directory\` tool is registered in this repository.`,
42598
+ globalNote: "Qwen Code honors this key wherever it is written, so in the global scope this decides whether the built-in `list_directory` tool is registered for every project on this machine."
42599
+ }
42600
+ }
42601
+ }, {
42602
+ groupName: "security",
42603
+ overrideKeys: QWEN_OVERRIDE_SECURITY_KEYS,
42604
+ scopedKeys: {
42605
+ allowPrivateNetworkHooks: { rule: "workspace-stripped" },
42606
+ allowedInsecureVoiceBaseUrls: {
42607
+ rule: "workspace-stripped",
42608
+ grants: isNonEmptyArray
42609
+ },
42610
+ allowedHttpHookUrls: { rule: "workspace-non-overriding" },
42611
+ folderTrust: { rule: "user-scope-trust-check" }
42612
+ }
42613
+ }];
42490
42614
  const QWEN_OVERRIDE_PERMISSIONS_KEYS = ["autoMode"];
42615
+ const QWEN_SCOPED_PERMISSIONS_KEYS = { autoMode: {
42616
+ rule: "global-machine-wide",
42617
+ projectNote: ({ qualifiedKey, quotedValue, filePath }) => `${qualifiedKey} = ${quotedValue} was written to the project-scoped ${filePath}, replacing the whole block rather than merging into it, so it rewrites the instructions Auto Mode's classifier follows in this repository — including any deny hint you had configured.`,
42618
+ globalNote: "Qwen Code honors this key wherever it is written, and the override replaces the whole block rather than merging into it, so in the global scope this rewrites the instructions Auto Mode's classifier follows — including any deny hint you had configured — for every project on this machine."
42619
+ } };
42491
42620
  function asPlainRecord(value) {
42492
42621
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
42493
42622
  }
@@ -42499,20 +42628,172 @@ function pickQwenOverrideKeys(group, keys) {
42499
42628
  return picked;
42500
42629
  }
42501
42630
  /**
42502
- * Drop the global-only `security` keys from the override when generating project
42503
- * settings, warning once per dropped key. Only the override copy is filtered, so
42504
- * a value the user already wrote into the project file stays untouched.
42631
+ * Whether a value written for a scoped key actually grants something. Qwen Code
42632
+ * reads the boolean ones with a plain truthiness check, so anything truthy turns
42633
+ * the capability on — `1` and the string `"false"` included, which is why this is
42634
+ * not a `=== true` test. Not all of them are booleans either:
42635
+ * `security.allowedInsecureVoiceBaseUrls` is a list of base URLs, and an empty
42636
+ * list grants nothing.
42637
+ *
42638
+ * Only the rules whose `announceOnlyGrants` is set consult this, because "empty"
42639
+ * does not mean the same thing for every list: an empty
42640
+ * `security.allowedHttpHookUrls` is Qwen Code's allow-all, the widest value there
42641
+ * is, so that key is announced whatever it says.
42642
+ */
42643
+ function isNonEmptyArray(value) {
42644
+ return Array.isArray(value) && value.length > 0;
42645
+ }
42646
+ /**
42647
+ * Whether a value turns something on, read the way Qwen Code reads it.
42648
+ *
42649
+ * Plain truthiness by default, because that is what Qwen Code applies to these
42650
+ * settings: `1` and the string `"false"` enable them, and so does an empty array
42651
+ * — which is why an empty list cannot be treated as harmless everywhere. A key
42652
+ * whose list Qwen Code matches against, rather than tests for truth, carries its
42653
+ * own `grants` instead.
42654
+ */
42655
+ function grantsSomething(value, scopedKey) {
42656
+ return (scopedKey?.grants ?? Boolean)(value);
42657
+ }
42658
+ /**
42659
+ * The autonomy and containment controls say the same thing in project scope, so
42660
+ * they share one note rather than repeating it three times.
42505
42661
  */
42506
- function scopeOverrideSecurity(overrideSecurity, { global, relativeFilePath, logger }) {
42507
- const scoped = { ...asPlainRecord(overrideSecurity) };
42508
- if (global) return scoped;
42509
- for (const key of QWEN_GLOBAL_ONLY_SECURITY_KEYS) {
42510
- if (scoped[key] === void 0) continue;
42511
- delete scoped[key];
42512
- logger?.warn(`Qwen permissions: 'security.${key}' is only honored in user/system settings, so it is skipped for the project-scoped ${relativeFilePath}. Author it in the global scope instead.`);
42662
+ function autonomyProjectNote({ qualifiedKey, quotedValue, filePath }) {
42663
+ return `${qualifiedKey} = ${quotedValue} was written to the project-scoped ${filePath}. Qwen Code honors this key in a workspace file, so it settles how far approvals are skipped and whether tool calls are contained while you work in this repository. Check it if the override arrived with a repository you cloned.`;
42664
+ }
42665
+ /**
42666
+ * Quote a `group.key` pair for a warning. The key goes through the same quoting
42667
+ * a value does, because it is one: the override's groups are `z.looseObject`s,
42668
+ * so a key the scope gate names may have been written by whoever authored the
42669
+ * `.rulesync/permissions.jsonc` rather than chosen from a list rulesync knows.
42670
+ * Serializing it — rather than wrapping it in quotes of our own — is what keeps
42671
+ * a key that contains a quote from closing it and continuing the sentence.
42672
+ */
42673
+ function quoteQualifiedKey({ groupName, key }) {
42674
+ return quoteValueForWarning(`${groupName}.${key}`);
42675
+ }
42676
+ /**
42677
+ * Compare two settings values without letting key order decide the answer, so
42678
+ * re-emitting `{ enabled: true, note: "x" }` as `{ note: "x", enabled: true }`
42679
+ * is not announced as a change.
42680
+ */
42681
+ function stableStringify(value) {
42682
+ return JSON.stringify(value, (_key, nested) => nested !== null && typeof nested === "object" && !Array.isArray(nested) ? Object.fromEntries(Object.entries(nested).toSorted(([left], [right]) => left.localeCompare(right))) : nested) ?? "undefined";
42683
+ }
42684
+ function sameSettingsValue(a, b) {
42685
+ return stableStringify(a) === stableStringify(b);
42686
+ }
42687
+ /**
42688
+ * Apply the scope rules for a settings group's scoped keys.
42689
+ *
42690
+ * Generating project settings drops the keys Qwen Code strips from a workspace
42691
+ * file before the merge — writing one there would be dead configuration — and
42692
+ * explains the keys whose project value is honored only conditionally rather
42693
+ * than dropping those. Either way only the override copy is touched, so a value
42694
+ * the user already wrote into the project file stays where it is.
42695
+ *
42696
+ * Generating global settings keeps every key, and announces the write: the
42697
+ * `.rulesync/permissions.jsonc` carrying it may have arrived with a cloned or
42698
+ * fetched repository, and the global file applies to every project on this
42699
+ * machine. So the new value is named alongside the one it replaces, exactly as
42700
+ * the deepagents startup override announces its own relaxations. Every key the
42701
+ * override writes is announced, not only the ones with a scope rule — the
42702
+ * groups are `z.looseObject`s, so an unmodeled key falls back to the
42703
+ * `unmodeled` rule rather than slipping past the gate that the modeled ones
42704
+ * pass through.
42705
+ */
42706
+ function scopeOverrideGroup(overrideGroup, { groupName, scopedKeys, existingGroup, global, filePath, logger }) {
42707
+ const scoped = { ...asPlainRecord(overrideGroup) };
42708
+ const previous = asPlainRecord(existingGroup);
42709
+ const rulesByKey = new Map(Object.entries(scopedKeys));
42710
+ for (const key of Object.keys(scoped)) {
42711
+ const value = scoped[key];
42712
+ if (value === void 0) continue;
42713
+ const scopedKey = rulesByKey.get(key);
42714
+ const rule = scopedKey?.rule ?? "unmodeled";
42715
+ const { stripInProjectScope, announceOnlyGrants } = QWEN_SCOPE_RULES[rule];
42716
+ const projectNote = scopedKey?.projectNote ?? QWEN_SCOPE_RULES[rule].projectNote;
42717
+ const globalNote = scopedKey?.globalNote ?? QWEN_SCOPE_RULES[rule].globalNote;
42718
+ const previousValue = Object.hasOwn(previous, key) ? previous[key] : void 0;
42719
+ const unchanged = sameSettingsValue(previousValue, value);
42720
+ if (!global) {
42721
+ if (stripInProjectScope) delete scoped[key];
42722
+ else if (unchanged) continue;
42723
+ if (projectNote) warnWithFallback(logger, `Qwen permissions: ${projectNote({
42724
+ qualifiedKey: quoteQualifiedKey({
42725
+ groupName,
42726
+ key
42727
+ }),
42728
+ quotedValue: quoteValueForWarning(value),
42729
+ filePath
42730
+ })}`);
42731
+ continue;
42732
+ }
42733
+ if (unchanged) continue;
42734
+ if (announceOnlyGrants && !grantsSomething(value, scopedKey)) continue;
42735
+ const replaced = previousValue === void 0 ? "" : ` (was ${quoteValueForWarning(previousValue)})`;
42736
+ warnWithFallback(logger, `Qwen permissions: the qwencode override wrote ${quoteQualifiedKey({
42737
+ groupName,
42738
+ key
42739
+ })} = ${quoteValueForWarning(value)}${replaced} into ${filePath}, your global Qwen Code settings. ${globalNote}`);
42513
42740
  }
42514
42741
  return scoped;
42515
42742
  }
42743
+ /**
42744
+ * Build the `tools`/`security` patch groups contributed by the `qwencode`
42745
+ * override. Each group is shallow-merged over what `settings.json` already has,
42746
+ * after the scope gate has handled the keys Qwen Code honors in one scope only.
42747
+ */
42748
+ function buildOverrideGroupsPatch({ settings, override, global, filePath, logger }) {
42749
+ const patch = {};
42750
+ for (const { groupName, scopedKeys } of QWEN_OVERRIDE_GROUPS) {
42751
+ const overrideGroup = override?.[groupName];
42752
+ if (overrideGroup === void 0) continue;
42753
+ const existingGroup = settings[groupName];
42754
+ const scoped = scopeOverrideGroup(overrideGroup, {
42755
+ groupName,
42756
+ scopedKeys,
42757
+ existingGroup,
42758
+ global,
42759
+ filePath,
42760
+ logger
42761
+ });
42762
+ const merged = {
42763
+ ...asPlainRecord(existingGroup),
42764
+ ...scoped
42765
+ };
42766
+ if (Object.keys(merged).length > 0) patch[groupName] = merged;
42767
+ }
42768
+ return patch;
42769
+ }
42770
+ /**
42771
+ * Warn about a scoped key lifted out of a *project* settings file being
42772
+ * imported. The file carries no scope marker, so import keeps the key either
42773
+ * way — but regenerating in the global scope would turn a value a workspace
42774
+ * could not decide for itself into one Qwen Code enforces everywhere, and a
42775
+ * settings file read out of a cloned repository is exactly where such a value
42776
+ * comes from. Importing the global file is the case this has nothing to say
42777
+ * about: the value is already in the scope the warning would send it to.
42778
+ */
42779
+ function warnAboutImportedScopedKeys(groups) {
42780
+ for (const { groupName, scopedKeys } of QWEN_OVERRIDE_GROUPS) {
42781
+ const group = groups[groupName];
42782
+ for (const [key, scopedKey] of Object.entries(scopedKeys)) {
42783
+ const { rule } = scopedKey;
42784
+ const value = group[key];
42785
+ if (value === void 0) continue;
42786
+ const { announceOnlyGrants, announceOnImport } = QWEN_SCOPE_RULES[rule];
42787
+ if (!announceOnImport) continue;
42788
+ if (announceOnlyGrants && !grantsSomething(value, scopedKey)) continue;
42789
+ const globalNote = scopedKey.globalNote ?? QWEN_SCOPE_RULES[rule].globalNote;
42790
+ moduleLogger$1.warn(`Qwen permissions: imported ${quoteQualifiedKey({
42791
+ groupName,
42792
+ key
42793
+ })} = ${quoteValueForWarning(value)}. ${globalNote} Review it before generating with the global scope.`);
42794
+ }
42795
+ }
42796
+ }
42516
42797
  var QwencodePermissions = class QwencodePermissions extends ToolPermissions {
42517
42798
  constructor(params) {
42518
42799
  super({
@@ -42541,7 +42822,8 @@ var QwencodePermissions = class QwencodePermissions extends ToolPermissions {
42541
42822
  relativeDirPath: paths.relativeDirPath,
42542
42823
  relativeFilePath: paths.relativeFilePath,
42543
42824
  fileContent,
42544
- validate
42825
+ validate,
42826
+ global
42545
42827
  });
42546
42828
  }
42547
42829
  static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, global = false, logger }) {
@@ -42557,6 +42839,7 @@ var QwencodePermissions = class QwencodePermissions extends ToolPermissions {
42557
42839
  } catch (error) {
42558
42840
  throw new Error(`Failed to parse existing Qwen settings at ${filePath}: ${formatError(error)}`, { cause: error });
42559
42841
  }
42842
+ const displayPath = toPosixPath(join(global ? "~" : ".", paths.relativeDirPath, paths.relativeFilePath));
42560
42843
  const config = rulesyncPermissions.getJson();
42561
42844
  const { allow, ask, deny } = convertRulesyncToQwenPermissions(config);
42562
42845
  const managedToolNames = new Set(Object.keys(config.permission).map((category) => toQwenToolName(category)));
@@ -42575,24 +42858,25 @@ var QwencodePermissions = class QwencodePermissions extends ToolPermissions {
42575
42858
  if (mergedDeny.length > 0) mergedPermissions.deny = mergedDeny;
42576
42859
  else delete mergedPermissions.deny;
42577
42860
  const override = config.qwencode;
42578
- if (override?.autoMode !== void 0) mergedPermissions.autoMode = override.autoMode;
42579
- const patch = { permissions: mergedPermissions };
42580
- if (override?.tools !== void 0) patch.tools = {
42581
- ...asPlainRecord(settings.tools),
42582
- ...asPlainRecord(override.tools)
42583
- };
42584
- if (override?.security !== void 0) {
42585
- const scopedSecurity = scopeOverrideSecurity(override.security, {
42861
+ if (override?.autoMode !== void 0) {
42862
+ const scopedAutoMode = scopeOverrideGroup({ autoMode: override.autoMode }, {
42863
+ groupName: "permissions",
42864
+ scopedKeys: QWEN_SCOPED_PERMISSIONS_KEYS,
42865
+ existingGroup: existingPermissions,
42586
42866
  global,
42587
- relativeFilePath: paths.relativeFilePath,
42867
+ filePath: displayPath,
42588
42868
  logger
42589
42869
  });
42590
- const mergedSecurity = {
42591
- ...asPlainRecord(settings.security),
42592
- ...scopedSecurity
42593
- };
42594
- if (Object.keys(mergedSecurity).length > 0) patch.security = mergedSecurity;
42870
+ if (scopedAutoMode.autoMode !== void 0) mergedPermissions.autoMode = scopedAutoMode.autoMode;
42595
42871
  }
42872
+ const patch = { permissions: mergedPermissions };
42873
+ Object.assign(patch, buildOverrideGroupsPatch({
42874
+ settings,
42875
+ override,
42876
+ global,
42877
+ filePath: displayPath,
42878
+ logger
42879
+ }));
42596
42880
  const fileContent = applySharedConfigPatch({
42597
42881
  fileKey: sharedConfigFileKey(paths),
42598
42882
  feature: "permissions",
@@ -42624,16 +42908,17 @@ var QwencodePermissions = class QwencodePermissions extends ToolPermissions {
42624
42908
  ask: permissions.ask ?? [],
42625
42909
  deny: permissions.deny ?? []
42626
42910
  });
42627
- const overrideTools = pickQwenOverrideKeys(settings.tools, QWEN_OVERRIDE_TOOLS_KEYS);
42628
- const overrideSecurity = pickQwenOverrideKeys(settings.security, QWEN_OVERRIDE_SECURITY_KEYS);
42629
- for (const key of QWEN_GLOBAL_ONLY_SECURITY_KEYS) {
42630
- if (overrideSecurity[key] === void 0) continue;
42631
- moduleLogger$1.warn(`Qwen permissions: imported 'security.${key}'. Qwen Code ignores it in workspace settings but enforces it in user/system settings, so review it before generating with the global scope.`);
42632
- }
42911
+ const overrideGroups = {
42912
+ tools: pickQwenOverrideKeys(settings.tools, QWEN_OVERRIDE_TOOLS_KEYS),
42913
+ security: pickQwenOverrideKeys(settings.security, QWEN_OVERRIDE_SECURITY_KEYS)
42914
+ };
42915
+ if (!this.global) warnAboutImportedScopedKeys(overrideGroups);
42633
42916
  const overridePermissions = pickQwenOverrideKeys(settings.permissions, QWEN_OVERRIDE_PERMISSIONS_KEYS);
42634
42917
  const qwencodeOverride = {};
42635
- if (Object.keys(overrideTools).length > 0) qwencodeOverride.tools = overrideTools;
42636
- if (Object.keys(overrideSecurity).length > 0) qwencodeOverride.security = overrideSecurity;
42918
+ for (const { groupName } of QWEN_OVERRIDE_GROUPS) {
42919
+ const group = overrideGroups[groupName];
42920
+ if (Object.keys(group).length > 0) qwencodeOverride[groupName] = group;
42921
+ }
42637
42922
  if (overridePermissions.autoMode !== void 0) qwencodeOverride.autoMode = overridePermissions.autoMode;
42638
42923
  const result = { ...config };
42639
42924
  if (Object.keys(qwencodeOverride).length > 0) result.qwencode = qwencodeOverride;
@@ -69137,4 +69422,4 @@ async function importChecksCore(params) {
69137
69422
  //#endregion
69138
69423
  export { RulesyncCheckFrontmatterSchema as $, ALL_TOOL_TARGETS_WITH_WILDCARD as $t, FACTORYDROID_DIR as A, RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH as An, fileExists as At, RulesyncSkillFrontmatterSchema as B, stripControlCharacters as Bn, readFileContent as Bt, CODEXCLI_BASH_RULES_FILE_NAME as C, RULESYNC_PERMISSIONS_LEGACY_FILE_NAME as Cn, assertDirectoryIfExists as Ct, CLAUDECODE_MEMORIES_DIR_NAME as D, RULESYNC_RULES_RELATIVE_DIR_PATH as Dn, createTempDirectory as Dt, CLAUDECODE_LOCAL_RULE_FILE_NAME as E, RULESYNC_RELATIVE_DIR_PATH as En, checkPathTraversal as Et, AUGMENTCODE_SETTINGS_LOCAL_FILE_NAME as F, formatError as Fn, isSymlink as Ft, RulesyncIgnore as G, removeFileStrict as Gt, RulesyncRuleFrontmatterSchema as H, stripHiddenCharacters as Hn, removeDirectory as Ht, getLocalSkillDirNames as I, truncateText as In, listDirectoryEntryNames as It, resolveRulesyncSourceWritePath as J, runWithDirectoryRollback as Jt, RulesyncHooks as K, removeTempDirectory as Kt, RulesyncSubagent as L, hasDeceptiveHiddenCharacters as Ln, listFilePathsRecursively as Lt, caseFoldIdentity as M, ALL_FEATURES as Mn, getHomeDirectory as Mt, groupSpellingsByCaseFoldedIdentity as N, ALL_FEATURES_WITH_WILDCARD as Nn, isFileNotFoundError as Nt, CLAUDECODE_SETTINGS_LOCAL_FILE_NAME as O, RULESYNC_SKILLS_RELATIVE_DIR_PATH as On, directoryExists as Ot, AUGMENTCODE_DIR as P, DEPRECATED_FEATURE_REPLACEMENTS as Pn, isFileSystemError as Pt, RulesyncCheck as Q, ALL_TOOL_TARGETS as Qt, RulesyncSubagentFrontmatterSchema as R, hasEnclosingMarkOutsideKeycap as Rn, listSubdirectoryNames as Rt, ChecksProcessor as S, RULESYNC_PERMISSIONS_FILE_NAME as Sn, applyFileMode as St, CLAUDECODE_DIR as T, RULESYNC_PERMISSIONS_SCHEMA_URL as Tn, assertWritablePathInsideRoot as Tt, RulesyncPermissions as U, removeDirectoryStrict as Ut, RulesyncRule as V, stripControlCharactersKeepingLineFeeds as Vn, readFileContentOrNull as Vt, RulesyncMcp as W, removeFile as Wt, RulesyncCommand as X, writeFileBuffer as Xt, parseJsonc as Y, toPosixPath as Yt, RulesyncCommandFrontmatterSchema as Z, writeFileContent as Zt, IgnoreProcessor as _, RULESYNC_MCP_FILE_NAME as _n, withFallbackLoggerTarget as _t, getProcessorRegistryEntry as a, RULESYNC_AIIGNORE_RELATIVE_FILE_PATH as an, mergeInputRootConfigs as at, QWENCODE_DIR as b, RULESYNC_MCP_SCHEMA_URL as bn, CLIError as bt, RulesProcessor as c, RULESYNC_CONFIG_RELATIVE_FILE_PATH as cn, ConfigFileSchema as ct, CODEBUDDY_DIR as d, RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH as dn, findControlCharacter as dt, PACKAGING_TOOL_TARGETS as en, stringifyFrontmatter as et, CODEBUDDY_LOCAL_RULE_FILE_NAME as f, RULESYNC_HOOKS_FILE_NAME as fn, ConsoleLogger as ft, McpProcessor as g, RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH as gn, warnOnConflictingFlags as gt, shortenToWidth as h, RULESYNC_IGNORE_RELATIVE_FILE_PATH as hn, fallbackLogger as ht, inspectInputRoots as i, RULESYNC_AIIGNORE_FILE_NAME as in, ConfigResolver as it, FACTORYDROID_SETTINGS_LOCAL_FILE_NAME as j, parseCommaSeparatedList as jn, getFileSize as jt, CLAUDECODE_SKILLS_DIR_PATH as k, RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH as kn, ensureDir as kt, SubagentsProcessor as l, RULESYNC_CONFIG_SCHEMA_URL as ln, GITIGNORE_DESTINATION_KEY as lt, displayWidthOf as m, RULESYNC_HOOKS_RELATIVE_FILE_PATH as mn, WarningCollectingLogger as mt, formatSourceLoadFailure as n, CURATED_RULES_FEATURE_SUBDIR as nn, SHARED_USER_MANAGED_CONFIG_PATHS as nt, convertFromTool as o, RULESYNC_CHECKS_RELATIVE_DIR_PATH as on, resolveEffectiveInputRoots as ot, ELLIPSIS_WIDTH as p, RULESYNC_HOOKS_LEGACY_FILE_NAME as pn, JsonLogger as pt, getRulesyncSourceCandidates as q, resolvePath as qt, generate as r, MAX_FILE_SIZE as rn, SKILL_FILE_NAME as rt, isPackagingToolTarget as s, RULESYNC_COMMANDS_RELATIVE_DIR_PATH as sn, CONFLICTING_TARGET_PAIRS as st, importFromTool as t, ToolTargetSchema as tn, loadYaml as tt, SkillsProcessor as u, RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH as un, SourceEntrySchema as ut, HooksProcessor as v, RULESYNC_MCP_LEGACY_FILE_NAME as vn, resetRunWarningState as vt, CODEXCLI_DIR as w, RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH as wn, assertTreeContainsNoSymlinks as wt, QWENCODE_LOCAL_RULE_FILE_NAME as x, RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH as xn, ErrorCodes as xt, CommandsProcessor as y, RULESYNC_MCP_RELATIVE_FILE_PATH as yn, withWarnOnceScope as yt, RulesyncSkill as z, quoteForLog as zn, pathEscapesRoot as zt };
69139
69424
 
69140
- //# sourceMappingURL=import-DIDEUv63.js.map
69425
+ //# sourceMappingURL=import-DyWRanf6.js.map