rulesync 43.2.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({
@@ -11646,7 +11697,8 @@ const RulesyncSkillFrontmatterSchema = z.looseObject({
11646
11697
  license: z.optional(z.string()),
11647
11698
  compatibility: z.optional(z.union([z.string(), z.looseObject({})])),
11648
11699
  metadata: z.optional(z.looseObject({})),
11649
- "allowed-tools": z.optional(z.union([z.string(), z.array(z.string())]))
11700
+ "allowed-tools": z.optional(z.union([z.string(), z.array(z.string())])),
11701
+ "disable-model-invocation": z.optional(z.boolean())
11650
11702
  })),
11651
11703
  hermesagent: z.optional(z.looseObject({})),
11652
11704
  vibe: z.optional(z.looseObject({
@@ -16204,8 +16256,9 @@ var ToolSkill = class extends AiDir {
16204
16256
  * {@link AntigravitySharedSkill.getToolTarget}.
16205
16257
  *
16206
16258
  * Frontmatter beyond `name`/`description` (e.g. `disable-slash-command`,
16207
- * `metadata`) comes from the rulesync section named after that target, with the
16208
- * 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
16209
16262
  * `.agents/skills/` tree both targets merge the `antigravity-ide` and
16210
16263
  * `antigravity-cli` sections so their output never diverges.
16211
16264
  */
@@ -16278,6 +16331,8 @@ var AntigravitySharedSkill = class extends ToolSkill {
16278
16331
  const { name: _name, description: _description, ...sectionFields } = section;
16279
16332
  const sectionDisableSlashCommand = sectionFields["disable-slash-command"];
16280
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"];
16281
16336
  const metadata = resolveMetadata({
16282
16337
  rootFrontmatter: rulesyncFrontmatter,
16283
16338
  section: sectionFields
@@ -16287,6 +16342,7 @@ var AntigravitySharedSkill = class extends ToolSkill {
16287
16342
  name: rulesyncFrontmatter.name,
16288
16343
  description: rulesyncFrontmatter.description,
16289
16344
  ...disableSlashCommand !== void 0 && { "disable-slash-command": disableSlashCommand },
16345
+ ...disableModelInvocation !== void 0 && { "disable-model-invocation": disableModelInvocation },
16290
16346
  ...metadata !== void 0 && { metadata }
16291
16347
  };
16292
16348
  return new this({
@@ -19600,7 +19656,8 @@ const AgentsSkillsSkillFrontmatterSchema = z.looseObject({
19600
19656
  license: z.optional(z.string()),
19601
19657
  compatibility: z.optional(z.union([z.string(), z.looseObject({})])),
19602
19658
  metadata: z.optional(z.looseObject({})),
19603
- "allowed-tools": z.optional(z.union([z.string(), z.array(z.string())]))
19659
+ "allowed-tools": z.optional(z.union([z.string(), z.array(z.string())])),
19660
+ "disable-model-invocation": z.optional(z.boolean())
19604
19661
  });
19605
19662
  const COMPATIBILITY_MAX_LENGTH = 500;
19606
19663
  const AGENT_SKILLS_SPEC = "the Agent Skills spec";
@@ -19741,6 +19798,38 @@ function toSpecConformantAgentSkillFields(section, { coerceMetadata = true } = {
19741
19798
  ...allowedTools !== void 0 && allowedTools.length > 0 && { "allowed-tools": allowedTools }
19742
19799
  };
19743
19800
  }
19801
+ /**
19802
+ * The opt-in `disable-model-invocation` flag of the `agentsskills` block.
19803
+ *
19804
+ * The key is not part of the Agent Skills specification, so `skills-ref
19805
+ * validate` rejects a `SKILL.md` that carries it, but several clients reading
19806
+ * `.agents/skills/` (e.g. pi) honor it. To keep the default output
19807
+ * spec-conformant, only an explicit `agentsskills.disable-model-invocation`
19808
+ * value is written; the root-level rulesync flag is deliberately not inherited.
19809
+ *
19810
+ * Kept apart from {@link toSpecConformantAgentSkillFields} because that helper
19811
+ * is shared with writers of other tools' own locations, which have their own
19812
+ * sections for tool-specific keys. Only writers of the shared `.agents/skills/`
19813
+ * root (see {@link isSharedAgentSkillsRoot}) — the native target, GitLab Duo's
19814
+ * project scope and the simulated `agentsmd` — spread this, so they all emit
19815
+ * the same frontmatter for that file.
19816
+ */
19817
+ function toAgentsSkillsInvocationField(section) {
19818
+ const value = section?.["disable-model-invocation"];
19819
+ return value === void 0 ? {} : { "disable-model-invocation": value };
19820
+ }
19821
+ /**
19822
+ * Whether a skill directory lives in the cross-client `.agents/skills/` root
19823
+ * (project, or `~/.agents/skills/` in global scope). The
19824
+ * `agentsskills.disable-model-invocation` opt-in exists for the clients that
19825
+ * read that root, so it is written and imported only there — not by the
19826
+ * subclasses that reuse this class for a tool's own location (Trae, Qoder, Xum,
19827
+ * …), where the key would otherwise leak into the `agentsskills` section on
19828
+ * import and from there into every `.agents/skills/` output.
19829
+ */
19830
+ function isSharedAgentSkillsRoot(relativeDirPath) {
19831
+ return toPosixPath(relativeDirPath) === toPosixPath(AGENTSMD_SKILLS_DIR_PATH);
19832
+ }
19744
19833
  /** The `SKILL.md` a diagnostic should point at, in the scope it is written to. */
19745
19834
  function agentSkillFilePath({ outputRoot, relativeDirPath, dirName }) {
19746
19835
  return join(outputRoot, relativeDirPath, dirName, SKILL_FILE_NAME);
@@ -19822,7 +19911,8 @@ var AgentsSkillsSkill = class AgentsSkillsSkill extends ToolSkill {
19822
19911
  ...frontmatter.license !== void 0 && { license: frontmatter.license },
19823
19912
  ...frontmatter.compatibility !== void 0 && { compatibility: frontmatter.compatibility },
19824
19913
  ...frontmatter.metadata !== void 0 && { metadata: frontmatter.metadata },
19825
- ...allowedTools !== void 0 && allowedTools.length > 0 && { "allowed-tools": allowedTools }
19914
+ ...allowedTools !== void 0 && allowedTools.length > 0 && { "allowed-tools": allowedTools },
19915
+ ...isSharedAgentSkillsRoot(this.getRelativeDirPath()) && frontmatter["disable-model-invocation"] !== void 0 && { "disable-model-invocation": frontmatter["disable-model-invocation"] }
19826
19916
  };
19827
19917
  const rulesyncFrontmatter = {
19828
19918
  name: frontmatter.name,
@@ -19848,7 +19938,8 @@ var AgentsSkillsSkill = class AgentsSkillsSkill extends ToolSkill {
19848
19938
  const agentsSkillsFrontmatter = {
19849
19939
  name: rulesyncFrontmatter.name,
19850
19940
  description: rulesyncFrontmatter.description,
19851
- ...toSpecConformantAgentSkillFields(resolveAgentsSkillsSection(rulesyncFrontmatter))
19941
+ ...toSpecConformantAgentSkillFields(resolveAgentsSkillsSection(rulesyncFrontmatter)),
19942
+ ...isSharedAgentSkillsRoot(settablePaths.relativeDirPath) && toAgentsSkillsInvocationField(rulesyncFrontmatter.agentsskills)
19852
19943
  };
19853
19944
  AgentsSkillsSkill.reportSpecViolations({
19854
19945
  outputRoot,
@@ -24922,7 +25013,8 @@ var AgentsmdSkill = class AgentsmdSkill extends SimulatedSkill {
24922
25013
  const relativeDirPath = this.getSettablePaths().relativeDirPath;
24923
25014
  const frontmatter = {
24924
25015
  ...defaults.frontmatter,
24925
- ...toSpecConformantAgentSkillFields(resolveAgentsSkillsSection(params.rulesyncSkill.getFrontmatter()))
25016
+ ...toSpecConformantAgentSkillFields(resolveAgentsSkillsSection(params.rulesyncSkill.getFrontmatter())),
25017
+ ...toAgentsSkillsInvocationField(params.rulesyncSkill.getFrontmatter().agentsskills)
24926
25018
  };
24927
25019
  AgentsSkillsSkill.reportSpecViolations({
24928
25020
  outputRoot: params.outputRoot ?? process.cwd(),
@@ -31679,16 +31771,17 @@ const VibeSkillFrontmatterSchema = z.looseObject({
31679
31771
  "allowed-tools": z.optional(z.union([z.string(), z.array(z.string())]))
31680
31772
  });
31681
31773
  /**
31682
- * Skill names Vibe's built-in skills occupy. `SkillManager` treats them as
31683
- * reserved: a project or user skill whose frontmatter `name` matches one is
31684
- * skipped at load time with only a debug log ("Skipping skill '<name>' ...
31685
- * because builtin skill names are reserved"), so a generated skill by that
31686
- * name is silently never offered. That is the legacy backend, still the
31687
- * default; the unified harness constructs the manager with
31688
- * `include_builtins=False` because its built-ins arrive as skills of the
31689
- * shipped `vibe` plugin under namespaced names, so it reserves nothing.
31690
- * @see https://github.com/mistralai/mistral-vibe/blob/v2.25.3/vibe/core/skills/builtins/__init__.py
31691
- * @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
31692
31785
  */
31693
31786
  const VIBE_RESERVED_SKILL_NAMES = /* @__PURE__ */ new Set(["vibe", "skill-creator"]);
31694
31787
  /**
@@ -31731,11 +31824,12 @@ function buildVibeFrontmatter(rulesyncFrontmatter) {
31731
31824
  /**
31732
31825
  * The skill is still generated under the reserved name — the name is the
31733
31826
  * user's to change, and a rename here would make it diverge from the other
31734
- * 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.
31735
31829
  */
31736
31830
  function warnAboutReservedSkillName({ name, logger }) {
31737
31831
  if (!VIBE_RESERVED_SKILL_NAMES.has(name)) return;
31738
- 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.`);
31739
31833
  }
31740
31834
  var VibeSkill = class VibeSkill extends ToolSkill {
31741
31835
  constructor({ outputRoot = process.cwd(), relativeDirPath = VibeSkill.getSettablePaths().relativeDirPath, dirName, frontmatter, body, otherFiles = [], validate = true, global = false }) {
@@ -42703,20 +42797,35 @@ const OPENCODE_KNOWN_SERVER_KEYS = /* @__PURE__ */ new Set([
42703
42797
  "disabledTools"
42704
42798
  ]);
42705
42799
  function convertFromOpencodeFormat(opencodeMcp, tools, remoteType) {
42800
+ const serverNames = Object.keys(opencodeMcp);
42706
42801
  return {
42707
- ...orphanMcpToolFiltersToRulesync(opencodeMcp, tools),
42802
+ ...orphanMcpToolFiltersToRulesync(opencodeMcp, Object.fromEntries(Object.entries(tools ?? {}).filter(([key]) => !findOpencodeToolOwner({
42803
+ key,
42804
+ serverNames
42805
+ })))),
42708
42806
  ...convertOpencodeServers(opencodeMcp, tools, remoteType)
42709
42807
  };
42710
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
+ }
42711
42818
  /** Split the shared top-level `tools` map into this server's own two lists. */
42712
- function splitOpencodeServerTools(serverName, tools) {
42819
+ function splitOpencodeServerTools(serverName, serverNames, tools) {
42713
42820
  const enabledTools = [];
42714
42821
  const disabledTools = [];
42715
- const prefix = `${serverName}_`;
42716
42822
  for (const [toolName, enabled] of Object.entries(tools ?? {})) {
42717
- if (!toolName.startsWith(prefix)) continue;
42718
- const toolSuffix = toolName.slice(prefix.length);
42719
- (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);
42720
42829
  }
42721
42830
  return {
42722
42831
  enabledTools,
@@ -42726,7 +42835,7 @@ function splitOpencodeServerTools(serverName, tools) {
42726
42835
  function convertOpencodeServers(opencodeMcp, tools, remoteType) {
42727
42836
  return Object.fromEntries(Object.entries(opencodeMcp).map(([serverName, serverConfig]) => {
42728
42837
  const extraFields = Object.fromEntries(Object.entries(serverConfig).filter(([key]) => !OPENCODE_KNOWN_SERVER_KEYS.has(key)));
42729
- const { enabledTools, disabledTools } = splitOpencodeServerTools(serverName, tools);
42838
+ const { enabledTools, disabledTools } = splitOpencodeServerTools(serverName, Object.keys(opencodeMcp), tools);
42730
42839
  if (!isOpencodeTransportServer(serverConfig)) return [serverName, {
42731
42840
  ...extraFields,
42732
42841
  disabled: serverConfig.enabled === false,
@@ -42862,8 +42971,8 @@ function convertToOpencodeFormat(mcpServers, existingMcp, configShape, logger) {
42862
42971
  return {
42863
42972
  mcp: Object.fromEntries(Object.entries(mcpServers).map(([serverName, serverConfig]) => {
42864
42973
  const converted = convertServerToOpencodeFormat(serverName, serverConfig, existingMcp[serverName], configShape, logger);
42865
- if (serverConfig.enabledTools) for (const tool of serverConfig.enabledTools) tools[`${serverName}_${tool}`] = true;
42866
- 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;
42867
42976
  return converted === null ? null : [serverName, converted];
42868
42977
  }).filter((entry) => entry !== null)),
42869
42978
  tools
@@ -55891,12 +56000,23 @@ function buildEffectiveHooks$1({ config, toolOverrideHooks, supportedEvents }) {
55891
56000
  * into separate groups, so a restricting field is never inherited by a hook
55892
56001
  * that did not ask for it.
55893
56002
  */
55894
- function groupDefinitionsByMatcher({ definitions, converterConfig }) {
56003
+ function groupDefinitionsByMatcher({ definitions, eventName, converterConfig, warn }) {
55895
56004
  const subdividingFields = (converterConfig.groupPassthroughFields ?? []).filter(({ subdividesGroup }) => subdividesGroup);
55896
56005
  const byMatcher = /* @__PURE__ */ new Map();
56006
+ const translatedMatchers = /* @__PURE__ */ new Map();
55897
56007
  for (const def of definitions) {
55898
56008
  const rawMatcher = def.matcher ?? "";
55899
- 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
+ }
55900
56020
  const key = [matcher, ...subdividingFields.map(({ canonical, valueType }) => {
55901
56021
  const value = def[canonical];
55902
56022
  return isGroupPassthroughValue(value, valueType) ? stableJson(value) : "";
@@ -56406,7 +56526,7 @@ function buildToolHooks({ defs, eventName, converterConfig, warn }) {
56406
56526
  converterConfig,
56407
56527
  warn
56408
56528
  }),
56409
- type: converterConfig.hookTypeNames?.[hookType] ?? hookType,
56529
+ type: hookType,
56410
56530
  ...command !== void 0 && command !== null && { command },
56411
56531
  ...def.timeout !== void 0 && def.timeout !== null && { timeout: emitTimeout({
56412
56532
  timeout: def.timeout,
@@ -56456,7 +56576,9 @@ function canonicalToToolHooks({ config, toolOverrideHooks, converterConfig, logg
56456
56576
  }) ?? eventName;
56457
56577
  const byMatcher = groupDefinitionsByMatcher({
56458
56578
  definitions,
56459
- converterConfig
56579
+ eventName,
56580
+ converterConfig,
56581
+ warn
56460
56582
  });
56461
56583
  const entries = [];
56462
56584
  const isNoMatcherEvent = converterConfig.noMatcherEvents?.has(eventName) ?? false;
@@ -56535,16 +56657,19 @@ function isImportedHookType(value) {
56535
56657
  return typeof value === "string" && IMPORTED_HOOK_TYPES.has(value);
56536
56658
  }
56537
56659
  /**
56538
- * The canonical hook type a tool-side `type` value denotes: the tool's own
56539
- * spelling from {@link ToolHooksConverterConfig.hookTypeNames} when it has
56540
- * one, otherwise the value itself when it is a canonical type, and `command`
56541
- * 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.
56542
56664
  */
56543
56665
  function resolveImportedHookType({ value, converterConfig }) {
56544
- const names = converterConfig.hookTypeNames;
56545
- if (names !== void 0 && typeof value === "string") {
56546
- for (const [canonical, tool] of Object.entries(names)) if (tool === value) return canonical;
56547
- 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;
56548
56673
  }
56549
56674
  return isImportedHookType(value) ? value : "command";
56550
56675
  }
@@ -56772,7 +56897,7 @@ function toolHookToCanonical({ h, rawEntry, eventName, converterConfig, warn })
56772
56897
  canonical: "description",
56773
56898
  warn
56774
56899
  });
56775
- const matcher = rawEntry.matcher;
56900
+ const matcher = typeof rawEntry.matcher === "string" && converterConfig.matcherTranslation ? converterConfig.matcherTranslation.toCanonical(rawEntry.matcher) : rawEntry.matcher;
56776
56901
  return {
56777
56902
  type: hookType,
56778
56903
  ...command !== void 0 && command !== null && { command },
@@ -58270,15 +58395,9 @@ const BOB_SPEC = {
58270
58395
  canonicalToToolEventNames: CANONICAL_TO_BOB_EVENT_NAMES,
58271
58396
  toolToCanonicalEventNames: BOB_TO_CANONICAL_EVENT_NAMES,
58272
58397
  projectDirVar: "",
58273
- noMatcherEvents: /* @__PURE__ */ new Set([
58274
- "sessionStart",
58275
- "beforeSubmitPrompt",
58276
- "preCompact",
58277
- "postCompact",
58278
- "stop"
58279
- ]),
58398
+ noMatcherEvents: /* @__PURE__ */ new Set(["beforeSubmitPrompt", "stop"]),
58280
58399
  supportedHookTypes: /* @__PURE__ */ new Set(["command", "http"]),
58281
- hookTypeNames: { http: "https" },
58400
+ importedHookTypeAliases: { https: "http" },
58282
58401
  wildcardMatcherMeansAll: true
58283
58402
  }
58284
58403
  };
@@ -58290,7 +58409,7 @@ const BOB_SPEC = {
58290
58409
  * `~/.bob/settings/settings.json` (user scope) — in the Claude-Code shape:
58291
58410
  * `{ "<Event>": [{ "matcher"?: "<regex>", "hooks": [{ "type": "command",
58292
58411
  * "command": "...", "timeout"?: <seconds> }] }] }`, where a handler may
58293
- * instead be `{ "type": "https", "url": "...", "timeout"?: <seconds> }`. The
58412
+ * instead be `{ "type": "http", "url": "https://...", "timeout"?: <seconds> }`. The
58294
58413
  * file also holds settings rulesync does not own, so generation merges the
58295
58414
  * `hooks` key into it (see `SHARED_CONFIG_OWNERSHIP`) instead of overwriting
58296
58415
  * the file.
@@ -60232,6 +60351,32 @@ var CopilotcliHooks = class CopilotcliHooks extends ToolHooks {
60232
60351
  }
60233
60352
  };
60234
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
+ }
60235
60380
  const CORTEXCODE_SPEC = {
60236
60381
  displayName: "Cortex Code",
60237
60382
  overrideKey: "cortexcode",
@@ -60241,7 +60386,7 @@ const CORTEXCODE_SPEC = {
60241
60386
  toolToCanonicalEventNames: CORTEXCODE_TO_CANONICAL_EVENT_NAMES,
60242
60387
  projectDirVar: "$CORTEX_PROJECT_DIR",
60243
60388
  prefixDotRelativeCommandsOnly: true,
60244
- noMatcherEvents: /* @__PURE__ */ new Set(["beforeSubmitPrompt", "stop"]),
60389
+ noMatcherEvents: CORTEXCODE_NO_MATCHER_EVENTS,
60245
60390
  supportedHookTypes: /* @__PURE__ */ new Set(["command", "prompt"]),
60246
60391
  booleanPassthroughFields: [{
60247
60392
  canonical: "enabled",
@@ -60256,7 +60401,11 @@ const CORTEXCODE_SPEC = {
60256
60401
  canonical: "source",
60257
60402
  tool: "source",
60258
60403
  commandOnly: true
60259
- }]
60404
+ }],
60405
+ matcherTranslation: {
60406
+ toTool: canonicalToCortexcodeMatcher,
60407
+ toCanonical: cortexcodeToCanonicalMatcher
60408
+ }
60260
60409
  }
60261
60410
  };
60262
60411
  /**
@@ -60437,35 +60586,80 @@ const NORMALIZED_CRUSH_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entr
60437
60586
  function normalizeCrushEventName(event) {
60438
60587
  return event.replaceAll("_", "").toLowerCase();
60439
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
+ }
60440
60608
  /**
60441
60609
  * Build the `hooks` block of `crush.json` from a canonical hooks config.
60442
60610
  * Crush keys a flat array of `{name, matcher, command, timeout}` entries by
60443
60611
  * event name; `matcher` is a regex tested against the (lower-case) Crush tool
60444
60612
  * name and `timeout` is in seconds. Only `type: "command"` canonical hooks are
60445
60613
  * emitted, since a Crush hook is a shell command. A shared canonical event
60446
- * Crush does not fire is skipped (the HooksProcessor reports it), while an
60447
- * event under the `crush.hooks` override — such as one an import filed there
60448
- * — 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.
60449
60625
  */
60450
60626
  function canonicalToCrushHooks({ config, toolOverride, logger }) {
60451
- const sharedHooks = {};
60452
- for (const [event, defs] of Object.entries(config.hooks)) if (SUPPORTED_CRUSH_EVENTS.has(event)) sharedHooks[event] = defs;
60453
- const effective = {
60454
- ...sharedHooks,
60455
- ...toolOverride
60456
- };
60457
- const hooks = {};
60458
- for (const [event, defs] of Object.entries(effective)) {
60459
- if (isPrototypePollutionKey(event)) continue;
60460
- 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({
60461
60630
  record: CANONICAL_TO_CRUSH_EVENT_NAMES,
60462
60631
  key: event
60463
- }) ?? 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));
60464
60657
  const entries = [];
60465
60658
  for (const def of defs) {
60466
60659
  const entry = canonicalDefToCrushEntry({
60467
60660
  def,
60468
- event,
60661
+ event: crushEvent,
60662
+ allowsMatcher,
60469
60663
  logger
60470
60664
  });
60471
60665
  if (entry !== null) entries.push(entry);
@@ -60475,7 +60669,7 @@ function canonicalToCrushHooks({ config, toolOverride, logger }) {
60475
60669
  return hooks;
60476
60670
  }
60477
60671
  /** Convert one canonical hook definition to a Crush entry, or null to skip. */
60478
- function canonicalDefToCrushEntry({ def, event, logger }) {
60672
+ function canonicalDefToCrushEntry({ def, event, allowsMatcher, logger }) {
60479
60673
  if ((def.type ?? "command") !== "command") return null;
60480
60674
  if (typeof def.command !== "string" || def.command === "") {
60481
60675
  logger?.warn(`Crush hook ${quoteValueForWarning(def.name ?? "")} under "${event}" has no "command", which Crush would discard, so it was skipped.`);
@@ -60483,7 +60677,10 @@ function canonicalDefToCrushEntry({ def, event, logger }) {
60483
60677
  }
60484
60678
  const entry = { command: def.command };
60485
60679
  if (typeof def.name === "string" && def.name !== "") entry.name = def.name;
60486
- 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
+ }
60487
60684
  if (typeof def.timeout === "number") entry.timeout = Math.ceil(def.timeout);
60488
60685
  return entry;
60489
60686
  }
@@ -60530,12 +60727,13 @@ function crushHooksToCanonical(hooksBlock) {
60530
60727
  * `<project>/crush.json` (or an existing `.crush.json`; Crush merges the pair,
60531
60728
  * lists concatenated) at project scope and `~/.config/crush/crush.json` at
60532
60729
  * user scope — as
60533
- * `hooks.<Event>: [{name?, matcher?, command, timeout?}]`. Only `PreToolUse`
60534
- * 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
60535
60733
  * of the file is preserved and the file is never deleted.
60536
60734
  *
60537
- * @see https://github.com/charmbracelet/crush/blob/main/docs/hooks/README.md
60538
- * @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
60539
60737
  */
60540
60738
  var CrushHooks = class CrushHooks extends ToolHooks {
60541
60739
  json;
@@ -63381,8 +63579,11 @@ var LettacodeHooks = class LettacodeHooks extends ToolHooks {
63381
63579
  * OpenCode 2.x no longer runs V1 plugin modules: a plugin must default-export
63382
63580
  * `{ id, setup(ctx) }`. Inside `setup` hooks are registered per domain
63383
63581
  * (`await ctx.tool.hook("execute.before", handler)`), and lifecycle events
63384
- * are read from `ctx.event.subscribe({ signal })`, an async iterable of the
63385
- * 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).
63386
63587
  *
63387
63588
  * Commands run through a private Bun Shell instance (`new Bun.$.Shell()`, the
63388
63589
  * plugin runs inside the Bun-built OpenCode binary) whose working directory is
@@ -63485,13 +63686,18 @@ function buildHookRegistrationLines(hookPoint, handlers) {
63485
63686
  }
63486
63687
  /**
63487
63688
  * Emit the event-stream loop. Each command is isolated in its own try/catch so
63488
- * 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.
63489
63691
  */
63490
63692
  function buildEventLoopLines(eventHandlers) {
63491
63693
  const lines = [];
63694
+ const types = [...eventHandlers.keys()].map((eventType) => JSON.stringify(eventType)).join(", ");
63492
63695
  lines.push("const __rulesyncEvents = new AbortController();");
63493
63696
  lines.push("void (async () => {");
63494
- 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(" })) {");
63495
63701
  let isFirst = true;
63496
63702
  for (const [eventType, handlers] of eventHandlers) {
63497
63703
  lines.push(` ${isFirst ? "if" : "} else if"} (event.type === "${eventType}") {`);
@@ -65625,7 +65831,12 @@ var VibeHooks = class extends ToolHooks {
65625
65831
  * resolves inside the bundle and is written as is. The file is loaded only
65626
65832
  * when `plugin.json` declares the `ai.mistral.vibe` extension.
65627
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
+ *
65628
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
65629
65840
  */
65630
65841
  var VibePluginHooks = class extends VibeHooks {
65631
65842
  static getSettablePaths() {
@@ -65709,6 +65920,14 @@ const ZCODE_CONVERTER_CONFIG = {
65709
65920
  tool: "shell",
65710
65921
  commandOnly: true
65711
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
+ }],
65712
65931
  wildcardMatcherMeansAll: true
65713
65932
  };
65714
65933
  /**
@@ -73849,9 +74068,21 @@ function toGitlabduoServer({ serverName, serverConfig, logger }) {
73849
74068
  ...rest
73850
74069
  };
73851
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";
73852
74079
  function toGitlabduoServers(servers, logger) {
73853
74080
  const result = {};
73854
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
+ }
73855
74086
  const converted = toGitlabduoServer({
73856
74087
  serverName,
73857
74088
  serverConfig,
@@ -75097,24 +75328,47 @@ async function readKimiCodeConfig({ outputRoot }) {
75097
75328
  }
75098
75329
  }
75099
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
+ /**
75100
75342
  * Read the `[mcp]` defaults back out of the shared user `config.toml` so a
75101
75343
  * generate → import round trip keeps them. Absent or unparseable content yields
75102
75344
  * no defaults rather than failing the import: this file belongs to three
75103
75345
  * features plus the user.
75104
75346
  */
75105
- async function readKimiCodeMcpDefaults({ outputRoot }) {
75347
+ async function readKimiCodeMcpDefaults({ outputRoot, logger }) {
75106
75348
  const config = await readKimiCodeConfig({ outputRoot });
75107
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
+ });
75108
75360
  return {
75109
75361
  ...typeof config.mcp.startup_timeout_ms === "number" && { startupTimeoutMs: config.mcp.startup_timeout_ms },
75110
75362
  ...typeof config.mcp.tool_timeout_ms === "number" && { toolTimeoutMs: config.mcp.tool_timeout_ms },
75111
- ...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 }
75112
75366
  };
75113
75367
  }
75114
75368
  /**
75115
75369
  * Kimi's `[mcp]` section in the shared user config sets the default connect and
75116
- * per-tool-call timeouts, and whether tools load on demand, for *every* MCP
75117
- * 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
75118
75372
  * `mcp.json` still take precedence; this only moves the default.
75119
75373
  *
75120
75374
  * Written as an auxiliary file so it goes through the normal write phase and
@@ -75152,7 +75406,9 @@ var KimiCodeMcpConfigToml = class KimiCodeMcpConfigToml extends ToolFile {
75152
75406
  ...existingSection,
75153
75407
  ...defaults.startupTimeoutMs !== void 0 && { startup_timeout_ms: defaults.startupTimeoutMs },
75154
75408
  ...defaults.toolTimeoutMs !== void 0 && { tool_timeout_ms: defaults.toolTimeoutMs },
75155
- ...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 }
75156
75412
  } },
75157
75413
  filePath: relativeConfigPath
75158
75414
  });
@@ -75201,7 +75457,10 @@ var KimiCodeMcp = class KimiCodeMcp extends ToolMcp {
75201
75457
  fileContent,
75202
75458
  validate,
75203
75459
  global,
75204
- configDefaults: global ? await readKimiCodeMcpDefaults({ outputRoot }) : {},
75460
+ configDefaults: global ? await readKimiCodeMcpDefaults({
75461
+ outputRoot,
75462
+ logger
75463
+ }) : {},
75205
75464
  logger
75206
75465
  });
75207
75466
  }
@@ -75251,7 +75510,17 @@ var KimiCodeMcp = class KimiCodeMcp extends ToolMcp {
75251
75510
  const startupTimeoutMs = typeof block.startupTimeoutMs === "number" ? block.startupTimeoutMs : void 0;
75252
75511
  const toolTimeoutMs = typeof block.toolTimeoutMs === "number" ? block.toolTimeoutMs : void 0;
75253
75512
  const defaultDeferred = typeof block.defaultDeferred === "boolean" ? block.defaultDeferred : void 0;
75254
- 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 [];
75255
75524
  const existing = await readKimiCodeConfig({ outputRoot });
75256
75525
  if (!existing.parsed) {
75257
75526
  const paths = kimiCodeConfigRelativePath();
@@ -75263,7 +75532,9 @@ var KimiCodeMcp = class KimiCodeMcp extends ToolMcp {
75263
75532
  defaults: {
75264
75533
  startupTimeoutMs,
75265
75534
  toolTimeoutMs,
75266
- defaultDeferred
75535
+ defaultDeferred,
75536
+ reconnectInitialDelayMs,
75537
+ reconnectMaxDelayMs
75267
75538
  },
75268
75539
  existing
75269
75540
  })];
@@ -85614,6 +85885,7 @@ const CLAUDECODE_ENV_IGNORED_IN_EVERY_FILE = /* @__PURE__ */ new Set([
85614
85885
  "CLAUDE_CODE_DISABLE_MOUNTED_DRIVE_RM_PROMPT",
85615
85886
  "CLAUDE_CODE_DISABLE_POWERSHELL_CMD_RM_DENY",
85616
85887
  "CLAUDE_CODE_DISABLE_SUBSTITUTION_RM_PROMPT",
85888
+ "CLAUDE_CODE_EVEN_MORE_DANGEROUSLY_SKIP_PERMISSIONS",
85617
85889
  "CLAUDE_CODE_MESSAGING_SOCKET",
85618
85890
  "CLAUDE_CODE_MESSAGING_TOKEN",
85619
85891
  "CLAUDE_CODE_PROJECT_DIR_NAME",
@@ -104859,32 +105131,59 @@ function buildChecksStrategy(ctx) {
104859
105131
  }
104860
105132
  // src/features/shared/antigravity-plugin-manifest.ts
104861
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
+ }
104862
105142
  /**
104863
105143
  * Create the `plugin.json` manifest Antigravity requires before it treats a
104864
105144
  * directory as a plugin, so a tree generated from scratch actually loads.
104865
105145
  *
104866
- * The manifest declares only `name` and `description`, and rejects anything
104867
- * else, so the one required field is derived from the plugin directory's name.
104868
- * An existing manifest is never read or rewritten: it may carry a
104869
- * 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
104870
105152
  * manifest kept in one of the manifest folders counts as present too, so no
104871
105153
  * root stub is written next to it.
104872
105154
  */
104873
105155
  async function ensureAntigravityPluginManifests({ outputRoots, dryRun, logger }) {
104874
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
+ };
104875
105166
  for (const outputRoot of outputRoots) {
104876
105167
  const filePath = join(outputRoot, ANTIGRAVITY_PLUGIN_MANIFEST_FILE_NAME);
104877
- 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
+ }
104878
105174
  if (await refusesWriteOutsideRoot({
104879
105175
  logger,
104880
105176
  rootPath: outputRoot,
104881
105177
  targetPath: filePath
104882
105178
  })) continue;
104883
- const name = basename(resolve(outputRoot));
104884
- if (!ANTIGRAVITY_PLUGIN_NAME_PATTERN.test(name)) {
104885
- 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".`);
104886
105183
  continue;
104887
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);
104888
105187
  const content = addTrailingNewline(JSON.stringify({ name }, null, 2));
104889
105188
  if (dryRun) logger.info(`[DRY RUN] Would write: ${stripControlCharacters(filePath)}`);
104890
105189
  else await writeFileContent(filePath, content);
@@ -104897,10 +105196,18 @@ async function ensureAntigravityPluginManifests({ outputRoots, dryRun, logger })
104897
105196
  sourceLoadFailed: false
104898
105197
  };
104899
105198
  }
104900
- async function hasAntigravityPluginManifest(outputRoot) {
105199
+ async function findAntigravityPluginManifest(outputRoot) {
104901
105200
  const candidates = [join(outputRoot, ANTIGRAVITY_PLUGIN_MANIFEST_FILE_NAME), ...ANTIGRAVITY_PLUGIN_MANIFEST_FOLDERS.map((folder) => join(outputRoot, folder, ANTIGRAVITY_PLUGIN_MANIFEST_FILE_NAME))];
104902
- for (const candidate of candidates) if (await fileExists(candidate)) return true;
104903
- 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
+ }
104904
105211
  }
104905
105212
  // src/features/shared/hermes-project-plugin-activation.ts
104906
105213
  function mergeEnabledPlugins({ existingContent, filePath, pluginNames }) {
@@ -107003,4 +107310,4 @@ async function importChecksCore(params) {
107003
107310
  }
107004
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 };
107005
107312
 
107006
- //# sourceMappingURL=import-DPXjeK8q.js.map
107313
+ //# sourceMappingURL=import-D2mUPFM0.js.map