rulesync 16.3.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.
@@ -2356,6 +2356,7 @@ const PI_HOOK_EVENTS = [
2356
2356
  "preToolUse",
2357
2357
  "postToolUse",
2358
2358
  "preModelInvocation",
2359
+ "postModelInvocation",
2359
2360
  "beforeSubmitPrompt",
2360
2361
  "stop",
2361
2362
  "preCompact",
@@ -3062,6 +3063,7 @@ const CANONICAL_TO_PI_EVENT_NAMES = {
3062
3063
  preToolUse: "tool_call",
3063
3064
  postToolUse: "tool_result",
3064
3065
  preModelInvocation: "context",
3066
+ postModelInvocation: "message_end",
3065
3067
  beforeSubmitPrompt: "input",
3066
3068
  stop: "agent_end",
3067
3069
  preCompact: "session_before_compact",
@@ -6827,24 +6829,14 @@ const SHARED_CONFIG_OWNERSHIP = {
6827
6829
  },
6828
6830
  ".devin/config.json": {
6829
6831
  format: "json",
6830
- features: {
6831
- mcp: {
6832
- kind: "replace-owned-keys",
6833
- ownedKeys: ["mcpServers"]
6834
- },
6835
- permissions: {
6836
- kind: "replace-owned-keys",
6837
- ownedKeys: ["permissions"]
6838
- }
6839
- }
6832
+ features: { permissions: {
6833
+ kind: "replace-owned-keys",
6834
+ ownedKeys: ["permissions"]
6835
+ } }
6840
6836
  },
6841
6837
  ".config/devin/config.json": {
6842
6838
  format: "json",
6843
6839
  features: {
6844
- mcp: {
6845
- kind: "replace-owned-keys",
6846
- ownedKeys: ["mcpServers"]
6847
- },
6848
6840
  hooks: {
6849
6841
  kind: "replace-owned-keys",
6850
6842
  ownedKeys: ["hooks"]
@@ -9044,6 +9036,7 @@ const DEVIN_GLOBAL_CONFIG_DIR_PATH = join(".config", "devin");
9044
9036
  const DEVIN_GLOBAL_AGENTS_DIR_PATH = join(DEVIN_GLOBAL_CONFIG_DIR_PATH, "agents");
9045
9037
  const DEVIN_GLOBAL_SKILLS_DIR_PATH = join(DEVIN_GLOBAL_CONFIG_DIR_PATH, "skills");
9046
9038
  const DEVIN_CONFIG_FILE_NAME = "config.json";
9039
+ const DEVIN_MCP_CONFIG_FILE_NAME = "mcp_config.json";
9047
9040
  const DEVIN_HOOKS_V1_FILE_NAME = "hooks.v1.json";
9048
9041
  const DEVIN_GLOBAL_AGENTS_FILE_NAME = "AGENTS.md";
9049
9042
  const DEVIN_IGNORE_FILE_NAME = ".devinignore";
@@ -11301,6 +11294,7 @@ const QWENCODE_COMMANDS_DIR_PATH = join(QWENCODE_DIR, "commands");
11301
11294
  const QWENCODE_AGENTS_DIR_PATH = join(QWENCODE_DIR, "agents");
11302
11295
  const QWENCODE_SKILLS_DIR_PATH = join(QWENCODE_DIR, "skills");
11303
11296
  const QWENCODE_RULE_FILE_NAME = "QWEN.md";
11297
+ const QWENCODE_LOCAL_RULE_FILE_NAME = "QWEN.local.md";
11304
11298
  const QWENCODE_IGNORE_FILE_NAME = ".qwenignore";
11305
11299
  const QWENCODE_SETTINGS_FILE_NAME = "settings.json";
11306
11300
  //#endregion
@@ -12047,6 +12041,7 @@ const WARP_SKILLS_DIR_PATH = join(WARP_DIR, "skills");
12047
12041
  const WARP_LINUX_DIR = join(".config", "warp-terminal");
12048
12042
  const WARP_WIN32_DIR = join("AppData", "Local", "warp", "Warp", "config");
12049
12043
  const WARP_RULE_FILE_NAME = "AGENTS.md";
12044
+ const WARP_GLOBAL_RULE_DIR = ".agents";
12050
12045
  const WARP_MCP_FILE_NAME = ".mcp.json";
12051
12046
  const WARP_PERMISSIONS_FILE_NAME = "settings.toml";
12052
12047
  const WARP_IGNORE_FILE_NAME = ".warpindexingignore";
@@ -14782,9 +14777,9 @@ const DEVIN_CONVERTER_CONFIG = {
14782
14777
  * - Project scope: `.devin/hooks.v1.json`. This is a standalone file whose top
14783
14778
  * level IS the event map (no wrapper key).
14784
14779
  * - Global scope: `~/.config/devin/config.json` under the `"hooks"` key. This
14785
- * file is shared with the MCP (`mcpServers`) and permissions (`permissions`)
14786
- * features, so reads and writes merge into the existing JSON and the file is
14787
- * 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.
14788
14783
  *
14789
14784
  * @see https://docs.devin.ai/cli/extensibility/hooks/overview
14790
14785
  */
@@ -16396,6 +16391,13 @@ var OpencodeHooks = class OpencodeHooks extends ToolHooks {
16396
16391
  */
16397
16392
  const PI_TOOL_EVENTS = /* @__PURE__ */ new Set(["tool_call", "tool_result"]);
16398
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
+ /**
16399
16401
  * Validate a hook matcher as a regular expression and return it as a JS
16400
16402
  * string-literal (JSON.stringify quoting) safe to embed in generated code.
16401
16403
  */
@@ -16436,7 +16438,10 @@ function buildSubscriptionLines(handlerGroups) {
16436
16438
  const lines = [];
16437
16439
  for (const [piEvent, handlers] of Object.entries(handlerGroups)) {
16438
16440
  const usesToolName = PI_TOOL_EVENTS.has(piEvent) && handlers.some((h) => h.matcher);
16439
- 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;`);
16440
16445
  for (const handler of handlers) {
16441
16446
  const embeddedCommand = JSON.stringify(handler.command);
16442
16447
  if (usesToolName && handler.matcher) {
@@ -16571,6 +16576,7 @@ function canonicalDefToQwencodeHook(def) {
16571
16576
  const type = def.type ?? "command";
16572
16577
  const isHttp = type === "http";
16573
16578
  const isCommand = type === "command";
16579
+ const isPrompt = type === "prompt";
16574
16580
  return {
16575
16581
  type,
16576
16582
  ...compact({
@@ -16580,6 +16586,8 @@ function canonicalDefToQwencodeHook(def) {
16580
16586
  name: def.name,
16581
16587
  description: def.description,
16582
16588
  statusMessage: def.statusMessage,
16589
+ prompt: isPrompt ? def.prompt : void 0,
16590
+ model: isPrompt ? def.model : void 0,
16583
16591
  async: isCommand ? def.async : void 0,
16584
16592
  env: isCommand ? def.env : void 0,
16585
16593
  shell: isCommand ? def.shell : void 0,
@@ -16598,7 +16606,7 @@ function canonicalDefToQwencodeHook(def) {
16598
16606
  * is needed here — commands are passed through verbatim and any such variable
16599
16607
  * reference stays intact for Qwen Code to expand.
16600
16608
  */
16601
- function canonicalToQwencodeHooks(config) {
16609
+ function canonicalToQwencodeHooks(config, logger) {
16602
16610
  const qwencodeSupported = new Set(QWENCODE_HOOK_EVENTS);
16603
16611
  const sharedHooks = {};
16604
16612
  for (const [event, defs] of Object.entries(config.hooks)) if (qwencodeSupported.has(event)) sharedHooks[event] = defs;
@@ -16618,6 +16626,7 @@ function canonicalToQwencodeHooks(config) {
16618
16626
  const byMatcher = /* @__PURE__ */ new Map();
16619
16627
  for (const def of definitions) {
16620
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.`);
16621
16630
  const key = def.matcher ?? "";
16622
16631
  const list = byMatcher.get(key);
16623
16632
  if (list) list.push(def);
@@ -16656,7 +16665,9 @@ const QwencodeHookEntrySchema = z.looseObject({
16656
16665
  shell: z.optional(z.string()),
16657
16666
  headers: z.optional(z.record(z.string(), z.string())),
16658
16667
  allowedEnvVars: z.optional(z.array(z.string())),
16659
- once: z.optional(z.boolean())
16668
+ once: z.optional(z.boolean()),
16669
+ prompt: z.optional(z.string()),
16670
+ model: z.optional(z.string())
16660
16671
  });
16661
16672
  /**
16662
16673
  * A matcher group entry in a Qwen Code event array.
@@ -16671,37 +16682,43 @@ const QwencodeMatcherEntrySchema = z.looseObject({
16671
16682
  /**
16672
16683
  * Convert a single parsed Qwen Code matcher group into canonical hook definitions.
16673
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
+ }
16674
16714
  function qwencodeMatcherEntryToCanonical(entry) {
16675
- const defs = [];
16676
- const hooks = entry.hooks ?? [];
16677
16715
  const sequential = entry.sequential === true;
16678
16716
  const matcher = entry.matcher !== void 0 && entry.matcher !== null && entry.matcher !== "" ? entry.matcher : void 0;
16679
- for (const h of hooks) {
16680
- const hookType = h.type === "command" || h.type === "prompt" || h.type === "http" || h.type === "function" ? h.type : "command";
16681
- const isHttp = hookType === "http";
16682
- const isCommand = hookType === "command";
16683
- const shell = h.shell === "bash" || h.shell === "powershell" ? h.shell : void 0;
16684
- defs.push({
16685
- type: hookType,
16686
- ...compact({
16687
- command: h.command,
16688
- url: h.url,
16689
- timeout: h.timeout,
16690
- name: h.name,
16691
- description: h.description,
16692
- statusMessage: h.statusMessage,
16693
- async: isCommand ? h.async : void 0,
16694
- env: isCommand ? h.env : void 0,
16695
- shell: isCommand ? shell : void 0,
16696
- headers: isHttp ? h.headers : void 0,
16697
- allowedEnvVars: isHttp ? h.allowedEnvVars : void 0,
16698
- once: isHttp ? h.once : void 0,
16699
- sequential: sequential ? true : void 0,
16700
- matcher
16701
- })
16702
- });
16703
- }
16704
- return defs;
16717
+ return (entry.hooks ?? []).map((hook) => qwencodeHookEntryToCanonical({
16718
+ hook,
16719
+ sequential,
16720
+ matcher
16721
+ }));
16705
16722
  }
16706
16723
  /**
16707
16724
  * Extract hooks from Qwen Code settings.json into canonical format.
@@ -16749,12 +16766,12 @@ var QwencodeHooks = class QwencodeHooks extends ToolHooks {
16749
16766
  validate
16750
16767
  });
16751
16768
  }
16752
- static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false }) {
16769
+ static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false, logger }) {
16753
16770
  const paths = QwencodeHooks.getSettablePaths({ global });
16754
16771
  const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
16755
16772
  const existingContent = await readFileContentOrNull(filePath) ?? JSON.stringify({}, null, 2);
16756
16773
  const config = rulesyncHooks.getJson();
16757
- const patch = { hooks: canonicalToQwencodeHooks(config) };
16774
+ const patch = { hooks: canonicalToQwencodeHooks(config, logger) };
16758
16775
  const disableAllHooks = config.qwencode?.disableAllHooks;
16759
16776
  if (typeof disableAllHooks === "boolean") patch.disableAllHooks = disableAllHooks;
16760
16777
  const fileContent = applySharedConfigPatch({
@@ -20780,18 +20797,24 @@ var DeepagentsMcp = class DeepagentsMcp extends ToolMcp {
20780
20797
  /**
20781
20798
  * MCP generator for Devin Local (native `.devin/` configuration).
20782
20799
  *
20783
- * Devin reads MCP servers from the `mcpServers` key of its native config file:
20784
- * - Project scope: `.devin/config.json`
20785
- * - 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`
20786
20805
  *
20787
- * The config file is shared with the permissions feature (`permissions` key)
20788
- * and, in global mode, the hooks feature (`hooks` key), so reads and writes
20789
- * merge into the existing JSON rather than overwriting it, and the file is
20790
- * never deleted. Each server is a stdio entry ({ command, args, env }) or a
20791
- * remote entry ({ serverUrl | url, headers }), and may carry an optional
20792
- * `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.
20793
20812
  *
20794
- * @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
20795
20818
  */
20796
20819
  var DevinMcp = class DevinMcp extends ToolMcp {
20797
20820
  json;
@@ -20809,31 +20832,27 @@ var DevinMcp = class DevinMcp extends ToolMcp {
20809
20832
  throw new Error(`Failed to parse Devin MCP config at ${join(relativeDirPath, relativeFilePath)}: ${formatError(error)}`, { cause: error });
20810
20833
  }
20811
20834
  }
20812
- /**
20813
- * config.json may carry the permissions/hooks features' keys, so it is never
20814
- * deleted; only the managed `mcpServers` key is rewritten.
20815
- */
20816
- isDeletable() {
20817
- return false;
20818
- }
20819
20835
  static getSettablePaths({ global = false } = {}) {
20820
20836
  if (global) return {
20821
20837
  relativeDirPath: DEVIN_GLOBAL_CONFIG_DIR_PATH,
20822
- relativeFilePath: DEVIN_CONFIG_FILE_NAME
20838
+ relativeFilePath: DEVIN_MCP_CONFIG_FILE_NAME
20823
20839
  };
20824
20840
  return {
20825
20841
  relativeDirPath: DEVIN_DIR,
20826
- relativeFilePath: DEVIN_CONFIG_FILE_NAME
20842
+ relativeFilePath: DEVIN_MCP_CONFIG_FILE_NAME
20827
20843
  };
20828
20844
  }
20829
20845
  static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
20830
20846
  const paths = this.getSettablePaths({ global });
20831
- const fileContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "{\"mcpServers\":{}}";
20832
- const json = this.parseJsonOrThrow(fileContent, paths.relativeDirPath, paths.relativeFilePath);
20833
- const newJson = {
20834
- ...json,
20835
- mcpServers: json.mcpServers ?? {}
20836
- };
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 ?? {} };
20837
20856
  return new DevinMcp({
20838
20857
  outputRoot,
20839
20858
  relativeDirPath: paths.relativeDirPath,
@@ -20843,21 +20862,21 @@ var DevinMcp = class DevinMcp extends ToolMcp {
20843
20862
  global
20844
20863
  });
20845
20864
  }
20846
- static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false }) {
20865
+ static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false, logger }) {
20847
20866
  const paths = this.getSettablePaths({ global });
20848
- const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
20849
- 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
+ }
20850
20875
  return new DevinMcp({
20851
20876
  outputRoot,
20852
20877
  relativeDirPath: paths.relativeDirPath,
20853
20878
  relativeFilePath: paths.relativeFilePath,
20854
- fileContent: applySharedConfigPatch({
20855
- fileKey: sharedConfigFileKey(paths),
20856
- feature: "mcp",
20857
- existingContent,
20858
- patch: { mcpServers: rulesyncMcp.getMcpServers() },
20859
- filePath
20860
- }),
20879
+ fileContent: JSON.stringify({ mcpServers: rulesyncMcp.getMcpServers() }, null, 2),
20861
20880
  validate,
20862
20881
  global
20863
20882
  });
@@ -27575,9 +27594,10 @@ function buildDevinPermissionEntry(scope, pattern) {
27575
27594
  * - Project scope: `.devin/config.json`
27576
27595
  * - Global scope: `~/.config/devin/config.json`
27577
27596
  *
27578
- * The config file is shared with the MCP (`mcpServers`) and, in global mode, the
27579
- * hooks (`hooks`) features, so reads and writes merge into the existing JSON and
27580
- * 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.
27581
27601
  *
27582
27602
  * @see https://docs.devin.ai/cli/reference/permissions
27583
27603
  */
@@ -31451,6 +31471,10 @@ function ensurePermission(permission, category) {
31451
31471
  const WARP_GLOBAL_ONLY_MESSAGE = "Warp permissions are global-only; use --global to sync Warp's settings.toml";
31452
31472
  const ALLOWLIST_KEY = "agent_mode_command_execution_allowlist";
31453
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";
31454
31478
  const WARP_OVERRIDE_KEYS = [
31455
31479
  "agent_mode_coding_permissions",
31456
31480
  "agent_mode_coding_file_read_allowlist",
@@ -31478,11 +31502,21 @@ function warpSettingsDir() {
31478
31502
  /**
31479
31503
  * Permissions adapter for Warp.
31480
31504
  *
31481
- * Warp gates **shell command** execution through two regex arrays under the
31482
- * `[agents.profiles]` table of the global user `settings.toml`:
31483
- * - `agent_mode_command_execution_allowlist` commands that auto-execute.
31484
- * - `agent_mode_command_execution_denylist` — commands that always require
31485
- * 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.
31486
31520
  *
31487
31521
  * This surface is **global only** — there is no project-scoped Warp permissions
31488
31522
  * file. rulesync's canonical `permission.bash` patterns map directly (`allow` →
@@ -31561,6 +31595,18 @@ var WarpPermissions = class WarpPermissions extends ToolPermissions {
31561
31595
  if (mergedDeny.length > 0) profiles[DENYLIST_KEY] = mergedDeny;
31562
31596
  else delete profiles[DENYLIST_KEY];
31563
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
+ }
31564
31610
  settings.agents = agents;
31565
31611
  return new WarpPermissions({
31566
31612
  outputRoot,
@@ -31580,9 +31626,11 @@ var WarpPermissions = class WarpPermissions extends ToolPermissions {
31580
31626
  }
31581
31627
  const agents = isRecord(settings.agents) ? settings.agents : {};
31582
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;
31583
31631
  const config = convertWarpToRulesyncPermissions({
31584
- allow: isStringArray$1(profiles[ALLOWLIST_KEY]) ? profiles[ALLOWLIST_KEY] : [],
31585
- 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] : []
31586
31634
  });
31587
31635
  const warpOverride = {};
31588
31636
  for (const key of WARP_OVERRIDE_KEYS) if (profiles[key] !== void 0) warpOverride[key] = profiles[key];
@@ -37332,7 +37380,8 @@ const toolSkillFactories = /* @__PURE__ */ new Map([
37332
37380
  meta: {
37333
37381
  supportsProject: true,
37334
37382
  supportsSimulated: false,
37335
- supportsGlobal: true
37383
+ supportsGlobal: true,
37384
+ lenientImport: true
37336
37385
  }
37337
37386
  }],
37338
37387
  ["aiassistant", {
@@ -37769,7 +37818,7 @@ var SkillsProcessor = class extends DirFeatureProcessor {
37769
37818
  global: this.global
37770
37819
  });
37771
37820
  } catch (error) {
37772
- if (!isConfiguredRoot) throw error;
37821
+ if (!isConfiguredRoot && !factory.meta.lenientImport) throw error;
37773
37822
  this.logger.warn(`Skipping ${join(relativeDirPath, dirName)}: ${formatError(error)}`);
37774
37823
  return null;
37775
37824
  }
@@ -46418,6 +46467,13 @@ var QwencodeRule = class QwencodeRule extends ToolRule {
46418
46467
  toolTarget: "qwencode"
46419
46468
  });
46420
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
+ }
46421
46477
  };
46422
46478
  //#endregion
46423
46479
  //#region src/features/rules/reasonix-rule.ts
@@ -47021,14 +47077,18 @@ var WarpRule = class WarpRule extends ToolRule {
47021
47077
  root: root ?? false
47022
47078
  });
47023
47079
  }
47024
- 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
+ } };
47025
47085
  return { root: {
47026
47086
  relativeDirPath: ".",
47027
47087
  relativeFilePath: WARP_RULE_FILE_NAME
47028
47088
  } };
47029
47089
  }
