rulesync 16.2.0 → 16.4.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.
@@ -61,6 +61,13 @@ const ALL_FEATURES = [
61
61
  ...ACTIVE_FEATURES_AFTER_IGNORE
62
62
  ];
63
63
  const ALL_FEATURES_WITH_WILDCARD = [...ALL_FEATURES, "*"];
64
+ /**
65
+ * Features that remain accepted for compatibility but are superseded by a
66
+ * newer feature. Maps each deprecated feature to its replacement; consumers
67
+ * (e.g. `rulesync doctor`) derive their deprecation warnings from this map so
68
+ * the set cannot drift from the schema above.
69
+ */
70
+ const DEPRECATED_FEATURE_REPLACEMENTS = { ignore: "permissions" };
64
71
  const ACTIVE_FEATURES = [...ACTIVE_FEATURES_BEFORE_IGNORE, ...ACTIVE_FEATURES_AFTER_IGNORE];
65
72
  const DeprecatedIgnoreFeatureSchema = z.literal("ignore").check(meta({
66
73
  deprecated: true,
@@ -810,6 +817,7 @@ const ErrorCodes = {
810
817
  GITIGNORE_FAILED: "GITIGNORE_FAILED",
811
818
  INIT_FAILED: "INIT_FAILED",
812
819
  MCP_FAILED: "MCP_FAILED",
820
+ DOCTOR_FAILED: "DOCTOR_FAILED",
813
821
  UNKNOWN_ERROR: "UNKNOWN_ERROR"
814
822
  };
815
823
  /**
@@ -818,10 +826,12 @@ const ErrorCodes = {
818
826
  var CLIError = class extends Error {
819
827
  code;
820
828
  exitCode;
821
- constructor(message, code = ErrorCodes.UNKNOWN_ERROR, exitCode = 1) {
829
+ details;
830
+ constructor(message, code = ErrorCodes.UNKNOWN_ERROR, exitCode = 1, details) {
822
831
  super(message);
823
832
  this.code = code;
824
833
  this.exitCode = exitCode;
834
+ this.details = details;
825
835
  this.name = "CLIError";
826
836
  }
827
837
  };
@@ -945,6 +955,7 @@ var JsonLogger = class extends BaseLogger {
945
955
  message: errorMessage
946
956
  };
947
957
  if (this._verbose && message instanceof Error && message.stack) errorInfo.stack = message.stack;
958
+ if (message instanceof CLIError && message.details !== void 0) errorInfo.details = message.details;
948
959
  this.outputJson(false, errorInfo);
949
960
  }
950
961
  debug(_message, ..._args) {}
@@ -1005,6 +1016,10 @@ function hasControlCharacters(value) {
1005
1016
  }
1006
1017
  //#endregion
1007
1018
  //#region src/config/config.ts
1019
+ /**
1020
+ * Key accepted alongside feature names in the per-feature object form of
1021
+ * `targets`. Exported so `rulesync doctor` treats the same key as valid.
1022
+ */
1008
1023
  const GITIGNORE_DESTINATION_KEY = "gitignoreDestination";
1009
1024
  /**
1010
1025
  * Schema for a single source entry in the sources array.
@@ -1071,7 +1086,9 @@ function normalizeConfigFilePath({ configFilePath, inputRoot }) {
1071
1086
  return isAbsolute(configFilePath) ? configFilePath : resolve(configFilePath);
1072
1087
  }
1073
1088
  /**
1074
- * Conflicting target pairs that cannot be used together
1089
+ * Conflicting target pairs that cannot be used together.
1090
+ * Exported so `rulesync doctor` can report the same conflicts as diagnostics
1091
+ * without duplicating the list.
1075
1092
  */
1076
1093
  const CONFLICTING_TARGET_PAIRS = [["augmentcode", "augmentcode-legacy"], ["claudecode", "claudecode-legacy"]];
1077
1094
  /**
@@ -2339,6 +2356,7 @@ const PI_HOOK_EVENTS = [
2339
2356
  "preToolUse",
2340
2357
  "postToolUse",
2341
2358
  "preModelInvocation",
2359
+ "postModelInvocation",
2342
2360
  "beforeSubmitPrompt",
2343
2361
  "stop",
2344
2362
  "preCompact",
@@ -3045,6 +3063,7 @@ const CANONICAL_TO_PI_EVENT_NAMES = {
3045
3063
  preToolUse: "tool_call",
3046
3064
  postToolUse: "tool_result",
3047
3065
  preModelInvocation: "context",
3066
+ postModelInvocation: "message_end",
3048
3067
  beforeSubmitPrompt: "input",
3049
3068
  stop: "agent_end",
3050
3069
  preCompact: "session_before_compact",
@@ -6810,24 +6829,14 @@ const SHARED_CONFIG_OWNERSHIP = {
6810
6829
  },
6811
6830
  ".devin/config.json": {
6812
6831
  format: "json",
6813
- features: {
6814
- mcp: {
6815
- kind: "replace-owned-keys",
6816
- ownedKeys: ["mcpServers"]
6817
- },
6818
- permissions: {
6819
- kind: "replace-owned-keys",
6820
- ownedKeys: ["permissions"]
6821
- }
6822
- }
6832
+ features: { permissions: {
6833
+ kind: "replace-owned-keys",
6834
+ ownedKeys: ["permissions"]
6835
+ } }
6823
6836
  },
6824
6837
  ".config/devin/config.json": {
6825
6838
  format: "json",
6826
6839
  features: {
6827
- mcp: {
6828
- kind: "replace-owned-keys",
6829
- ownedKeys: ["mcpServers"]
6830
- },
6831
6840
  hooks: {
6832
6841
  kind: "replace-owned-keys",
6833
6842
  ownedKeys: ["hooks"]
@@ -9027,6 +9036,7 @@ const DEVIN_GLOBAL_CONFIG_DIR_PATH = join(".config", "devin");
9027
9036
  const DEVIN_GLOBAL_AGENTS_DIR_PATH = join(DEVIN_GLOBAL_CONFIG_DIR_PATH, "agents");
9028
9037
  const DEVIN_GLOBAL_SKILLS_DIR_PATH = join(DEVIN_GLOBAL_CONFIG_DIR_PATH, "skills");
9029
9038
  const DEVIN_CONFIG_FILE_NAME = "config.json";
9039
+ const DEVIN_MCP_CONFIG_FILE_NAME = "mcp_config.json";
9030
9040
  const DEVIN_HOOKS_V1_FILE_NAME = "hooks.v1.json";
9031
9041
  const DEVIN_GLOBAL_AGENTS_FILE_NAME = "AGENTS.md";
9032
9042
  const DEVIN_IGNORE_FILE_NAME = ".devinignore";
@@ -11284,6 +11294,7 @@ const QWENCODE_COMMANDS_DIR_PATH = join(QWENCODE_DIR, "commands");
11284
11294
  const QWENCODE_AGENTS_DIR_PATH = join(QWENCODE_DIR, "agents");
11285
11295
  const QWENCODE_SKILLS_DIR_PATH = join(QWENCODE_DIR, "skills");
11286
11296
  const QWENCODE_RULE_FILE_NAME = "QWEN.md";
11297
+ const QWENCODE_LOCAL_RULE_FILE_NAME = "QWEN.local.md";
11287
11298
  const QWENCODE_IGNORE_FILE_NAME = ".qwenignore";
11288
11299
  const QWENCODE_SETTINGS_FILE_NAME = "settings.json";
11289
11300
  //#endregion
@@ -12030,6 +12041,7 @@ const WARP_SKILLS_DIR_PATH = join(WARP_DIR, "skills");
12030
12041
  const WARP_LINUX_DIR = join(".config", "warp-terminal");
12031
12042
  const WARP_WIN32_DIR = join("AppData", "Local", "warp", "Warp", "config");
12032
12043
  const WARP_RULE_FILE_NAME = "AGENTS.md";
12044
+ const WARP_GLOBAL_RULE_DIR = ".agents";
12033
12045
  const WARP_MCP_FILE_NAME = ".mcp.json";
12034
12046
  const WARP_PERMISSIONS_FILE_NAME = "settings.toml";
12035
12047
  const WARP_IGNORE_FILE_NAME = ".warpindexingignore";
@@ -14765,9 +14777,9 @@ const DEVIN_CONVERTER_CONFIG = {
14765
14777
  * - Project scope: `.devin/hooks.v1.json`. This is a standalone file whose top
14766
14778
  * level IS the event map (no wrapper key).
14767
14779
  * - Global scope: `~/.config/devin/config.json` under the `"hooks"` key. This
14768
- * file is shared with the MCP (`mcpServers`) and permissions (`permissions`)
14769
- * features, so reads and writes merge into the existing JSON and the file is
14770
- * never deleted in global mode.
14780
+ * file is shared with the permissions (`permissions`) feature (MCP moved to
14781
+ * the dedicated mcp_config.json in v3000.3), so reads and writes merge into
14782
+ * the existing JSON and the file is never deleted in global mode.
14771
14783
  *
14772
14784
  * @see https://docs.devin.ai/cli/extensibility/hooks/overview
14773
14785
  */
@@ -16379,6 +16391,13 @@ var OpencodeHooks = class OpencodeHooks extends ToolHooks {
16379
16391
  */
16380
16392
  const PI_TOOL_EVENTS = /* @__PURE__ */ new Set(["tool_call", "tool_result"]);
16381
16393
  /**
16394
+ * Pi extension events that fire for every message role (user, assistant,
16395
+ * toolResult). The generated handler gates these on the assistant role so a
16396
+ * `postModelInvocation` hook runs once per finalized model response rather
16397
+ * than for every message in the conversation.
16398
+ */
16399
+ const PI_ASSISTANT_MESSAGE_EVENTS = /* @__PURE__ */ new Set(["message_end"]);
16400
+ /**
16382
16401
  * Validate a hook matcher as a regular expression and return it as a JS
16383
16402
  * string-literal (JSON.stringify quoting) safe to embed in generated code.
16384
16403
  */
@@ -16419,7 +16438,10 @@ function buildSubscriptionLines(handlerGroups) {
16419
16438
  const lines = [];
16420
16439
  for (const [piEvent, handlers] of Object.entries(handlerGroups)) {
16421
16440
  const usesToolName = PI_TOOL_EVENTS.has(piEvent) && handlers.some((h) => h.matcher);
16422
- lines.push(` pi.on(${JSON.stringify(piEvent)}, async (${usesToolName ? "event" : ""}) => {`);
16441
+ const gatesOnAssistant = PI_ASSISTANT_MESSAGE_EVENTS.has(piEvent);
16442
+ const usesEvent = usesToolName || gatesOnAssistant;
16443
+ lines.push(` pi.on(${JSON.stringify(piEvent)}, async (${usesEvent ? "event" : ""}) => {`);
16444
+ if (gatesOnAssistant) lines.push(` if (event.message.role !== "assistant") return;`);
16423
16445
  for (const handler of handlers) {
16424
16446
  const embeddedCommand = JSON.stringify(handler.command);
16425
16447
  if (usesToolName && handler.matcher) {
@@ -16554,6 +16576,7 @@ function canonicalDefToQwencodeHook(def) {
16554
16576
  const type = def.type ?? "command";
16555
16577
  const isHttp = type === "http";
16556
16578
  const isCommand = type === "command";
16579
+ const isPrompt = type === "prompt";
16557
16580
  return {
16558
16581
  type,
16559
16582
  ...compact({
@@ -16563,6 +16586,8 @@ function canonicalDefToQwencodeHook(def) {
16563
16586
  name: def.name,
16564
16587
  description: def.description,
16565
16588
  statusMessage: def.statusMessage,
16589
+ prompt: isPrompt ? def.prompt : void 0,
16590
+ model: isPrompt ? def.model : void 0,
16566
16591
  async: isCommand ? def.async : void 0,
16567
16592
  env: isCommand ? def.env : void 0,
16568
16593
  shell: isCommand ? def.shell : void 0,
@@ -16581,7 +16606,7 @@ function canonicalDefToQwencodeHook(def) {
16581
16606
  * is needed here — commands are passed through verbatim and any such variable
16582
16607
  * reference stays intact for Qwen Code to expand.
16583
16608
  */
16584
- function canonicalToQwencodeHooks(config) {
16609
+ function canonicalToQwencodeHooks(config, logger) {
16585
16610
  const qwencodeSupported = new Set(QWENCODE_HOOK_EVENTS);
16586
16611
  const sharedHooks = {};
16587
16612
  for (const [event, defs] of Object.entries(config.hooks)) if (qwencodeSupported.has(event)) sharedHooks[event] = defs;
@@ -16601,6 +16626,7 @@ function canonicalToQwencodeHooks(config) {
16601
16626
  const byMatcher = /* @__PURE__ */ new Map();
16602
16627
  for (const def of definitions) {
16603
16628
  if (!qwencodeSupportedTypes.has(def.type ?? "command")) continue;
16629
+ if (def.type === "prompt" && !def.prompt) logger?.warn(`Qwen Code prompt hook on '${eventName}' has no 'prompt' field; Qwen Code will load it and fail it at runtime.`);
16604
16630
  const key = def.matcher ?? "";
16605
16631
  const list = byMatcher.get(key);
16606
16632
  if (list) list.push(def);
@@ -16639,7 +16665,9 @@ const QwencodeHookEntrySchema = z.looseObject({
16639
16665
  shell: z.optional(z.string()),
16640
16666
  headers: z.optional(z.record(z.string(), z.string())),
16641
16667
  allowedEnvVars: z.optional(z.array(z.string())),
16642
- once: z.optional(z.boolean())
16668
+ once: z.optional(z.boolean()),
16669
+ prompt: z.optional(z.string()),
16670
+ model: z.optional(z.string())
16643
16671
  });
16644
16672
  /**
16645
16673
  * A matcher group entry in a Qwen Code event array.
@@ -16654,37 +16682,43 @@ const QwencodeMatcherEntrySchema = z.looseObject({
16654
16682
  /**
16655
16683
  * Convert a single parsed Qwen Code matcher group into canonical hook definitions.
16656
16684
  */
16685
+ function qwencodeHookEntryToCanonical({ hook, sequential, matcher }) {
16686
+ const h = hook;
16687
+ const hookType = h.type === "command" || h.type === "prompt" || h.type === "http" || h.type === "function" ? h.type : "command";
16688
+ const isHttp = hookType === "http";
16689
+ const isCommand = hookType === "command";
16690
+ const isPrompt = hookType === "prompt";
16691
+ const shell = h.shell === "bash" || h.shell === "powershell" ? h.shell : void 0;
16692
+ return {
16693
+ type: hookType,
16694
+ ...compact({
16695
+ command: h.command,
16696
+ url: h.url,
16697
+ timeout: h.timeout,
16698
+ name: h.name,
16699
+ description: h.description,
16700
+ statusMessage: h.statusMessage,
16701
+ async: isCommand ? h.async : void 0,
16702
+ env: isCommand ? h.env : void 0,
16703
+ shell: isCommand ? shell : void 0,
16704
+ headers: isHttp ? h.headers : void 0,
16705
+ allowedEnvVars: isHttp ? h.allowedEnvVars : void 0,
16706
+ once: isHttp ? h.once : void 0,
16707
+ prompt: isPrompt ? h.prompt : void 0,
16708
+ model: isPrompt ? h.model : void 0,
16709
+ sequential: sequential ? true : void 0,
16710
+ matcher
16711
+ })
16712
+ };
16713
+ }
16657
16714
  function qwencodeMatcherEntryToCanonical(entry) {
16658
- const defs = [];
16659
- const hooks = entry.hooks ?? [];
16660
16715
  const sequential = entry.sequential === true;
16661
16716
  const matcher = entry.matcher !== void 0 && entry.matcher !== null && entry.matcher !== "" ? entry.matcher : void 0;
16662
- for (const h of hooks) {
16663
- const hookType = h.type === "command" || h.type === "prompt" || h.type === "http" || h.type === "function" ? h.type : "command";
16664
- const isHttp = hookType === "http";
16665
- const isCommand = hookType === "command";
16666
- const shell = h.shell === "bash" || h.shell === "powershell" ? h.shell : void 0;
16667
- defs.push({
16668
- type: hookType,
16669
- ...compact({
16670
- command: h.command,
16671
- url: h.url,
16672
- timeout: h.timeout,
16673
- name: h.name,
16674
- description: h.description,
16675
- statusMessage: h.statusMessage,
16676
- async: isCommand ? h.async : void 0,
16677
- env: isCommand ? h.env : void 0,
16678
- shell: isCommand ? shell : void 0,
16679
- headers: isHttp ? h.headers : void 0,
16680
- allowedEnvVars: isHttp ? h.allowedEnvVars : void 0,
16681
- once: isHttp ? h.once : void 0,
16682
- sequential: sequential ? true : void 0,
16683
- matcher
16684
- })
16685
- });
16686
- }
16687
- return defs;
16717
+ return (entry.hooks ?? []).map((hook) => qwencodeHookEntryToCanonical({
16718
+ hook,
16719
+ sequential,
16720
+ matcher
16721
+ }));
16688
16722
  }
16689
16723
  /**
16690
16724
  * Extract hooks from Qwen Code settings.json into canonical format.
@@ -16732,12 +16766,12 @@ var QwencodeHooks = class QwencodeHooks extends ToolHooks {
16732
16766
  validate
16733
16767
  });
16734
16768
  }
16735
- static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false }) {
16769
+ static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false, logger }) {
16736
16770
  const paths = QwencodeHooks.getSettablePaths({ global });
16737
16771
  const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
16738
16772
  const existingContent = await readFileContentOrNull(filePath) ?? JSON.stringify({}, null, 2);
16739
16773
  const config = rulesyncHooks.getJson();
16740
- const patch = { hooks: canonicalToQwencodeHooks(config) };
16774
+ const patch = { hooks: canonicalToQwencodeHooks(config, logger) };
16741
16775
  const disableAllHooks = config.qwencode?.disableAllHooks;
16742
16776
  if (typeof disableAllHooks === "boolean") patch.disableAllHooks = disableAllHooks;
16743
16777
  const fileContent = applySharedConfigPatch({
@@ -20763,18 +20797,24 @@ var DeepagentsMcp = class DeepagentsMcp extends ToolMcp {
20763
20797
  /**
20764
20798
  * MCP generator for Devin Local (native `.devin/` configuration).
20765
20799
  *
20766
- * Devin reads MCP servers from the `mcpServers` key of its native config file:
20767
- * - Project scope: `.devin/config.json`
20768
- * - Global scope: `~/.config/devin/config.json`
20800
+ * Since v3000.3 (the Local 3.6 release), Devin reads MCP servers from a
20801
+ * dedicated `mcpServers`-keyed config file rather than the shared
20802
+ * `config.json`:
20803
+ * - Project scope: `.devin/mcp_config.json`
20804
+ * - Global scope: `~/.config/devin/mcp_config.json`
20769
20805
  *
20770
- * The config file is shared with the permissions feature (`permissions` key)
20771
- * and, in global mode, the hooks feature (`hooks` key), so reads and writes
20772
- * merge into the existing JSON rather than overwriting it, and the file is
20773
- * never deleted. Each server is a stdio entry ({ command, args, env }) or a
20774
- * remote entry ({ serverUrl | url, headers }), and may carry an optional
20775
- * `disabledTools` array.
20806
+ * Legacy `mcpServers` entries left in `config.json` are auto-migrated into
20807
+ * the dedicated file on Devin startup, so writing the legacy key would fight
20808
+ * the migration. Import still falls back to the legacy `config.json`
20809
+ * `mcpServers` key when no dedicated file exists, so pre-v3000.3 repos
20810
+ * migrate cleanly. The gitignored `.devin/mcp_config.local.json` override is
20811
+ * the user's personal territory and is never read or written.
20776
20812
  *
20777
- * @see https://docs.devin.ai/cli/extensibility/configuration
20813
+ * Each server is a stdio entry ({ command, args, env }) or a remote entry
20814
+ * ({ serverUrl | url, headers }), and may carry an optional `disabledTools`
20815
+ * array.
20816
+ *
20817
+ * @see https://docs.devin.ai/cli/extensibility/mcp/configuration
20778
20818
  */
20779
20819
  var DevinMcp = class DevinMcp extends ToolMcp {
20780
20820
  json;
@@ -20792,31 +20832,27 @@ var DevinMcp = class DevinMcp extends ToolMcp {
20792
20832
  throw new Error(`Failed to parse Devin MCP config at ${join(relativeDirPath, relativeFilePath)}: ${formatError(error)}`, { cause: error });
20793
20833
  }
20794
20834
  }
20795
- /**
20796
- * config.json may carry the permissions/hooks features' keys, so it is never
20797
- * deleted; only the managed `mcpServers` key is rewritten.
20798
- */
20799
- isDeletable() {
20800
- return false;
20801
- }
20802
20835
  static getSettablePaths({ global = false } = {}) {
20803
20836
  if (global) return {
20804
20837
  relativeDirPath: DEVIN_GLOBAL_CONFIG_DIR_PATH,
20805
- relativeFilePath: DEVIN_CONFIG_FILE_NAME
20838
+ relativeFilePath: DEVIN_MCP_CONFIG_FILE_NAME
20806
20839
  };
20807
20840
  return {
20808
20841
  relativeDirPath: DEVIN_DIR,
20809
- relativeFilePath: DEVIN_CONFIG_FILE_NAME
20842
+ relativeFilePath: DEVIN_MCP_CONFIG_FILE_NAME
20810
20843
  };
20811
20844
  }
20812
20845
  static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
20813
20846
  const paths = this.getSettablePaths({ global });
20814
- const fileContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "{\"mcpServers\":{}}";
20815
- const json = this.parseJsonOrThrow(fileContent, paths.relativeDirPath, paths.relativeFilePath);
20816
- const newJson = {
20817
- ...json,
20818
- mcpServers: json.mcpServers ?? {}
20819
- };
20847
+ let fileContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath));
20848
+ if (fileContent === null) {
20849
+ const legacyContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, DEVIN_CONFIG_FILE_NAME));
20850
+ if (legacyContent !== null) {
20851
+ const legacyJson = this.parseJsonOrThrow(legacyContent, paths.relativeDirPath, DEVIN_CONFIG_FILE_NAME);
20852
+ fileContent = JSON.stringify({ mcpServers: legacyJson.mcpServers ?? {} }, null, 2);
20853
+ }
20854
+ }
20855
+ const newJson = { mcpServers: this.parseJsonOrThrow(fileContent ?? "{\"mcpServers\":{}}", paths.relativeDirPath, paths.relativeFilePath).mcpServers ?? {} };
20820
20856
  return new DevinMcp({
20821
20857
  outputRoot,
20822
20858
  relativeDirPath: paths.relativeDirPath,
@@ -20826,21 +20862,21 @@ var DevinMcp = class DevinMcp extends ToolMcp {
20826
20862
  global
20827
20863
  });
20828
20864
  }
20829
- static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false }) {
20865
+ static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false, logger }) {
20830
20866
  const paths = this.getSettablePaths({ global });
20831
- const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
20832
- const existingContent = await readFileContentOrNull(filePath) ?? JSON.stringify({ mcpServers: {} }, null, 2);
20867
+ const existingContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath));
20868
+ if (existingContent !== null) {
20869
+ const existingJson = this.parseJsonOrThrow(existingContent, paths.relativeDirPath, paths.relativeFilePath);
20870
+ const existingServers = existingJson.mcpServers && typeof existingJson.mcpServers === "object" ? Object.keys(existingJson.mcpServers) : [];
20871
+ const managedServers = new Set(Object.keys(rulesyncMcp.getMcpServers()));
20872
+ const dropped = existingServers.filter((name) => !managedServers.has(name));
20873
+ if (dropped.length > 0) logger?.warn(`Devin MCP servers not managed by rulesync will be removed from ${join(paths.relativeDirPath, paths.relativeFilePath)}: ${dropped.join(", ")}. Run 'rulesync import' first to keep them, or move them to mcp_config.local.json.`);
20874
+ }
20833
20875
  return new DevinMcp({
20834
20876
  outputRoot,
20835
20877
  relativeDirPath: paths.relativeDirPath,
20836
20878
  relativeFilePath: paths.relativeFilePath,
20837
- fileContent: applySharedConfigPatch({
20838
- fileKey: sharedConfigFileKey(paths),
20839
- feature: "mcp",
20840
- existingContent,
20841
- patch: { mcpServers: rulesyncMcp.getMcpServers() },
20842
- filePath
20843
- }),
20879
+ fileContent: JSON.stringify({ mcpServers: rulesyncMcp.getMcpServers() }, null, 2),
20844
20880
  validate,
20845
20881
  global
20846
20882
  });
@@ -27558,9 +27594,10 @@ function buildDevinPermissionEntry(scope, pattern) {
27558
27594
  * - Project scope: `.devin/config.json`
27559
27595
  * - Global scope: `~/.config/devin/config.json`
27560
27596
  *
27561
- * The config file is shared with the MCP (`mcpServers`) and, in global mode, the
27562
- * hooks (`hooks`) features, so reads and writes merge into the existing JSON and
27563
- * the file is never deleted; only the managed `permissions` key is rewritten.
27597
+ * In global mode the config file is shared with the hooks (`hooks`) feature
27598
+ * (MCP moved to the dedicated mcp_config.json in v3000.3), so reads and writes
27599
+ * merge into the existing JSON and the file is never deleted; only the managed
27600
+ * `permissions` key is rewritten.
27564
27601
  *
27565
27602
  * @see https://docs.devin.ai/cli/reference/permissions
27566
27603
  */
@@ -31434,6 +31471,10 @@ function ensurePermission(permission, category) {
31434
31471
  const WARP_GLOBAL_ONLY_MESSAGE = "Warp permissions are global-only; use --global to sync Warp's settings.toml";
31435
31472
  const ALLOWLIST_KEY = "agent_mode_command_execution_allowlist";
31436
31473
  const DENYLIST_KEY = "agent_mode_command_execution_denylist";
31474
+ const EXECUTION_PROFILES_KEY = "execution_profiles";
31475
+ const DEFAULT_PROFILE_KEY = "default";
31476
+ const PROFILE_ALLOWLIST_KEY = "command_allowlist";
31477
+ const PROFILE_DENYLIST_KEY = "command_denylist";
31437
31478
  const WARP_OVERRIDE_KEYS = [
31438
31479
  "agent_mode_coding_permissions",
31439
31480
  "agent_mode_coding_file_read_allowlist",
@@ -31461,11 +31502,21 @@ function warpSettingsDir() {
31461
31502
  /**
31462
31503
  * Permissions adapter for Warp.
31463
31504
  *
31464
- * Warp gates **shell command** execution through two regex arrays under the
31465
- * `[agents.profiles]` table of the global user `settings.toml`:
31466
- * - `agent_mode_command_execution_allowlist` commands that auto-execute.
31467
- * - `agent_mode_command_execution_denylist` — commands that always require
31468
- * permission (the denylist wins over the allowlist).
31505
+ * Warp gates **shell command** execution through two regex arrays in the
31506
+ * global user `settings.toml`. Since file-backed execution profiles went
31507
+ * Stable (2026-07-28) the authoritative surface is the `default` record of the
31508
+ * `[agents.execution_profiles.<id>]` collection:
31509
+ * - `command_allowlist` commands that auto-execute.
31510
+ * - `command_denylist` — commands that always require permission (the denylist
31511
+ * wins over the allowlist).
31512
+ *
31513
+ * The legacy `[agents.profiles]` keys
31514
+ * (`agent_mode_command_execution_allowlist` / `denylist`) are consumed only
31515
+ * once by Warp's one-shot migration and are ignored afterwards. Both surfaces
31516
+ * are written: the legacy block keeps un-migrated installs and old clients
31517
+ * working, and the `default` execution profile (merged in place, only when the
31518
+ * collection already exists) keeps migrated installs enforcing the lists.
31519
+ * Import prefers the execution profile and falls back to the legacy keys.
31469
31520
  *
31470
31521
  * This surface is **global only** — there is no project-scoped Warp permissions
31471
31522
  * file. rulesync's canonical `permission.bash` patterns map directly (`allow` →
@@ -31544,6 +31595,18 @@ var WarpPermissions = class WarpPermissions extends ToolPermissions {
31544
31595
  if (mergedDeny.length > 0) profiles[DENYLIST_KEY] = mergedDeny;
31545
31596
  else delete profiles[DENYLIST_KEY];
31546
31597
  agents.profiles = profiles;
31598
+ if (isRecord(agents[EXECUTION_PROFILES_KEY])) {
31599
+ const executionProfiles = { ...agents[EXECUTION_PROFILES_KEY] };
31600
+ const defaultProfile = isRecord(executionProfiles[DEFAULT_PROFILE_KEY]) ? { ...executionProfiles[DEFAULT_PROFILE_KEY] } : {};
31601
+ if (mergedAllow.length > 0) defaultProfile[PROFILE_ALLOWLIST_KEY] = mergedAllow;
31602
+ else delete defaultProfile[PROFILE_ALLOWLIST_KEY];
31603
+ if (mergedDeny.length > 0) defaultProfile[PROFILE_DENYLIST_KEY] = mergedDeny;
31604
+ else delete defaultProfile[PROFILE_DENYLIST_KEY];
31605
+ executionProfiles[DEFAULT_PROFILE_KEY] = defaultProfile;
31606
+ agents[EXECUTION_PROFILES_KEY] = executionProfiles;
31607
+ const otherProfileIds = Object.keys(executionProfiles).filter((id) => id !== DEFAULT_PROFILE_KEY);
31608
+ if (mergedDeny.length > 0 && otherProfileIds.length > 0 && logger) logger.warn(`Warp command deny rules were written to the 'default' execution profile only; they are not enforced while another profile (${otherProfileIds.join(", ")}) is active.`);
31609
+ }
31547
31610
  settings.agents = agents;
31548
31611
  return new WarpPermissions({
31549
31612
  outputRoot,
@@ -31563,9 +31626,11 @@ var WarpPermissions = class WarpPermissions extends ToolPermissions {
31563
31626
  }
31564
31627
  const agents = isRecord(settings.agents) ? settings.agents : {};
31565
31628
  const profiles = isRecord(agents.profiles) ? agents.profiles : {};
31629
+ const executionProfiles = isRecord(agents[EXECUTION_PROFILES_KEY]) ? agents[EXECUTION_PROFILES_KEY] : void 0;
31630
+ const defaultProfile = executionProfiles && isRecord(executionProfiles[DEFAULT_PROFILE_KEY]) ? executionProfiles[DEFAULT_PROFILE_KEY] : void 0;
31566
31631
  const config = convertWarpToRulesyncPermissions({
31567
- allow: isStringArray$1(profiles[ALLOWLIST_KEY]) ? profiles[ALLOWLIST_KEY] : [],
31568
- deny: isStringArray$1(profiles[DENYLIST_KEY]) ? profiles[DENYLIST_KEY] : []
31632
+ allow: defaultProfile ? isStringArray$1(defaultProfile[PROFILE_ALLOWLIST_KEY]) ? defaultProfile[PROFILE_ALLOWLIST_KEY] : [] : isStringArray$1(profiles[ALLOWLIST_KEY]) ? profiles[ALLOWLIST_KEY] : [],
31633
+ deny: defaultProfile ? isStringArray$1(defaultProfile[PROFILE_DENYLIST_KEY]) ? defaultProfile[PROFILE_DENYLIST_KEY] : [] : isStringArray$1(profiles[DENYLIST_KEY]) ? profiles[DENYLIST_KEY] : []
31569
31634
  });
31570
31635
  const warpOverride = {};
31571
31636
  for (const key of WARP_OVERRIDE_KEYS) if (profiles[key] !== void 0) warpOverride[key] = profiles[key];
@@ -37315,7 +37380,8 @@ const toolSkillFactories = /* @__PURE__ */ new Map([
37315
37380
  meta: {
37316
37381
  supportsProject: true,
37317
37382
  supportsSimulated: false,
37318
- supportsGlobal: true
37383
+ supportsGlobal: true,
37384
+ lenientImport: true
37319
37385
  }
37320
37386
  }],
37321
37387
  ["aiassistant", {
@@ -37752,7 +37818,7 @@ var SkillsProcessor = class extends DirFeatureProcessor {
37752
37818
  global: this.global
37753
37819
  });
37754
37820
  } catch (error) {
37755
- if (!isConfiguredRoot) throw error;
37821
+ if (!isConfiguredRoot && !factory.meta.lenientImport) throw error;
37756
37822
  this.logger.warn(`Skipping ${join(relativeDirPath, dirName)}: ${formatError(error)}`);
37757
37823
  return null;
37758
37824
  }
@@ -46401,6 +46467,13 @@ var QwencodeRule = class QwencodeRule extends ToolRule {
46401
46467
  toolTarget: "qwencode"
46402
46468
  });
46403
46469
  }
46470
+ /**
46471
+ * The personal local context file lives under `.qwen/`, not at the project
46472
+ * root where the settable root path points, so override the deletion glob.
46473
+ */
46474
+ static getLocalRootDeletionGlob({ outputRoot, fileName }) {
46475
+ return join(outputRoot, QWENCODE_DIR, fileName);
46476
+ }
46404
46477
  };
46405
46478
  //#endregion
46406
46479
  //#region src/features/rules/reasonix-rule.ts
@@ -47004,14 +47077,18 @@ var WarpRule = class WarpRule extends ToolRule {
47004
47077
  root: root ?? false
47005
47078
  });
47006
47079
  }
47007
- static getSettablePaths(_options = {}) {
47080
+ static getSettablePaths({ global = false } = {}) {
47081
+ if (global) return { root: {
47082
+ relativeDirPath: WARP_GLOBAL_RULE_DIR,
47083
+ relativeFilePath: WARP_RULE_FILE_NAME
47084
+ } };
47008
47085
  return { root: {
47009
47086
  relativeDirPath: ".",
47010
47087
  relativeFilePath: WARP_RULE_FILE_NAME
47011
47088
  } };
47012
47089
  }
47013
- static async fromFile({ outputRoot = process.cwd(), relativeFilePath: _relativeFilePath, validate = true }) {
47014
- const { root } = this.getSettablePaths();
47090
+ static async fromFile({ outputRoot = process.cwd(), relativeFilePath: _relativeFilePath, validate = true, global = false }) {
47091
+ const { root } = this.getSettablePaths({ global });
47015
47092
  const fileContent = await readFileContent(join(outputRoot, join(root.relativeDirPath, root.relativeFilePath)));
47016
47093
  return new WarpRule({
47017
47094
  outputRoot,
@@ -47022,8 +47099,8 @@ var WarpRule = class WarpRule extends ToolRule {
47022
47099
  root: true
47023
47100
  });
47024
47101
  }
47025
- static fromRulesyncRule({ outputRoot = process.cwd(), rulesyncRule, validate = true }) {
47026
- const { root } = this.getSettablePaths();
47102
+ static fromRulesyncRule({ outputRoot = process.cwd(), rulesyncRule, validate = true, global = false }) {
47103
+ const { root } = this.getSettablePaths({ global });
47027
47104
  const isRoot = rulesyncRule.getFrontmatter().root ?? false;
47028
47105
  return new WarpRule({
47029
47106
  outputRoot,
@@ -47043,8 +47120,8 @@ var WarpRule = class WarpRule extends ToolRule {
47043
47120
  error: null
47044
47121
  };
47045
47122
  }
47046
- static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
47047
- const { root } = this.getSettablePaths();
47123
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
47124
+ const { root } = this.getSettablePaths({ global });
47048
47125
  const isRoot = relativeFilePath === root.relativeFilePath && relativeDirPath === root.relativeDirPath;
47049
47126
  return new WarpRule({
47050
47127
  outputRoot,
@@ -47417,6 +47494,8 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
47417
47494
  extension: "md",
47418
47495
  supportsGlobal: true,
47419
47496
  ruleDiscoveryMode: "auto",
47497
+ localRootMode: "separate-local-file",
47498
+ localRootFileName: QWENCODE_LOCAL_RULE_FILE_NAME,
47420
47499
  additionalConventions: { subagents: { subagentClass: QwencodeSubagent } }
47421
47500
  }
47422
47501
  }],
@@ -47481,7 +47560,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
47481
47560
  class: WarpRule,
47482
47561
  meta: {
47483
47562
  extension: "md",
47484
- supportsGlobal: false,
47563
+ supportsGlobal: true,
47485
47564
  ruleDiscoveryMode: "toon",
47486
47565
  collisionPolicy: "fold"
47487
47566
  }
@@ -47815,6 +47894,14 @@ var RulesProcessor = class extends FeatureProcessor {
47815
47894
  validate: true,
47816
47895
  root: true
47817
47896
  });
47897
+ if (factory.class === QwencodeRule) return new QwencodeRule({
47898
+ outputRoot: this.outputRoot,
47899
+ relativeDirPath: QWENCODE_DIR,
47900
+ relativeFilePath: fileName,
47901
+ fileContent: body,
47902
+ validate: true,
47903
+ root: true
47904
+ });
47818
47905
  return null;
47819
47906
  }
47820
47907
  /**
@@ -49967,6 +50054,6 @@ async function importChecksCore(params) {
49967
50054
  return writtenCount;
49968
50055
  }
49969
50056
  //#endregion
49970
- export { ErrorCodes as $, RULESYNC_SKILLS_RELATIVE_DIR_PATH as $t, RulesyncMcp as A, RULESYNC_AIIGNORE_RELATIVE_FILE_PATH as At, stringifyFrontmatter as B, RULESYNC_IGNORE_RELATIVE_FILE_PATH as Bt, RulesyncSubagent as C, writeFileContent as Ct, RulesyncRule as D, ToolTargetSchema as Dt, RulesyncSkillFrontmatterSchema as E, PACKAGING_TOOL_TARGETS as Et, parseJsonc as F, RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH as Ft, ConfigFileSchema as G, RULESYNC_MCP_SCHEMA_URL as Gt, SHARED_USER_MANAGED_CONFIG_PATHS as H, RULESYNC_MCP_FILE_NAME as Ht, RulesyncCommand as I, RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH as It, ConsoleLogger as J, RULESYNC_PERMISSIONS_LEGACY_FILE_NAME as Jt, SourceEntrySchema as K, RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH as Kt, RulesyncCommandFrontmatterSchema as L, RULESYNC_HOOKS_FILE_NAME as Lt, RulesyncHooks as M, RULESYNC_COMMANDS_RELATIVE_DIR_PATH as Mt, getRulesyncSourceCandidates as N, RULESYNC_CONFIG_RELATIVE_FILE_PATH as Nt, RulesyncRuleFrontmatterSchema as O, MAX_FILE_SIZE as Ot, resolveRulesyncSourceWritePath as P, RULESYNC_CONFIG_SCHEMA_URL as Pt, CLIError as Q, RULESYNC_RULES_RELATIVE_DIR_PATH as Qt, RulesyncCheck as R, RULESYNC_HOOKS_LEGACY_FILE_NAME as Rt, getLocalSkillDirNames as S, toPosixPath as St, RulesyncSkill as T, ALL_TOOL_TARGETS_WITH_WILDCARD as Tt, SKILL_FILE_NAME as U, RULESYNC_MCP_LEGACY_FILE_NAME as Ut, loadYaml as V, RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH as Vt, ConfigResolver as W, RULESYNC_MCP_RELATIVE_FILE_PATH as Wt, fallbackLogger as X, RULESYNC_PERMISSIONS_SCHEMA_URL as Xt, JsonLogger as Y, RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH as Yt, warnOnConflictingFlags as Z, RULESYNC_RELATIVE_DIR_PATH as Zt, CLAUDECODE_SETTINGS_LOCAL_FILE_NAME as _, removeFile as _t, convertFromTool as a, directoryExists as at, CODEXCLI_BASH_RULES_FILE_NAME as b, resolvePath as bt, SubagentsProcessor as c, findFilesByGlobs as ct, IgnoreProcessor as d, isSymlink as dt, RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH as en, assertDirectoryIfExists as et, HooksProcessor as f, listDirectoryFiles as ft, CLAUDECODE_MEMORIES_DIR_NAME as g, removeDirectoryStrict as gt, CLAUDECODE_LOCAL_RULE_FILE_NAME as h, removeDirectory as ht, getProcessorRegistryEntry as i, formatError as in, createTempDirectory as it, RulesyncIgnore as j, RULESYNC_CHECKS_RELATIVE_DIR_PATH as jt, RulesyncPermissions as k, RULESYNC_AIIGNORE_FILE_NAME as kt, SkillsProcessor as l, getFileSize as lt, CLAUDECODE_DIR as m, readFileContentOrNull as mt, checkRulesyncDirExists as n, ALL_FEATURES as nn, assertWritablePathInsideRoot as nt, isPackagingToolTarget as o, ensureDir as ot, CommandsProcessor as p, readFileContent as pt, findControlCharacter as q, RULESYNC_PERMISSIONS_FILE_NAME as qt, generate as r, ALL_FEATURES_WITH_WILDCARD as rn, checkPathTraversal as rt, RulesProcessor as s, fileExists as st, importFromTool as t, RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH as tn, assertTreeContainsNoSymlinks as tt, McpProcessor as u, getHomeDirectory as ut, CLAUDECODE_SKILLS_DIR_PATH as v, removeFileStrict as vt, RulesyncSubagentFrontmatterSchema as w, ALL_TOOL_TARGETS as wt, CODEXCLI_DIR as x, runWithDirectoryRollback as xt, ChecksProcessor as y, removeTempDirectory as yt, RulesyncCheckFrontmatterSchema as z, RULESYNC_HOOKS_RELATIVE_FILE_PATH as zt };
50057
+ export { JsonLogger as $, RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH as $t, RulesyncRuleFrontmatterSchema as A, PACKAGING_TOOL_TARGETS as At, RulesyncCheck as B, RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH as Bt, CODEXCLI_DIR as C, removeTempDirectory as Ct, RulesyncSkill as D, writeFileContent as Dt, RulesyncSubagentFrontmatterSchema as E, toPosixPath as Et, getRulesyncSourceCandidates as F, RULESYNC_CHECKS_RELATIVE_DIR_PATH as Ft, SKILL_FILE_NAME as G, RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH as Gt, stringifyFrontmatter as H, RULESYNC_HOOKS_LEGACY_FILE_NAME as Ht, resolveRulesyncSourceWritePath as I, RULESYNC_COMMANDS_RELATIVE_DIR_PATH as It, ConfigFileSchema as J, RULESYNC_MCP_RELATIVE_FILE_PATH as Jt, ConfigResolver as K, RULESYNC_MCP_FILE_NAME as Kt, parseJsonc as L, RULESYNC_CONFIG_RELATIVE_FILE_PATH as Lt, RulesyncMcp as M, MAX_FILE_SIZE as Mt, RulesyncIgnore as N, RULESYNC_AIIGNORE_FILE_NAME as Nt, RulesyncSkillFrontmatterSchema as O, ALL_TOOL_TARGETS as Ot, RulesyncHooks as P, RULESYNC_AIIGNORE_RELATIVE_FILE_PATH as Pt, ConsoleLogger as Q, RULESYNC_PERMISSIONS_LEGACY_FILE_NAME as Qt, RulesyncCommand as R, RULESYNC_CONFIG_SCHEMA_URL as Rt, CODEXCLI_BASH_RULES_FILE_NAME as S, removeFileStrict as St, RulesyncSubagent as T, runWithDirectoryRollback as Tt, loadYaml as U, RULESYNC_HOOKS_RELATIVE_FILE_PATH as Ut, RulesyncCheckFrontmatterSchema as V, RULESYNC_HOOKS_FILE_NAME as Vt, SHARED_USER_MANAGED_CONFIG_PATHS as W, RULESYNC_IGNORE_RELATIVE_FILE_PATH as Wt, SourceEntrySchema as X, RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH as Xt, GITIGNORE_DESTINATION_KEY as Y, RULESYNC_MCP_SCHEMA_URL as Yt, findControlCharacter as Z, RULESYNC_PERMISSIONS_FILE_NAME as Zt, CLAUDECODE_LOCAL_RULE_FILE_NAME as _, readFileContent as _t, convertFromTool as a, RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH as an, assertTreeContainsNoSymlinks as at, CLAUDECODE_SKILLS_DIR_PATH as b, removeDirectoryStrict as bt, SubagentsProcessor as c, DEPRECATED_FEATURE_REPLACEMENTS as cn, createTempDirectory as ct, IgnoreProcessor as d, fileExists as dt, RULESYNC_PERMISSIONS_SCHEMA_URL as en, fallbackLogger as et, HooksProcessor as f, findFilesByGlobs as ft, CLAUDECODE_DIR as g, listDirectoryFiles as gt, QWENCODE_LOCAL_RULE_FILE_NAME as h, isSymlink as ht, getProcessorRegistryEntry as i, RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH as in, assertDirectoryIfExists as it, RulesyncPermissions as j, ToolTargetSchema as jt, RulesyncRule as k, ALL_TOOL_TARGETS_WITH_WILDCARD as kt, SkillsProcessor as l, formatError as ln, directoryExists as lt, QWENCODE_DIR as m, getHomeDirectory as mt, checkRulesyncDirExists as n, RULESYNC_RULES_RELATIVE_DIR_PATH as nn, CLIError as nt, isPackagingToolTarget as o, ALL_FEATURES as on, assertWritablePathInsideRoot as ot, CommandsProcessor as p, getFileSize as pt, CONFLICTING_TARGET_PAIRS as q, RULESYNC_MCP_LEGACY_FILE_NAME as qt, generate as r, RULESYNC_SKILLS_RELATIVE_DIR_PATH as rn, ErrorCodes as rt, RulesProcessor as s, ALL_FEATURES_WITH_WILDCARD as sn, checkPathTraversal as st, importFromTool as t, RULESYNC_RELATIVE_DIR_PATH as tn, warnOnConflictingFlags as tt, McpProcessor as u, ensureDir as ut, CLAUDECODE_MEMORIES_DIR_NAME as v, readFileContentOrNull as vt, getLocalSkillDirNames as w, resolvePath as wt, ChecksProcessor as x, removeFile as xt, CLAUDECODE_SETTINGS_LOCAL_FILE_NAME as y, removeDirectory as yt, RulesyncCommandFrontmatterSchema as z, RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH as zt };
49971
50058
 
49972
- //# sourceMappingURL=import-CArKOPG_.js.map
50059
+ //# sourceMappingURL=import-BwakMyyf.js.map