rulesync 43.3.0 → 43.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.
@@ -5613,11 +5613,28 @@ const POOL_TO_CANONICAL_EVENT_NAMES = invertEventNames(CANONICAL_TO_POOL_EVENT_N
5613
5613
  /**
5614
5614
  * Hook events supported by Crush.
5615
5615
  *
5616
- * Crush's `hooks` config block currently fires a single event, `PreToolUse`
5617
- * (Claude Code-compatible payload; `matcher` is a regex on the tool name).
5618
- * @see https://github.com/charmbracelet/crush/blob/main/docs/hooks/README.md
5616
+ * Crush v0.99.0 fires 13 events under its `hooks` config block (Claude
5617
+ * Code-compatible payload; `matcher` is a regex on the tool name and is only
5618
+ * accepted on tool events). Twelve of them have a canonical counterpart;
5619
+ * `PermissionResult` has none and is authored under the `crush.hooks`
5620
+ * override.
5621
+ * @see https://github.com/charmbracelet/crush/blob/v0.99.0/docs/hooks/README.md
5622
+ * @see https://github.com/charmbracelet/crush/blob/v0.99.0/internal/hooks/events.go
5619
5623
  */
5620
- const CRUSH_HOOK_EVENTS = ["preToolUse"];
5624
+ const CRUSH_HOOK_EVENTS = [
5625
+ "preToolUse",
5626
+ "postToolUse",
5627
+ "postToolUseFailure",
5628
+ "permissionRequest",
5629
+ "sessionStart",
5630
+ "sessionEnd",
5631
+ "beforeSubmitPrompt",
5632
+ "stop",
5633
+ "stopFailure",
5634
+ "stopCancelled",
5635
+ "preCompact",
5636
+ "postCompact"
5637
+ ];
5621
5638
  /**
5622
5639
  * Hook events supported by JetBrains Junie CLI.
5623
5640
  *
@@ -5882,9 +5899,10 @@ const ZCODE_TO_CANONICAL_EVENT_NAMES = invertEventNames(CANONICAL_TO_ZCODE_EVENT
5882
5899
  * Bob reads `hooks` from `.bob/settings.json` (project) and
5883
5900
  * `~/.bob/settings/settings.json` (user) in the Claude-Code shape and fires
5884
5901
  * seven events: SessionStart, UserPromptSubmit, PreToolUse, PostToolUse,
5885
- * PreCompact, PostCompact (both added by Bob Shell 2.0.3) and Stop. Handlers
5886
- * are `command` or `https` hooks, and `matcher` (a regex over the tool name)
5887
- * applies to the two tool events.
5902
+ * PreCompact, PostCompact and Stop (Bob IDE and Bob Shell list the same
5903
+ * seven). Handlers are `command` or `http` hooks. `matcher` is a regex matched
5904
+ * against the tool name on PreToolUse / PostToolUse, `source` on SessionStart
5905
+ * and `trigger` on PreCompact / PostCompact; the other events ignore it.
5888
5906
  *
5889
5907
  * @see https://bob.ibm.com/docs/shell/configuration/lifecycle-hooks
5890
5908
  * @see https://bob.ibm.com/docs/ide/configuration/lifecycle-hooks
@@ -7066,10 +7084,38 @@ const CANONICAL_TO_CODEWHALE_EVENT_NAMES = {
7066
7084
  const CODEWHALE_TO_CANONICAL_EVENT_NAMES = invertEventNames(CANONICAL_TO_CODEWHALE_EVENT_NAMES);
7067
7085
  /**
7068
7086
  * Canonical -> Crush event names. Crush spells its events the Claude Code
7069
- * way (`PreToolUse`).
7070
- * @see https://github.com/charmbracelet/crush/blob/main/docs/hooks/README.md
7087
+ * way (`PreToolUse`), but names the turn events `TurnStart` (Claude Code's
7088
+ * `UserPromptSubmit`) and `TurnEnd` (Claude Code's `Stop`), and the
7089
+ * user-cancelled turn `Interrupt`.
7090
+ * @see https://github.com/charmbracelet/crush/blob/v0.99.0/docs/hooks/README.md
7091
+ */
7092
+ const CANONICAL_TO_CRUSH_EVENT_NAMES = {
7093
+ preToolUse: "PreToolUse",
7094
+ postToolUse: "PostToolUse",
7095
+ postToolUseFailure: "PostToolUseFailure",
7096
+ permissionRequest: "PermissionRequest",
7097
+ sessionStart: "SessionStart",
7098
+ sessionEnd: "SessionEnd",
7099
+ beforeSubmitPrompt: "TurnStart",
7100
+ stop: "TurnEnd",
7101
+ stopFailure: "StopFailure",
7102
+ stopCancelled: "Interrupt",
7103
+ preCompact: "PreCompact",
7104
+ postCompact: "PostCompact"
7105
+ };
7106
+ /**
7107
+ * Crush events that carry a tool call, the only ones whose hooks may set a
7108
+ * `matcher` (Crush's `EventHasTool`). A matcher on any other event makes
7109
+ * Crush reject the whole config at load time.
7110
+ * @see https://github.com/charmbracelet/crush/blob/v0.99.0/internal/config/hooks.go
7071
7111
  */
7072
- const CANONICAL_TO_CRUSH_EVENT_NAMES = { preToolUse: "PreToolUse" };
7112
+ const CRUSH_TOOL_EVENTS = [
7113
+ "PreToolUse",
7114
+ "PostToolUse",
7115
+ "PostToolUseFailure",
7116
+ "PermissionRequest",
7117
+ "PermissionResult"
7118
+ ];
7073
7119
  const CRUSH_TO_CANONICAL_EVENT_NAMES = invertEventNames(CANONICAL_TO_CRUSH_EVENT_NAMES);
7074
7120
  /**
7075
7121
  * Map canonical camelCase event names to Qwen Code PascalCase.
@@ -7613,14 +7659,18 @@ const toolScopedMcpSchema = z.looseObject({ mcpServers: z.optional(z.record(z.st
7613
7659
  * server opts out. Per-server `startupTimeoutMs` / `toolTimeoutMs` / `deferred`
7614
7660
  * in `mcpServers` still win. `defaultDeferred` only takes effect when Kimi's
7615
7661
  * experimental `tool-select` flag is on, and is captured when a server connects
7616
- * (MoonshotAI/kimi-code#4210).
7662
+ * (MoonshotAI/kimi-code#4210). `reconnectInitialDelayMs` / `reconnectMaxDelayMs`
7663
+ * (Kimi Code 2.4.0) set the first delay and the cap of the automatic reconnect
7664
+ * backoff for workspace-level servers (MoonshotAI/kimi-code#4283).
7617
7665
  *
7618
7666
  * @see https://moonshotai.github.io/kimi-code/en/configuration/config-files.html#mcp
7619
7667
  */
7620
7668
  const kimiCodeScopedMcpSchema = z.extend(toolScopedMcpSchema, {
7621
7669
  startupTimeoutMs: z.optional(z.number()),
7622
7670
  toolTimeoutMs: z.optional(z.number()),
7623
- defaultDeferred: z.optional(z.boolean())
7671
+ defaultDeferred: z.optional(z.boolean()),
7672
+ reconnectInitialDelayMs: z.optional(z.int().check(z.nonnegative())),
7673
+ reconnectMaxDelayMs: z.optional(z.int().check(z.nonnegative()))
7624
7674
  });
7625
7675
  /**
7626
7676
  * Qwen Code's tool-scoped block also carries the server-level allow/deny lists
@@ -11446,6 +11496,7 @@ const piSkillSectionSchema = z.looseObject({
11446
11496
  });
11447
11497
  const antigravitySkillSectionSchema = z.looseObject({
11448
11498
  "disable-slash-command": z.optional(z.boolean()),
11499
+ "disable-model-invocation": z.optional(z.boolean()),
11449
11500
  metadata: z.optional(z.looseObject({}))
11450
11501
  });
11451
11502
  const RulesyncSkillFrontmatterSchema = z.looseObject({
@@ -16205,8 +16256,9 @@ var ToolSkill = class extends AiDir {
16205
16256
  * {@link AntigravitySharedSkill.getToolTarget}.
16206
16257
  *
16207
16258
  * Frontmatter beyond `name`/`description` (e.g. `disable-slash-command`,
16208
- * `metadata`) comes from the rulesync section named after that target, with the
16209
- * root-level `user-invocable` / `metadata` as defaults. On the shared project
16259
+ * `disable-model-invocation`, `metadata`) comes from the rulesync section named
16260
+ * after that target, with the root-level `user-invocable` /
16261
+ * `disable-model-invocation` / `metadata` as defaults. On the shared project
16210
16262
  * `.agents/skills/` tree both targets merge the `antigravity-ide` and
16211
16263
  * `antigravity-cli` sections so their output never diverges.
16212
16264
  */
@@ -16279,6 +16331,8 @@ var AntigravitySharedSkill = class extends ToolSkill {
16279
16331
  const { name: _name, description: _description, ...sectionFields } = section;
16280
16332
  const sectionDisableSlashCommand = sectionFields["disable-slash-command"];
16281
16333
  const disableSlashCommand = typeof sectionDisableSlashCommand === "boolean" ? sectionDisableSlashCommand : rulesyncFrontmatter["user-invocable"] === false ? true : void 0;
16334
+ const sectionDisableModelInvocation = sectionFields["disable-model-invocation"];
16335
+ const disableModelInvocation = typeof sectionDisableModelInvocation === "boolean" ? sectionDisableModelInvocation : rulesyncFrontmatter["disable-model-invocation"];
16282
16336
  const metadata = resolveMetadata({
16283
16337
  rootFrontmatter: rulesyncFrontmatter,
16284
16338
  section: sectionFields
@@ -16288,6 +16342,7 @@ var AntigravitySharedSkill = class extends ToolSkill {
16288
16342
  name: rulesyncFrontmatter.name,
16289
16343
  description: rulesyncFrontmatter.description,
16290
16344
  ...disableSlashCommand !== void 0 && { "disable-slash-command": disableSlashCommand },
16345
+ ...disableModelInvocation !== void 0 && { "disable-model-invocation": disableModelInvocation },
16291
16346
  ...metadata !== void 0 && { metadata }
16292
16347
  };
16293
16348
  return new this({
@@ -31716,16 +31771,17 @@ const VibeSkillFrontmatterSchema = z.looseObject({
31716
31771
  "allowed-tools": z.optional(z.union([z.string(), z.array(z.string())]))
31717
31772
  });
31718
31773
  /**
31719
- * Skill names Vibe's built-in skills occupy. `SkillManager` treats them as
31720
- * reserved: a project or user skill whose frontmatter `name` matches one is
31721
- * skipped at load time with only a debug log ("Skipping skill '<name>' ...
31722
- * because builtin skill names are reserved"), so a generated skill by that
31723
- * name is silently never offered. That is the legacy backend, still the
31724
- * default; the unified harness constructs the manager with
31725
- * `include_builtins=False` because its built-ins arrive as skills of the
31726
- * shipped `vibe` plugin under namespaced names, so it reserves nothing.
31727
- * @see https://github.com/mistralai/mistral-vibe/blob/v2.25.3/vibe/core/skills/builtins/__init__.py
31728
- * @see https://github.com/mistralai/mistral-vibe/blob/v2.25.3/vibe/core/skills/manager.py
31774
+ * Skill names Vibe's built-in skills occupy. Before v3.0.4, `SkillManager`
31775
+ * treats them as reserved: a project or user skill whose frontmatter `name`
31776
+ * matches one is skipped at load time with only a debug log ("Skipping skill
31777
+ * '<name>' ... because builtin skill names are reserved"), so a generated
31778
+ * skill by that name is silently never offered. v3.0.4 removed
31779
+ * `_reserved_builtins` and that skip ("A user skill named vibe or
31780
+ * skill-creator is no longer silently skipped"), so the warning below is
31781
+ * scoped to older Vibe releases.
31782
+ * @see https://github.com/mistralai/mistral-vibe/blob/v3.0.3/vibe/core/skills/manager.py
31783
+ * @see https://github.com/mistralai/mistral-vibe/blob/v3.0.4/vibe/core/skills/manager.py
31784
+ * @see https://github.com/mistralai/mistral-vibe/blob/v3.0.4/CHANGELOG.md
31729
31785
  */
31730
31786
  const VIBE_RESERVED_SKILL_NAMES = /* @__PURE__ */ new Set(["vibe", "skill-creator"]);
31731
31787
  /**
@@ -31768,11 +31824,12 @@ function buildVibeFrontmatter(rulesyncFrontmatter) {
31768
31824
  /**
31769
31825
  * The skill is still generated under the reserved name — the name is the
31770
31826
  * user's to change, and a rename here would make it diverge from the other
31771
- * targets — but Vibe would drop it without a visible word, so say so now.
31827
+ * targets — but Vibe before v3.0.4 would drop it without a visible word, so
31828
+ * say so now.
31772
31829
  */
31773
31830
  function warnAboutReservedSkillName({ name, logger }) {
31774
31831
  if (!VIBE_RESERVED_SKILL_NAMES.has(name)) return;
31775
- logger?.warn(`Vibe skills: the skill name ${quoteValueForWarning(name)} is reserved for a Vibe built-in skill, so Vibe skips a project or user skill by that name when it loads skills. Rename the skill for it to be available in Vibe.`);
31832
+ logger?.warn(`Vibe skills: the skill name ${quoteValueForWarning(name)} matches a Vibe built-in skill. Vibe before v3.0.4 skips a project or user skill by that name when it loads skills; upgrade Vibe to v3.0.4 or later, or rename the skill, for it to be available.`);
31776
31833
  }
31777
31834
  var VibeSkill = class VibeSkill extends ToolSkill {
31778
31835
  constructor({ outputRoot = process.cwd(), relativeDirPath = VibeSkill.getSettablePaths().relativeDirPath, dirName, frontmatter, body, otherFiles = [], validate = true, global = false }) {
@@ -42740,20 +42797,35 @@ const OPENCODE_KNOWN_SERVER_KEYS = /* @__PURE__ */ new Set([
42740
42797
  "disabledTools"
42741
42798
  ]);
42742
42799
  function convertFromOpencodeFormat(opencodeMcp, tools, remoteType) {
42800
+ const serverNames = Object.keys(opencodeMcp);
42743
42801
  return {
42744
- ...orphanMcpToolFiltersToRulesync(opencodeMcp, tools),
42802
+ ...orphanMcpToolFiltersToRulesync(opencodeMcp, Object.fromEntries(Object.entries(tools ?? {}).filter(([key]) => !findOpencodeToolOwner({
42803
+ key,
42804
+ serverNames
42805
+ })))),
42745
42806
  ...convertOpencodeServers(opencodeMcp, tools, remoteType)
42746
42807
  };
42747
42808
  }
42809
+ /** Recognize both OpenCode's sanitized names and keys older rulesync versions wrote. */
42810
+ function findOpencodeToolOwner({ key, serverNames }) {
42811
+ return findOpencodeMcpToolKeyOwner({
42812
+ key,
42813
+ serverNames,
42814
+ builtinKeys: /* @__PURE__ */ new Set(),
42815
+ acceptRawPrefix: true
42816
+ });
42817
+ }
42748
42818
  /** Split the shared top-level `tools` map into this server's own two lists. */
42749
- function splitOpencodeServerTools(serverName, tools) {
42819
+ function splitOpencodeServerTools(serverName, serverNames, tools) {
42750
42820
  const enabledTools = [];
42751
42821
  const disabledTools = [];
42752
- const prefix = `${serverName}_`;
42753
42822
  for (const [toolName, enabled] of Object.entries(tools ?? {})) {
42754
- if (!toolName.startsWith(prefix)) continue;
42755
- const toolSuffix = toolName.slice(prefix.length);
42756
- (enabled ? enabledTools : disabledTools).push(toolSuffix);
42823
+ const owner = findOpencodeToolOwner({
42824
+ key: toolName,
42825
+ serverNames
42826
+ });
42827
+ if (owner?.serverName !== serverName) continue;
42828
+ (enabled ? enabledTools : disabledTools).push(owner.toolName);
42757
42829
  }
42758
42830
  return {
42759
42831
  enabledTools,
@@ -42763,7 +42835,7 @@ function splitOpencodeServerTools(serverName, tools) {
42763
42835
  function convertOpencodeServers(opencodeMcp, tools, remoteType) {
42764
42836
  return Object.fromEntries(Object.entries(opencodeMcp).map(([serverName, serverConfig]) => {
42765
42837
  const extraFields = Object.fromEntries(Object.entries(serverConfig).filter(([key]) => !OPENCODE_KNOWN_SERVER_KEYS.has(key)));
42766
- const { enabledTools, disabledTools } = splitOpencodeServerTools(serverName, tools);
42838
+ const { enabledTools, disabledTools } = splitOpencodeServerTools(serverName, Object.keys(opencodeMcp), tools);
42767
42839
  if (!isOpencodeTransportServer(serverConfig)) return [serverName, {
42768
42840
  ...extraFields,
42769
42841
  disabled: serverConfig.enabled === false,
@@ -42899,8 +42971,8 @@ function convertToOpencodeFormat(mcpServers, existingMcp, configShape, logger) {
42899
42971
  return {
42900
42972
  mcp: Object.fromEntries(Object.entries(mcpServers).map(([serverName, serverConfig]) => {
42901
42973
  const converted = convertServerToOpencodeFormat(serverName, serverConfig, existingMcp[serverName], configShape, logger);
42902
- if (serverConfig.enabledTools) for (const tool of serverConfig.enabledTools) tools[`${serverName}_${tool}`] = true;
42903
- if (serverConfig.disabledTools) for (const tool of serverConfig.disabledTools) tools[`${serverName}_${tool}`] = false;
42974
+ if (serverConfig.enabledTools) for (const tool of serverConfig.enabledTools) tools[opencodeMcpToolPermissionKey(serverName, tool)] = true;
42975
+ if (serverConfig.disabledTools) for (const tool of serverConfig.disabledTools) tools[opencodeMcpToolPermissionKey(serverName, tool)] = false;
42904
42976
  return converted === null ? null : [serverName, converted];
42905
42977
  }).filter((entry) => entry !== null)),
42906
42978
  tools
@@ -55928,12 +56000,23 @@ function buildEffectiveHooks$1({ config, toolOverrideHooks, supportedEvents }) {
55928
56000
  * into separate groups, so a restricting field is never inherited by a hook
55929
56001
  * that did not ask for it.
55930
56002
  */
55931
- function groupDefinitionsByMatcher({ definitions, converterConfig }) {
56003
+ function groupDefinitionsByMatcher({ definitions, eventName, converterConfig, warn }) {
55932
56004
  const subdividingFields = (converterConfig.groupPassthroughFields ?? []).filter(({ subdividesGroup }) => subdividesGroup);
55933
56005
  const byMatcher = /* @__PURE__ */ new Map();
56006
+ const translatedMatchers = /* @__PURE__ */ new Map();
55934
56007
  for (const def of definitions) {
55935
56008
  const rawMatcher = def.matcher ?? "";
55936
- const matcher = converterConfig.wildcardMatcherMeansAll && rawMatcher === "*" ? "" : rawMatcher;
56009
+ const normalizedMatcher = converterConfig.wildcardMatcherMeansAll && rawMatcher === "*" ? "" : rawMatcher;
56010
+ const translation = converterConfig.matcherTranslation;
56011
+ let matcher = normalizedMatcher;
56012
+ if (translation && normalizedMatcher && !converterConfig.noMatcherEvents?.has(eventName)) {
56013
+ matcher = translatedMatchers.get(normalizedMatcher) ?? translation.toTool({
56014
+ matcher: normalizedMatcher,
56015
+ eventName,
56016
+ warn
56017
+ });
56018
+ translatedMatchers.set(normalizedMatcher, matcher);
56019
+ }
55937
56020
  const key = [matcher, ...subdividingFields.map(({ canonical, valueType }) => {
55938
56021
  const value = def[canonical];
55939
56022
  return isGroupPassthroughValue(value, valueType) ? stableJson(value) : "";
@@ -56443,7 +56526,7 @@ function buildToolHooks({ defs, eventName, converterConfig, warn }) {
56443
56526
  converterConfig,
56444
56527
  warn
56445
56528
  }),
56446
- type: converterConfig.hookTypeNames?.[hookType] ?? hookType,
56529
+ type: hookType,
56447
56530
  ...command !== void 0 && command !== null && { command },
56448
56531
  ...def.timeout !== void 0 && def.timeout !== null && { timeout: emitTimeout({
56449
56532
  timeout: def.timeout,
@@ -56493,7 +56576,9 @@ function canonicalToToolHooks({ config, toolOverrideHooks, converterConfig, logg
56493
56576
  }) ?? eventName;
56494
56577
  const byMatcher = groupDefinitionsByMatcher({
56495
56578
  definitions,
56496
- converterConfig
56579
+ eventName,
56580
+ converterConfig,
56581
+ warn
56497
56582
  });
56498
56583
  const entries = [];
56499
56584
  const isNoMatcherEvent = converterConfig.noMatcherEvents?.has(eventName) ?? false;
@@ -56572,16 +56657,19 @@ function isImportedHookType(value) {
56572
56657
  return typeof value === "string" && IMPORTED_HOOK_TYPES.has(value);
56573
56658
  }
56574
56659
  /**
56575
- * The canonical hook type a tool-side `type` value denotes: the tool's own
56576
- * spelling from {@link ToolHooksConverterConfig.hookTypeNames} when it has
56577
- * one, otherwise the value itself when it is a canonical type, and `command`
56578
- * for anything else (the same coercion as before the renames existed).
56660
+ * The canonical hook type a tool-side `type` value denotes: the canonical type
56661
+ * an {@link ToolHooksConverterConfig.importedHookTypeAliases} entry maps it to
56662
+ * when it has one, otherwise the value itself when it is a canonical type, and
56663
+ * `command` for anything else.
56579
56664
  */
56580
56665
  function resolveImportedHookType({ value, converterConfig }) {
56581
- const names = converterConfig.hookTypeNames;
56582
- if (names !== void 0 && typeof value === "string") {
56583
- for (const [canonical, tool] of Object.entries(names)) if (tool === value) return canonical;
56584
- if (Object.hasOwn(names, value)) return "command";
56666
+ const aliases = converterConfig.importedHookTypeAliases;
56667
+ if (aliases !== void 0 && typeof value === "string") {
56668
+ const aliased = lookupOwn({
56669
+ record: aliases,
56670
+ key: value
56671
+ });
56672
+ if (aliased !== void 0) return aliased;
56585
56673
  }
56586
56674
  return isImportedHookType(value) ? value : "command";
56587
56675
  }
@@ -56809,7 +56897,7 @@ function toolHookToCanonical({ h, rawEntry, eventName, converterConfig, warn })
56809
56897
  canonical: "description",
56810
56898
  warn
56811
56899
  });
56812
- const matcher = rawEntry.matcher;
56900
+ const matcher = typeof rawEntry.matcher === "string" && converterConfig.matcherTranslation ? converterConfig.matcherTranslation.toCanonical(rawEntry.matcher) : rawEntry.matcher;
56813
56901
  return {
56814
56902
  type: hookType,
56815
56903
  ...command !== void 0 && command !== null && { command },
@@ -58307,15 +58395,9 @@ const BOB_SPEC = {
58307
58395
  canonicalToToolEventNames: CANONICAL_TO_BOB_EVENT_NAMES,
58308
58396
  toolToCanonicalEventNames: BOB_TO_CANONICAL_EVENT_NAMES,
58309
58397
  projectDirVar: "",
58310
- noMatcherEvents: /* @__PURE__ */ new Set([
58311
- "sessionStart",
58312
- "beforeSubmitPrompt",
58313
- "preCompact",
58314
- "postCompact",
58315
- "stop"
58316
- ]),
58398
+ noMatcherEvents: /* @__PURE__ */ new Set(["beforeSubmitPrompt", "stop"]),
58317
58399
  supportedHookTypes: /* @__PURE__ */ new Set(["command", "http"]),
58318
- hookTypeNames: { http: "https" },
58400
+ importedHookTypeAliases: { https: "http" },
58319
58401
  wildcardMatcherMeansAll: true
58320
58402
  }
58321
58403
  };
@@ -58327,7 +58409,7 @@ const BOB_SPEC = {
58327
58409
  * `~/.bob/settings/settings.json` (user scope) — in the Claude-Code shape:
58328
58410
  * `{ "<Event>": [{ "matcher"?: "<regex>", "hooks": [{ "type": "command",
58329
58411
  * "command": "...", "timeout"?: <seconds> }] }] }`, where a handler may
58330
- * instead be `{ "type": "https", "url": "...", "timeout"?: <seconds> }`. The
58412
+ * instead be `{ "type": "http", "url": "https://...", "timeout"?: <seconds> }`. The
58331
58413
  * file also holds settings rulesync does not own, so generation merges the
58332
58414
  * `hooks` key into it (see `SHARED_CONFIG_OWNERSHIP`) instead of overwriting
58333
58415
  * the file.
@@ -60269,6 +60351,32 @@ var CopilotcliHooks = class CopilotcliHooks extends ToolHooks {
60269
60351
  }
60270
60352
  };
60271
60353
  // src/features/hooks/cortexcode-hooks.ts
60354
+ const CORTEXCODE_NO_MATCHER_EVENTS = /* @__PURE__ */ new Set(["beforeSubmitPrompt", "stop"]);
60355
+ const CANONICAL_TO_CORTEXCODE_TOOL_NAMES = {
60356
+ Bash: "bash",
60357
+ Read: "read",
60358
+ Write: "write",
60359
+ Edit: "edit",
60360
+ Grep: "grep",
60361
+ Glob: "glob"
60362
+ };
60363
+ const CORTEXCODE_TO_CANONICAL_TOOL_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_CORTEXCODE_TOOL_NAMES).map(([canonical, tool]) => [tool, canonical]));
60364
+ const MATCHER_TOKEN_PATTERN = /\\.|\[(?:\\.|[^\]\\])*\]|(?<=^|[^\w\\]|\\\w)[A-Za-z_]\w*(?!\w)/gs;
60365
+ function isMatcherName(token) {
60366
+ return !token.startsWith("\\") && !token.startsWith("[");
60367
+ }
60368
+ function replaceMatcherNames(matcher, names) {
60369
+ return matcher.replace(MATCHER_TOKEN_PATTERN, (token) => isMatcherName(token) && Object.hasOwn(names, token) ? names[token] ?? token : token);
60370
+ }
60371
+ function canonicalToCortexcodeMatcher({ matcher, eventName, warn }) {
60372
+ const translated = replaceMatcherNames(matcher, CANONICAL_TO_CORTEXCODE_TOOL_NAMES);
60373
+ const unmatched = [...translated.matchAll(MATCHER_TOKEN_PATTERN)].map(([token]) => token).filter((token) => isMatcherName(token) && /^[A-Z]/.test(token));
60374
+ if (unmatched.length > 0) warn(`Cortex Code matches hook matchers case-sensitively against lowercase runtime tool names, and ${[...new Set(unmatched)].map((name) => quoteValueForWarning(name)).join(", ")} in the "${eventName}" matcher ${quoteValueForWarning(matcher)} is not a tool Cortex Code documents a runtime ID for, so it is written as-is and will likely never match. Replace it with the lowercase name Cortex Code uses for that tool, if it has one.`);
60375
+ return translated;
60376
+ }
60377
+ function cortexcodeToCanonicalMatcher(matcher) {
60378
+ return replaceMatcherNames(matcher, CORTEXCODE_TO_CANONICAL_TOOL_NAMES);
60379
+ }
60272
60380
  const CORTEXCODE_SPEC = {
60273
60381
  displayName: "Cortex Code",
60274
60382
  overrideKey: "cortexcode",
@@ -60278,7 +60386,7 @@ const CORTEXCODE_SPEC = {
60278
60386
  toolToCanonicalEventNames: CORTEXCODE_TO_CANONICAL_EVENT_NAMES,
60279
60387
  projectDirVar: "$CORTEX_PROJECT_DIR",
60280
60388
  prefixDotRelativeCommandsOnly: true,
60281
- noMatcherEvents: /* @__PURE__ */ new Set(["beforeSubmitPrompt", "stop"]),
60389
+ noMatcherEvents: CORTEXCODE_NO_MATCHER_EVENTS,
60282
60390
  supportedHookTypes: /* @__PURE__ */ new Set(["command", "prompt"]),
60283
60391
  booleanPassthroughFields: [{
60284
60392
  canonical: "enabled",
@@ -60293,7 +60401,11 @@ const CORTEXCODE_SPEC = {
60293
60401
  canonical: "source",
60294
60402
  tool: "source",
60295
60403
  commandOnly: true
60296
- }]
60404
+ }],
60405
+ matcherTranslation: {
60406
+ toTool: canonicalToCortexcodeMatcher,
60407
+ toCanonical: cortexcodeToCanonicalMatcher
60408
+ }
60297
60409
  }
60298
60410
  };
60299
60411
  /**
@@ -60474,35 +60586,80 @@ const NORMALIZED_CRUSH_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entr
60474
60586
  function normalizeCrushEventName(event) {
60475
60587
  return event.replaceAll("_", "").toLowerCase();
60476
60588
  }
60589
+ /** Crush tool events, by normalized spelling, on which a `matcher` is valid. */
60590
+ const NORMALIZED_CRUSH_TOOL_EVENTS = new Set(CRUSH_TOOL_EVENTS.map((event) => normalizeCrushEventName(event)));
60591
+ /**
60592
+ * Resolve a `crush.hooks` override key to the canonical event it names, or
60593
+ * null when it names no canonical event. The key may be spelled the canonical
60594
+ * way (`stop`) or in any spelling Crush accepts for the event (`TurnEnd`,
60595
+ * `turn_end`), so an override an earlier import filed under a Crush name still
60596
+ * lands on the same event as the shared hooks.
60597
+ */
60598
+ function resolveOverrideCanonicalEvent(key) {
60599
+ if (lookupOwn({
60600
+ record: CANONICAL_TO_CRUSH_EVENT_NAMES,
60601
+ key
60602
+ }) !== void 0) return key;
60603
+ return lookupOwn({
60604
+ record: NORMALIZED_CRUSH_TO_CANONICAL_EVENT_NAMES,
60605
+ key: normalizeCrushEventName(key)
60606
+ }) ?? null;
60607
+ }
60477
60608
  /**
60478
60609
  * Build the `hooks` block of `crush.json` from a canonical hooks config.
60479
60610
  * Crush keys a flat array of `{name, matcher, command, timeout}` entries by
60480
60611
  * event name; `matcher` is a regex tested against the (lower-case) Crush tool
60481
60612
  * name and `timeout` is in seconds. Only `type: "command"` canonical hooks are
60482
60613
  * emitted, since a Crush hook is a shell command. A shared canonical event
60483
- * Crush does not fire is skipped (the HooksProcessor reports it), while an
60484
- * event under the `crush.hooks` override — such as one an import filed there
60485
- * — is written verbatim so it round-trips.
60614
+ * Crush does not fire is skipped (the HooksProcessor reports it).
60615
+ *
60616
+ * Every `crush.hooks` override key is resolved to its event first, whatever
60617
+ * spelling it uses: the override replaces the shared entries of that event
60618
+ * (as for every other tool's override), and override keys that name the same
60619
+ * event are concatenated in order, the way Crush itself merges them. A key
60620
+ * that names no canonical event — such as `PermissionResult`, or one an
60621
+ * import filed there — is written verbatim so it round-trips; keys of that
60622
+ * kind that Crush reads as the same event are concatenated under the first
60623
+ * spelling. A `matcher` is only written on a tool event: Crush rejects the
60624
+ * whole config when one appears anywhere else.
60486
60625
  */
60487
60626
  function canonicalToCrushHooks({ config, toolOverride, logger }) {
60488
- const sharedHooks = {};
60489
- for (const [event, defs] of Object.entries(config.hooks)) if (SUPPORTED_CRUSH_EVENTS.has(event)) sharedHooks[event] = defs;
60490
- const effective = {
60491
- ...sharedHooks,
60492
- ...toolOverride
60493
- };
60494
- const hooks = {};
60495
- for (const [event, defs] of Object.entries(effective)) {
60496
- if (isPrototypePollutionKey(event)) continue;
60497
- const crushEvent = lookupOwn({
60627
+ const shared = /* @__PURE__ */ new Map();
60628
+ for (const [event, defs] of Object.entries(config.hooks)) {
60629
+ const crushEvent = SUPPORTED_CRUSH_EVENTS.has(event) ? lookupOwn({
60498
60630
  record: CANONICAL_TO_CRUSH_EVENT_NAMES,
60499
60631
  key: event
60500
- }) ?? event;
60632
+ }) : void 0;
60633
+ if (crushEvent !== void 0) shared.set(crushEvent, defs);
60634
+ }
60635
+ const override = /* @__PURE__ */ new Map();
60636
+ const verbatimSpellings = /* @__PURE__ */ new Map();
60637
+ for (const [key, defs] of Object.entries(toolOverride ?? {})) {
60638
+ if (isPrototypePollutionKey(key) || !Array.isArray(defs)) continue;
60639
+ const canonical = resolveOverrideCanonicalEvent(key);
60640
+ let crushEvent;
60641
+ if (canonical !== null) crushEvent = lookupOwn({
60642
+ record: CANONICAL_TO_CRUSH_EVENT_NAMES,
60643
+ key: canonical
60644
+ }) ?? key;
60645
+ else {
60646
+ const normalized = normalizeCrushEventName(key);
60647
+ crushEvent = verbatimSpellings.get(normalized) ?? key;
60648
+ verbatimSpellings.set(normalized, crushEvent);
60649
+ }
60650
+ override.set(crushEvent, [...override.get(crushEvent) ?? [], ...defs]);
60651
+ }
60652
+ const effective = new Map(shared);
60653
+ for (const [crushEvent, defs] of override) effective.set(crushEvent, defs);
60654
+ const hooks = {};
60655
+ for (const [crushEvent, defs] of effective) {
60656
+ const allowsMatcher = NORMALIZED_CRUSH_TOOL_EVENTS.has(normalizeCrushEventName(crushEvent));
60501
60657
  const entries = [];
60502
60658
  for (const def of defs) {
60503
60659
  const entry = canonicalDefToCrushEntry({
60504
60660
  def,
60505
- event,
60661
+ event: crushEvent,
60662
+ allowsMatcher,
60506
60663
  logger
60507
60664
  });
60508
60665
  if (entry !== null) entries.push(entry);
@@ -60512,7 +60669,7 @@ function canonicalToCrushHooks({ config, toolOverride, logger }) {
60512
60669
  return hooks;
60513
60670
  }
60514
60671
  /** Convert one canonical hook definition to a Crush entry, or null to skip. */
60515
- function canonicalDefToCrushEntry({ def, event, logger }) {
60672
+ function canonicalDefToCrushEntry({ def, event, allowsMatcher, logger }) {
60516
60673
  if ((def.type ?? "command") !== "command") return null;
60517
60674
  if (typeof def.command !== "string" || def.command === "") {
60518
60675
  logger?.warn(`Crush hook ${quoteValueForWarning(def.name ?? "")} under "${event}" has no "command", which Crush would discard, so it was skipped.`);
@@ -60520,7 +60677,10 @@ function canonicalDefToCrushEntry({ def, event, logger }) {
60520
60677
  }
60521
60678
  const entry = { command: def.command };
60522
60679
  if (typeof def.name === "string" && def.name !== "") entry.name = def.name;
60523
- if (typeof def.matcher === "string" && def.matcher !== "" && def.matcher !== "*") entry.matcher = def.matcher;
60680
+ if (typeof def.matcher === "string" && def.matcher !== "" && def.matcher !== "*") {
60681
+ if (allowsMatcher) entry.matcher = def.matcher;
60682
+ else logger?.warn(`matcher ${quoteValueForWarning(def.matcher)} on "${event}" hook will be ignored — Crush only accepts a matcher on tool events.`);
60683
+ }
60524
60684
  if (typeof def.timeout === "number") entry.timeout = Math.ceil(def.timeout);
60525
60685
  return entry;
60526
60686
  }
@@ -60567,12 +60727,13 @@ function crushHooksToCanonical(hooksBlock) {
60567
60727
  * `<project>/crush.json` (or an existing `.crush.json`; Crush merges the pair,
60568
60728
  * lists concatenated) at project scope and `~/.config/crush/crush.json` at
60569
60729
  * user scope — as
60570
- * `hooks.<Event>: [{name?, matcher?, command, timeout?}]`. Only `PreToolUse`
60571
- * fires today. The `hooks` key is owned outright; every other top-level key
60730
+ * `hooks.<Event>: [{name?, matcher?, command, timeout?}]`. `matcher` is only
60731
+ * valid on tool events (`PreToolUse`, `PostToolUse`, `PostToolUseFailure`,
60732
+ * `PermissionRequest`, `PermissionResult`). The `hooks` key is owned outright; every other top-level key
60572
60733
  * of the file is preserved and the file is never deleted.
60573
60734
  *
60574
- * @see https://github.com/charmbracelet/crush/blob/main/docs/hooks/README.md
60575
- * @see https://github.com/charmbracelet/crush/blob/main/internal/config/config.go
60735
+ * @see https://github.com/charmbracelet/crush/blob/v0.99.0/docs/hooks/README.md
60736
+ * @see https://github.com/charmbracelet/crush/blob/v0.99.0/internal/config/hooks.go
60576
60737
  */
60577
60738
  var CrushHooks = class CrushHooks extends ToolHooks {
60578
60739
  json;
@@ -63418,8 +63579,11 @@ var LettacodeHooks = class LettacodeHooks extends ToolHooks {
63418
63579
  * OpenCode 2.x no longer runs V1 plugin modules: a plugin must default-export
63419
63580
  * `{ id, setup(ctx) }`. Inside `setup` hooks are registered per domain
63420
63581
  * (`await ctx.tool.hook("execute.before", handler)`), and lifecycle events
63421
- * are read from `ctx.event.subscribe({ signal })`, an async iterable of the
63422
- * public event stream whose payload lives under `event.data`.
63582
+ * are read from `ctx.event.subscribe({ types, signal })`, an async iterable of
63583
+ * the public event stream whose payload lives under `event.data`. `types`
63584
+ * narrows the stream to the event types the plugin handles (a newer 2.x
63585
+ * option; the per-type checks inside the loop stay, so an older 2.x that
63586
+ * ignores it still runs each command only for its own event).
63423
63587
  *
63424
63588
  * Commands run through a private Bun Shell instance (`new Bun.$.Shell()`, the
63425
63589
  * plugin runs inside the Bun-built OpenCode binary) whose working directory is
@@ -63522,13 +63686,18 @@ function buildHookRegistrationLines(hookPoint, handlers) {
63522
63686
  }
63523
63687
  /**
63524
63688
  * Emit the event-stream loop. Each command is isolated in its own try/catch so
63525
- * a failing hook neither skips its siblings nor ends the subscription.
63689
+ * a failing hook neither skips its siblings nor ends the subscription. The
63690
+ * subscription passes `types` so the server delivers only the handled events.
63526
63691
  */
63527
63692
  function buildEventLoopLines(eventHandlers) {
63528
63693
  const lines = [];
63694
+ const types = [...eventHandlers.keys()].map((eventType) => JSON.stringify(eventType)).join(", ");
63529
63695
  lines.push("const __rulesyncEvents = new AbortController();");
63530
63696
  lines.push("void (async () => {");
63531
- lines.push(" for await (const event of ctx.event.subscribe({ signal: __rulesyncEvents.signal })) {");
63697
+ lines.push(" for await (const event of ctx.event.subscribe({");
63698
+ lines.push(` types: [${types}],`);
63699
+ lines.push(" signal: __rulesyncEvents.signal,");
63700
+ lines.push(" })) {");
63532
63701
  let isFirst = true;
63533
63702
  for (const [eventType, handlers] of eventHandlers) {
63534
63703
  lines.push(` ${isFirst ? "if" : "} else if"} (event.type === "${eventType}") {`);
@@ -65662,7 +65831,12 @@ var VibeHooks = class extends ToolHooks {
65662
65831
  * resolves inside the bundle and is written as is. The file is loaded only
65663
65832
  * when `plugin.json` declares the `ai.mistral.vibe` extension.
65664
65833
  *
65834
+ * Plugin hooks run on Vibe v2.x and on v3.0.4+. v3.0.0 through v3.0.3 do not
65835
+ * run them (v3.0.4 Fixed: "Hooks shipped in a native Vibe plugin's
65836
+ * `ai.mistral.vibe/hooks.toml` now run"). The output is the same either way.
65837
+ *
65665
65838
  * @see https://github.com/mistralai/mistral-vibe/blob/v2.25.8/vibe/core/plugins/_native.py
65839
+ * @see https://github.com/mistralai/mistral-vibe/blob/v3.0.4/CHANGELOG.md
65666
65840
  */
65667
65841
  var VibePluginHooks = class extends VibeHooks {
65668
65842
  static getSettablePaths() {
@@ -65746,6 +65920,14 @@ const ZCODE_CONVERTER_CONFIG = {
65746
65920
  tool: "shell",
65747
65921
  commandOnly: true
65748
65922
  }],
65923
+ keywordPassthroughFields: [{
65924
+ canonical: "failClosed",
65925
+ tool: "onError",
65926
+ trueValue: "deny",
65927
+ falseValue: "allow",
65928
+ events: /* @__PURE__ */ new Set(["preToolUse"]),
65929
+ ignoredWhenFlags: ["async"]
65930
+ }],
65749
65931
  wildcardMatcherMeansAll: true
65750
65932
  };
65751
65933
  /**
@@ -73886,9 +74068,21 @@ function toGitlabduoServer({ serverName, serverConfig, logger }) {
73886
74068
  ...rest
73887
74069
  };
73888
74070
  }
74071
+ /**
74072
+ * GitLab Duo CLI reserves the MCP server name `duo` for its first-party server
74073
+ * (gitlab-lsp v9.33.0+): a workspace, user or plugin entry named exactly `duo`
74074
+ * (case-sensitive) is dropped with only a log line, so it would never load.
74075
+ *
74076
+ * @see https://gitlab.com/gitlab-org/editor-extensions/gitlab-lsp/-/commit/0572967c915465c1ac94ca38ef57676eb44e16d6
74077
+ */
74078
+ const GITLABDUO_RESERVED_MCP_SERVER_NAME = "duo";
73889
74079
  function toGitlabduoServers(servers, logger) {
73890
74080
  const result = {};
73891
74081
  for (const [serverName, serverConfig] of Object.entries(servers)) {
74082
+ if (serverName === GITLABDUO_RESERVED_MCP_SERVER_NAME) {
74083
+ logger?.warn(`GitLab Duo CLI MCP: skipping "${serverName}" because GitLab Duo CLI 9.33.0+ reserves that name for its first-party server and never loads a config entry with it. Rename the server to use it with GitLab Duo CLI.`);
74084
+ continue;
74085
+ }
73892
74086
  const converted = toGitlabduoServer({
73893
74087
  serverName,
73894
74088
  serverConfig,
@@ -75134,24 +75328,47 @@ async function readKimiCodeConfig({ outputRoot }) {
75134
75328
  }
75135
75329
  }
75136
75330
  /**
75331
+ * Kimi's reconnect delays are millisecond counts, so only a non-negative safe
75332
+ * integer is passed through. Anything else (a negative, a fraction, a string)
75333
+ * is dropped with a warning rather than written into, or lifted out of, the
75334
+ * shared global `config.toml`.
75335
+ */
75336
+ function toKimiCodeReconnectDelay({ value, key, logger }) {
75337
+ if (value === void 0) return;
75338
+ if (typeof value === "number" && Number.isSafeInteger(value) && value >= 0) return value;
75339
+ warnWithFallback(logger, `Kimi Code MCP: dropping ${key} because it is not a non-negative integer number of milliseconds.`);
75340
+ }
75341
+ /**
75137
75342
  * Read the `[mcp]` defaults back out of the shared user `config.toml` so a
75138
75343
  * generate → import round trip keeps them. Absent or unparseable content yields
75139
75344
  * no defaults rather than failing the import: this file belongs to three
75140
75345
  * features plus the user.
75141
75346
  */
75142
- async function readKimiCodeMcpDefaults({ outputRoot }) {
75347
+ async function readKimiCodeMcpDefaults({ outputRoot, logger }) {
75143
75348
  const config = await readKimiCodeConfig({ outputRoot });
75144
75349
  if (!config.parsed) return {};
75350
+ const reconnectInitialDelayMs = toKimiCodeReconnectDelay({
75351
+ value: config.mcp.reconnect_initial_delay_ms,
75352
+ key: "[mcp] reconnect_initial_delay_ms",
75353
+ logger
75354
+ });
75355
+ const reconnectMaxDelayMs = toKimiCodeReconnectDelay({
75356
+ value: config.mcp.reconnect_max_delay_ms,
75357
+ key: "[mcp] reconnect_max_delay_ms",
75358
+ logger
75359
+ });
75145
75360
  return {
75146
75361
  ...typeof config.mcp.startup_timeout_ms === "number" && { startupTimeoutMs: config.mcp.startup_timeout_ms },
75147
75362
  ...typeof config.mcp.tool_timeout_ms === "number" && { toolTimeoutMs: config.mcp.tool_timeout_ms },
75148
- ...typeof config.mcp.default_deferred === "boolean" && { defaultDeferred: config.mcp.default_deferred }
75363
+ ...typeof config.mcp.default_deferred === "boolean" && { defaultDeferred: config.mcp.default_deferred },
75364
+ ...reconnectInitialDelayMs !== void 0 && { reconnectInitialDelayMs },
75365
+ ...reconnectMaxDelayMs !== void 0 && { reconnectMaxDelayMs }
75149
75366
  };
75150
75367
  }
75151
75368
  /**
75152
75369
  * Kimi's `[mcp]` section in the shared user config sets the default connect and
75153
- * per-tool-call timeouts, and whether tools load on demand, for *every* MCP
75154
- * server, including ones rulesync did not write. Per-server values in
75370
+ * per-tool-call timeouts, whether tools load on demand, and the automatic
75371
+ * reconnect backoff for *every* MCP server, including ones rulesync did not write. Per-server values in
75155
75372
  * `mcp.json` still take precedence; this only moves the default.
75156
75373
  *
75157
75374
  * Written as an auxiliary file so it goes through the normal write phase and
@@ -75189,7 +75406,9 @@ var KimiCodeMcpConfigToml = class KimiCodeMcpConfigToml extends ToolFile {
75189
75406
  ...existingSection,
75190
75407
  ...defaults.startupTimeoutMs !== void 0 && { startup_timeout_ms: defaults.startupTimeoutMs },
75191
75408
  ...defaults.toolTimeoutMs !== void 0 && { tool_timeout_ms: defaults.toolTimeoutMs },
75192
- ...defaults.defaultDeferred !== void 0 && { default_deferred: defaults.defaultDeferred }
75409
+ ...defaults.defaultDeferred !== void 0 && { default_deferred: defaults.defaultDeferred },
75410
+ ...defaults.reconnectInitialDelayMs !== void 0 && { reconnect_initial_delay_ms: defaults.reconnectInitialDelayMs },
75411
+ ...defaults.reconnectMaxDelayMs !== void 0 && { reconnect_max_delay_ms: defaults.reconnectMaxDelayMs }
75193
75412
  } },
75194
75413
  filePath: relativeConfigPath
75195
75414
  });
@@ -75238,7 +75457,10 @@ var KimiCodeMcp = class KimiCodeMcp extends ToolMcp {
75238
75457
  fileContent,
75239
75458
  validate,
75240
75459
  global,
75241
- configDefaults: global ? await readKimiCodeMcpDefaults({ outputRoot }) : {},
75460
+ configDefaults: global ? await readKimiCodeMcpDefaults({
75461
+ outputRoot,
75462
+ logger
75463
+ }) : {},
75242
75464
  logger
75243
75465
  });
75244
75466
  }
@@ -75288,7 +75510,17 @@ var KimiCodeMcp = class KimiCodeMcp extends ToolMcp {
75288
75510
  const startupTimeoutMs = typeof block.startupTimeoutMs === "number" ? block.startupTimeoutMs : void 0;
75289
75511
  const toolTimeoutMs = typeof block.toolTimeoutMs === "number" ? block.toolTimeoutMs : void 0;
75290
75512
  const defaultDeferred = typeof block.defaultDeferred === "boolean" ? block.defaultDeferred : void 0;
75291
- if (startupTimeoutMs === void 0 && toolTimeoutMs === void 0 && defaultDeferred === void 0) return [];
75513
+ const reconnectInitialDelayMs = toKimiCodeReconnectDelay({
75514
+ value: block.reconnectInitialDelayMs,
75515
+ key: "kimi-code.reconnectInitialDelayMs",
75516
+ logger
75517
+ });
75518
+ const reconnectMaxDelayMs = toKimiCodeReconnectDelay({
75519
+ value: block.reconnectMaxDelayMs,
75520
+ key: "kimi-code.reconnectMaxDelayMs",
75521
+ logger
75522
+ });
75523
+ if (startupTimeoutMs === void 0 && toolTimeoutMs === void 0 && defaultDeferred === void 0 && reconnectInitialDelayMs === void 0 && reconnectMaxDelayMs === void 0) return [];
75292
75524
  const existing = await readKimiCodeConfig({ outputRoot });
75293
75525
  if (!existing.parsed) {
75294
75526
  const paths = kimiCodeConfigRelativePath();
@@ -75300,7 +75532,9 @@ var KimiCodeMcp = class KimiCodeMcp extends ToolMcp {
75300
75532
  defaults: {
75301
75533
  startupTimeoutMs,
75302
75534
  toolTimeoutMs,
75303
- defaultDeferred
75535
+ defaultDeferred,
75536
+ reconnectInitialDelayMs,
75537
+ reconnectMaxDelayMs
75304
75538
  },
75305
75539
  existing
75306
75540
  })];
@@ -85651,6 +85885,7 @@ const CLAUDECODE_ENV_IGNORED_IN_EVERY_FILE = /* @__PURE__ */ new Set([
85651
85885
  "CLAUDE_CODE_DISABLE_MOUNTED_DRIVE_RM_PROMPT",
85652
85886
  "CLAUDE_CODE_DISABLE_POWERSHELL_CMD_RM_DENY",
85653
85887
  "CLAUDE_CODE_DISABLE_SUBSTITUTION_RM_PROMPT",
85888
+ "CLAUDE_CODE_EVEN_MORE_DANGEROUSLY_SKIP_PERMISSIONS",
85654
85889
  "CLAUDE_CODE_MESSAGING_SOCKET",
85655
85890
  "CLAUDE_CODE_MESSAGING_TOKEN",
85656
85891
  "CLAUDE_CODE_PROJECT_DIR_NAME",
@@ -104896,32 +105131,59 @@ function buildChecksStrategy(ctx) {
104896
105131
  }
104897
105132
  // src/features/shared/antigravity-plugin-manifest.ts
104898
105133
  const ANTIGRAVITY_PLUGIN_NAME_PATTERN = /^[a-zA-Z0-9-_]+$/;
105134
+ const ANTIGRAVITY_PLUGIN_STRICT_NAME_PATTERN = /^[a-z0-9]+(-[a-z0-9]+)*$/;
105135
+ /**
105136
+ * Lower-case a directory name and turn `_` into `-` (collapsing repeated and
105137
+ * trimming edge hyphens), so `My_Plugin` becomes `my-plugin`.
105138
+ */
105139
+ function toStrictAntigravityPluginName(name) {
105140
+ return name.toLowerCase().replace(/[-_]+/g, "-").replace(/^-+|-+$/g, "");
105141
+ }
104899
105142
  /**
104900
105143
  * Create the `plugin.json` manifest Antigravity requires before it treats a
104901
105144
  * directory as a plugin, so a tree generated from scratch actually loads.
104902
105145
  *
104903
- * The manifest declares only `name` and `description`, and rejects anything
104904
- * else, so the one required field is derived from the plugin directory's name.
104905
- * An existing manifest is never read or rewritten: it may carry a
104906
- * hand-authored `name` or `description` that rulesync has no source for. A
105146
+ * Only the required `name` is written, derived from the plugin directory's
105147
+ * name and normalized to the lowercase kebab-case form `agy plugin validate
105148
+ * --strict` and the Marketplace require; the other manifest fields (such as
105149
+ * `description`, `version` or `author`) have no rulesync source. An existing
105150
+ * manifest is never rewritten: it may carry hand-authored fields. Its `name` is
105151
+ * read only to report a collision with another output root's name. A
104907
105152
  * manifest kept in one of the manifest folders counts as present too, so no
104908
105153
  * root stub is written next to it.
104909
105154
  */
104910
105155
  async function ensureAntigravityPluginManifests({ outputRoots, dryRun, logger }) {
104911
105156
  const paths = [];
105157
+ const seenNames = /* @__PURE__ */ new Map();
105158
+ const noteName = (name, manifestPath) => {
105159
+ const previousPath = seenNames.get(name);
105160
+ if (previousPath === void 0) {
105161
+ seenNames.set(name, manifestPath);
105162
+ return;
105163
+ }
105164
+ warnOnceWithFallback(logger, `The Antigravity plugin manifests ${quoteForLog(manifestPath)} and ${quoteForLog(previousPath)} both name the plugin ${quoteForLog(name)}, so the plugins collide in Antigravity. Rename one of the directories or set a distinct "name" by hand.`);
105165
+ };
104912
105166
  for (const outputRoot of outputRoots) {
104913
105167
  const filePath = join(outputRoot, ANTIGRAVITY_PLUGIN_MANIFEST_FILE_NAME);
104914
- if (await hasAntigravityPluginManifest(outputRoot)) continue;
105168
+ const existingManifestPath = await findAntigravityPluginManifest(outputRoot);
105169
+ if (existingManifestPath !== void 0) {
105170
+ const existingName = await readManifestName(existingManifestPath);
105171
+ if (existingName !== void 0) noteName(existingName, existingManifestPath);
105172
+ continue;
105173
+ }
104915
105174
  if (await refusesWriteOutsideRoot({
104916
105175
  logger,
104917
105176
  rootPath: outputRoot,
104918
105177
  targetPath: filePath
104919
105178
  })) continue;
104920
- const name = basename(resolve(outputRoot));
104921
- if (!ANTIGRAVITY_PLUGIN_NAME_PATTERN.test(name)) {
104922
- warnOnceWithFallback(logger, `Cannot derive an Antigravity plugin name from the directory ${quoteForLog(name)}: it must match ${ANTIGRAVITY_PLUGIN_NAME_PATTERN.source}. Create ${quoteForLog(filePath)} by hand with a valid "name".`);
105179
+ const dirName = basename(resolve(outputRoot));
105180
+ const name = toStrictAntigravityPluginName(dirName);
105181
+ if (!ANTIGRAVITY_PLUGIN_NAME_PATTERN.test(dirName) || !ANTIGRAVITY_PLUGIN_STRICT_NAME_PATTERN.test(name)) {
105182
+ warnOnceWithFallback(logger, `Cannot derive an Antigravity plugin name from the directory ${quoteForLog(dirName)}: it must contain only letters, digits, "-" and "_", and at least one letter or digit, so that it normalizes to ${ANTIGRAVITY_PLUGIN_STRICT_NAME_PATTERN.source}. Create ${quoteForLog(filePath)} by hand with a valid "name".`);
104923
105183
  continue;
104924
105184
  }
105185
+ if (name !== dirName) warnOnceWithFallback(logger, `The Antigravity plugin directory ${quoteForLog(dirName)} is not lowercase kebab-case, which \`agy plugin validate --strict\` and the Marketplace reject, so ${quoteForLog(filePath)} names the plugin ${quoteForLog(name)}.`);
105186
+ noteName(name, filePath);
104925
105187
  const content = addTrailingNewline(JSON.stringify({ name }, null, 2));
104926
105188
  if (dryRun) logger.info(`[DRY RUN] Would write: ${stripControlCharacters(filePath)}`);
104927
105189
  else await writeFileContent(filePath, content);
@@ -104934,10 +105196,18 @@ async function ensureAntigravityPluginManifests({ outputRoots, dryRun, logger })
104934
105196
  sourceLoadFailed: false
104935
105197
  };
104936
105198
  }
104937
- async function hasAntigravityPluginManifest(outputRoot) {
105199
+ async function findAntigravityPluginManifest(outputRoot) {
104938
105200
  const candidates = [join(outputRoot, ANTIGRAVITY_PLUGIN_MANIFEST_FILE_NAME), ...ANTIGRAVITY_PLUGIN_MANIFEST_FOLDERS.map((folder) => join(outputRoot, folder, ANTIGRAVITY_PLUGIN_MANIFEST_FILE_NAME))];
104939
- for (const candidate of candidates) if (await fileExists(candidate)) return true;
104940
- return false;
105201
+ for (const candidate of candidates) if (await fileExists(candidate)) return candidate;
105202
+ }
105203
+ /** The manifest's string `name`, or `undefined` when it is unreadable or absent. */
105204
+ async function readManifestName(manifestPath) {
105205
+ try {
105206
+ const parsed = JSON.parse(await readFileContent(manifestPath));
105207
+ return isRecord(parsed) && typeof parsed.name === "string" ? parsed.name : void 0;
105208
+ } catch {
105209
+ return;
105210
+ }
104941
105211
  }
104942
105212
  // src/features/shared/hermes-project-plugin-activation.ts
104943
105213
  function mergeEnabledPlugins({ existingContent, filePath, pluginNames }) {
@@ -107040,4 +107310,4 @@ async function importChecksCore(params) {
107040
107310
  }
107041
107311
  export { RulesyncPermissions as $, formatError as $n, isSymlink as $t, CODEBUDDY_DIR as A, MAX_FILE_SIZE as An, withFallbackLoggerTarget as At, CLAUDECODE_LOCAL_RULE_FILE_NAME as B, RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH as Bn, assertTreeContainsNoSymlinks as Bt, QWENCODE_LOCAL_RULE_FILE_NAME as C, PACKAGING_TOOL_TARGETS as Cn, getSourceTransport as Ct, FACTORYDROID_SETTINGS_LOCAL_FILE_NAME as D, DEPRECATED_FEATURE_REPLACEMENTS as Dn, WarningCollectingLogger as Dt, FACTORYDROID_DIR as E, ALL_FEATURES_WITH_WILDCARD as En, JsonLogger as Et, groupSpellingsByCaseFoldedIdentity as F, RULESYNC_CONFIG_SCHEMA_URL as Fn, PathOutsideRootError as Ft, toInstructionBudgetFile as G, RULESYNC_PERMISSIONS_SCHEMA_URL as Gn, ensureDir as Gt, CLAUDECODE_SETTINGS_LOCAL_FILE_NAME as H, RULESYNC_MCP_SCHEMA_URL as Hn, checkPathTraversal as Ht, CODEXCLI_BASH_RULES_FILE_NAME as I, RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH as In, applyFileMode as It, RulesyncSubagentFrontmatterSchema as J, RULESYNC_SKILLS_RELATIVE_DIR_PATH as Jn, findFilesByGlobs as Jt, getLocalSkillDirNames as K, RULESYNC_RELATIVE_DIR_PATH as Kn, fileExists as Kt, CODEXCLI_DIR as L, RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH as Ln, assertDirectoryIfExists as Lt, AUGMENTCODE_DIR as M, RULESYNC_CHECKS_RELATIVE_DIR_PATH as Mn, withWarnOnceScope as Mt, AUGMENTCODE_SETTINGS_LOCAL_FILE_NAME as N, RULESYNC_COMMANDS_RELATIVE_DIR_PATH as Nn, CLIError as Nt, COMMANDCODE_DIR as O, CURATED_RULES_FEATURE_SUBDIR as On, fallbackLogger as Ot, caseFoldIdentity as P, RULESYNC_CONFIG_RELATIVE_FILE_PATH as Pn, ErrorCodes as Pt, RulesyncRuleFrontmatterSchema as Q, RULESYNC_XDG_CONFIG_HOME_DEFAULT_DIR_NAME as Qn, isFileSystemError as Qt, CLAUDECODE_AGENT_MEMORY_LOCAL_DIR_NAME as R, RULESYNC_HOOKS_RELATIVE_FILE_PATH as Rn, assertNoSymlinkBelowRoot as Rt, QWENCODE_DIR as S, ALL_TOOL_TARGETS_WITH_WILDCARD as Sn, assertTargetsFeaturesExclusive as St, LETTACODE_SETTINGS_LOCAL_FILE_NAME as T, ALL_FEATURES as Tn, ConsoleLogger as Tt, CLAUDECODE_SKILLS_DIR_PATH as U, RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH as Un, createTempDirectory as Ut, CLAUDECODE_MEMORIES_DIR_NAME as V, RULESYNC_MCP_RELATIVE_FILE_PATH as Vn, assertWritablePathInsideRoot as Vt, findProjectInstructionBudgetFindings as W, RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH as Wn, directoryExists as Wt, RulesyncSkillFrontmatterSchema as X, RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH as Xn, getHomeDirectory as Xt, RulesyncSkill as Y, RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH as Yn, getFileSizeAndExecutableBit as Yt, RulesyncRule as Z, RULESYNC_USER_CONFIG_DIR_NAME as Zn, isFileNotFoundError as Zt, SubagentsProcessor as _, toPosixPath as _n, resolveEffectiveInputRoots as _t, convertFromTool as a, readFileContent as an, stripControlCharactersKeepingLineFeeds as ar, parseJsonc as at, CommandsProcessor as b, mapWithConcurrency as bn, GITIGNORE_DESTINATION_KEY as bt, parseCommaSeparatedList as c, removeDirectoryStrict as cn, RulesyncCheck as ct, shortenToWidth as d, removeFileWithoutFollowingSymlinks as dn, loadYaml as dt, listDirectoryEntryNames as en, truncateText as er, RulesyncMcp as et, McpProcessor as f, removeTempDirectory as fn, isRecord as ft, RulesProcessor as g, splitPathSegments as gn, mergeInputRootConfigs as gt, ChecksProcessor as h, runWithDirectoryRollback as hn, ConfigResolver as ht, inspectInputRoots as i, posixRelativePathEscapesRoot as in, stripControlCharacters as ir, resolveRulesyncSourceWritePath as it, CODEBUDDY_LOCAL_RULE_FILE_NAME as j, RULESYNC_AIIGNORE_RELATIVE_FILE_PATH as jn, resetRunWarningState as jt, COMMANDCODE_SETTINGS_LOCAL_FILE_NAME as k, FEATURE_SOURCE_TREE_ENTRIES as kn, warnOnConflictingFlags as kt, ELLIPSIS_WIDTH as l, removeFile as ln, RulesyncCheckFrontmatterSchema as lt, HooksProcessor as m, resolvedRelativePath as mn, SKILL_FILE_NAME as mt, formatSourceLoadFailure as n, listSubdirectoryNames as nn, hasEnclosingMarkOutsideKeycap as nr, RulesyncHooks as nt, isPackagingToolTarget as o, readFileContentOrNull as on, stripControlCharactersKeepingLineFeedsAndTabs as or, RulesyncCommand as ot, IgnoreProcessor as p, resolvePath as pn, SHARED_USER_MANAGED_CONFIG_PATHS as pt, RulesyncSubagent as q, RULESYNC_RULES_RELATIVE_DIR_PATH as qn, fileExistsStrict as qt, generate as r, pathEscapesRoot as rn, quoteForLog as rr, getRulesyncSourceCandidates as rt, getProcessorRegistryEntry as s, removeDirectory as sn, stripHiddenCharacters as sr, RulesyncCommandFrontmatterSchema as st, importFromTool as t, listFilePathsRecursively as tn, hasDeceptiveHiddenCharacters as tr, RulesyncIgnore as tt, displayWidthOf as u, removeFileStrict as un, stringifyFrontmatter as ut, SkillsProcessor as v, writeFileBuffer as vn, CONFLICTING_TARGET_PAIRS as vt, LETTACODE_DIR as w, ToolTargetSchema as wn, findControlCharacter as wt, REASONIX_LOCAL_RULE_FILE_NAME as x, ALL_TOOL_TARGETS as xn, SourceEntrySchema as xt, CRUSH_LOCAL_RULE_FILE_NAME as y, writeFileContent as yn, ConfigFileSchema as yt, CLAUDECODE_DIR as z, RULESYNC_IGNORE_RELATIVE_FILE_PATH as zn, assertPathResolvesInsideRoot as zt };
107042
107312
 
107043
- //# sourceMappingURL=import-BQNLyW5s.js.map
107313
+ //# sourceMappingURL=import-D2mUPFM0.js.map