47030
- static async fromFile({ outputRoot = process.cwd(), relativeFilePath: _relativeFilePath, validate = true }) {
47031
- const { root } = this.getSettablePaths();
47090
+ static async fromFile({ outputRoot = process.cwd(), relativeFilePath: _relativeFilePath, validate = true, global = false }) {
47091
+ const { root } = this.getSettablePaths({ global });
47032
47092
  const fileContent = await readFileContent(join(outputRoot, join(root.relativeDirPath, root.relativeFilePath)));
47033
47093
  return new WarpRule({
47034
47094
  outputRoot,
@@ -47039,8 +47099,8 @@ var WarpRule = class WarpRule extends ToolRule {
47039
47099
  root: true
47040
47100
  });
47041
47101
  }
47042
- static fromRulesyncRule({ outputRoot = process.cwd(), rulesyncRule, validate = true }) {
47043
- const { root } = this.getSettablePaths();
47102
+ static fromRulesyncRule({ outputRoot = process.cwd(), rulesyncRule, validate = true, global = false }) {
47103
+ const { root } = this.getSettablePaths({ global });
47044
47104
  const isRoot = rulesyncRule.getFrontmatter().root ?? false;
47045
47105
  return new WarpRule({
47046
47106
  outputRoot,
@@ -47060,8 +47120,8 @@ var WarpRule = class WarpRule extends ToolRule {
47060
47120
  error: null
47061
47121
  };
47062
47122
  }
47063
- static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
47064
- const { root } = this.getSettablePaths();
47123
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
47124
+ const { root } = this.getSettablePaths({ global });
47065
47125
  const isRoot = relativeFilePath === root.relativeFilePath && relativeDirPath === root.relativeDirPath;
47066
47126
  return new WarpRule({
47067
47127
  outputRoot,
@@ -47434,6 +47494,8 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
47434
47494
  extension: "md",
47435
47495
  supportsGlobal: true,
47436
47496
  ruleDiscoveryMode: "auto",
47497
+ localRootMode: "separate-local-file",
47498
+ localRootFileName: QWENCODE_LOCAL_RULE_FILE_NAME,
47437
47499
  additionalConventions: { subagents: { subagentClass: QwencodeSubagent } }
47438
47500
  }
47439
47501
  }],
@@ -47498,7 +47560,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
47498
47560
  class: WarpRule,
47499
47561
  meta: {
47500
47562
  extension: "md",
47501
- supportsGlobal: false,
47563
+ supportsGlobal: true,
47502
47564
  ruleDiscoveryMode: "toon",
47503
47565
  collisionPolicy: "fold"
47504
47566
  }
@@ -47832,6 +47894,14 @@ var RulesProcessor = class extends FeatureProcessor {
47832
47894
  validate: true,
47833
47895
  root: true
47834
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
+ });
47835
47905
  return null;
47836
47906
  }
47837
47907
  /**
@@ -49984,6 +50054,6 @@ async function importChecksCore(params) {
49984
50054
  return writtenCount;
49985
50055
  }
49986
50056
  //#endregion
49987
- export { warnOnConflictingFlags as $, RULESYNC_RELATIVE_DIR_PATH as $t, RulesyncMcp as A, MAX_FILE_SIZE as At, stringifyFrontmatter as B, RULESYNC_HOOKS_LEGACY_FILE_NAME as Bt, RulesyncSubagent as C, runWithDirectoryRollback as Ct, RulesyncRule as D, ALL_TOOL_TARGETS_WITH_WILDCARD as Dt, RulesyncSkillFrontmatterSchema as E, ALL_TOOL_TARGETS as Et, parseJsonc as F, RULESYNC_CONFIG_RELATIVE_FILE_PATH as Ft, CONFLICTING_TARGET_PAIRS as G, RULESYNC_MCP_LEGACY_FILE_NAME as Gt, SHARED_USER_MANAGED_CONFIG_PATHS as H, RULESYNC_IGNORE_RELATIVE_FILE_PATH as Ht, RulesyncCommand as I, RULESYNC_CONFIG_SCHEMA_URL as It, SourceEntrySchema as J, RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH as Jt, ConfigFileSchema as K, RULESYNC_MCP_RELATIVE_FILE_PATH as Kt, RulesyncCommandFrontmatterSchema as L, RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH as Lt, RulesyncHooks as M, RULESYNC_AIIGNORE_RELATIVE_FILE_PATH as Mt, getRulesyncSourceCandidates as N, RULESYNC_CHECKS_RELATIVE_DIR_PATH as Nt, RulesyncRuleFrontmatterSchema as O, PACKAGING_TOOL_TARGETS as Ot, resolveRulesyncSourceWritePath as P, RULESYNC_COMMANDS_RELATIVE_DIR_PATH as Pt, fallbackLogger as Q, RULESYNC_PERMISSIONS_SCHEMA_URL as Qt, RulesyncCheck as R, RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH as Rt, getLocalSkillDirNames as S, resolvePath as St, RulesyncSkill as T, writeFileContent as Tt, SKILL_FILE_NAME as U, RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH as Ut, loadYaml as V, RULESYNC_HOOKS_RELATIVE_FILE_PATH as Vt, ConfigResolver as W, RULESYNC_MCP_FILE_NAME as Wt, ConsoleLogger as X, RULESYNC_PERMISSIONS_LEGACY_FILE_NAME as Xt, findControlCharacter as Y, RULESYNC_PERMISSIONS_FILE_NAME as Yt, JsonLogger as Z, RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH as Zt, CLAUDECODE_SETTINGS_LOCAL_FILE_NAME as _, removeDirectory as _t, convertFromTool as a, ALL_FEATURES_WITH_WILDCARD as an, checkPathTraversal as at, CODEXCLI_BASH_RULES_FILE_NAME as b, removeFileStrict as bt, SubagentsProcessor as c, ensureDir as ct, IgnoreProcessor as d, getFileSize as dt, RULESYNC_RULES_RELATIVE_DIR_PATH as en, CLIError as et, HooksProcessor as f, getHomeDirectory as ft, CLAUDECODE_MEMORIES_DIR_NAME as g, readFileContentOrNull as gt, CLAUDECODE_LOCAL_RULE_FILE_NAME as h, readFileContent as ht, getProcessorRegistryEntry as i, ALL_FEATURES as in, assertWritablePathInsideRoot as it, RulesyncIgnore as j, RULESYNC_AIIGNORE_FILE_NAME as jt, RulesyncPermissions as k, ToolTargetSchema as kt, SkillsProcessor as l, fileExists as lt, CLAUDECODE_DIR as m, listDirectoryFiles as mt, checkRulesyncDirExists as n, RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH as nn, assertDirectoryIfExists as nt, isPackagingToolTarget as o, DEPRECATED_FEATURE_REPLACEMENTS as on, createTempDirectory as ot, CommandsProcessor as p, isSymlink as pt, GITIGNORE_DESTINATION_KEY as q, RULESYNC_MCP_SCHEMA_URL as qt, generate as r, RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH as rn, assertTreeContainsNoSymlinks as rt, RulesProcessor as s, formatError as sn, directoryExists as st, importFromTool as t, RULESYNC_SKILLS_RELATIVE_DIR_PATH as tn, ErrorCodes as tt, McpProcessor as u, findFilesByGlobs as ut, CLAUDECODE_SKILLS_DIR_PATH as v, removeDirectoryStrict as vt, RulesyncSubagentFrontmatterSchema as w, toPosixPath as wt, CODEXCLI_DIR as x, removeTempDirectory as xt, ChecksProcessor as y, removeFile as yt, RulesyncCheckFrontmatterSchema as z, RULESYNC_HOOKS_FILE_NAME 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 };
49988
50058
 
49989
- //# sourceMappingURL=import-Bj5HxY9p.js.map
50059
+ //# sourceMappingURL=import-BwakMyyf.js.map