rulesync 16.6.0 → 16.7.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.
@@ -614,6 +614,13 @@ async function readFileBuffer(filepath) {
614
614
  return readFile(filepath);
615
615
  }
616
616
  /**
617
+ * Read file as a buffer if it exists, otherwise return null.
618
+ */
619
+ async function readFileBufferOrNull(filepath) {
620
+ if (await fileExists(filepath)) return readFileBuffer(filepath);
621
+ return null;
622
+ }
623
+ /**
617
624
  * Normalizes text to LF line endings and adds exactly one trailing newline.
618
625
  * Removes any existing trailing whitespace and appends a single newline.
619
626
  */
@@ -625,6 +632,10 @@ async function writeFileContent(filepath, content) {
625
632
  await ensureDir(dirname(filepath));
626
633
  await writeFile(filepath, content, "utf-8");
627
634
  }
635
+ async function writeFileBuffer(filepath, buffer) {
636
+ await ensureDir(dirname(filepath));
637
+ await writeFile(filepath, buffer);
638
+ }
628
639
  async function fileExists(filepath) {
629
640
  try {
630
641
  await stat(filepath);
@@ -2157,6 +2168,7 @@ const HookDefinitionSchema = z.looseObject({
2157
2168
  name: z.optional(safeString),
2158
2169
  description: z.optional(safeString),
2159
2170
  failClosed: z.optional(z.boolean()),
2171
+ commandRegex: z.optional(safeString),
2160
2172
  sequential: z.optional(z.boolean()),
2161
2173
  async: z.optional(z.boolean()),
2162
2174
  env: z.optional(z.record(z.string(), safeString)),
@@ -2173,6 +2185,7 @@ const HookDefinitionSchema = z.looseObject({
2173
2185
  metadata: z.optional(z.looseObject({})),
2174
2186
  if: z.optional(safeString),
2175
2187
  commandWindows: z.optional(safeString),
2188
+ additionalContextLimit: z.optional(z.int().check(nonnegative())),
2176
2189
  asyncRewake: z.optional(z.boolean()),
2177
2190
  continueOnBlock: z.optional(z.boolean())
2178
2191
  });
@@ -4220,7 +4233,8 @@ const ReasonixPermissionsOverrideSchema = z.looseObject({
4220
4233
  */
4221
4234
  const FactorydroidPermissionsOverrideSchema = z.looseObject({
4222
4235
  permission: z.optional(ToolScopedPermissionSchema),
4223
- commandBlocklist: z.optional(z.array(z.string()))
4236
+ commandBlocklist: z.optional(z.array(z.string())),
4237
+ disabledSkills: z.optional(z.array(z.string()))
4224
4238
  });
4225
4239
  /**
4226
4240
  * Tool-scoped override block for Warp. Warp's `[agents.profiles]` table exposes
@@ -4666,13 +4680,22 @@ const CodexBasePermissionProfileSchema = z.enum(CODEX_BASE_PERMISSION_PROFILES);
4666
4680
  * `base_permission_profile` it is consumed by the profile builder, not
4667
4681
  * written as a top-level config key.
4668
4682
  *
4669
- * Two surfaces are deliberately NOT authorable here so the override can never
4670
- * clobber a feature-owned key: `mcp_servers.*` per-MCP gating is owned by the
4671
- * MCP feature (`codexcli-mcp.ts` already writes the `mcp_servers` tables in the
4672
- * same `config.toml`), and `permissions` / `default_permissions` are owned by
4673
- * the canonical model. Any such key placed in the override is skipped with a
4674
- * warning. Kept `looseObject` (verbatim passthrough) so future top-level Codex
4675
- * config keys can be authored without Rulesync modeling each one.
4683
+ * The keys written to `config.toml` are an **allowlist**, not verbatim
4684
+ * passthrough: only `CODEXCLI_OVERRIDE_KEYS`
4685
+ * (`src/constants/codexcli-paths.ts` `approval_policy`, `sandbox_mode`,
4686
+ * `sandbox_workspace_write`, `apps`, `approvals_reviewer`) are emitted, and
4687
+ * `computeCodexcliOverridePatch` skips anything else with a warning.
4688
+ * `base_permission_profile` and `git_write_rules` are consumed by the profile
4689
+ * builder rather than written, as described above, and `permission` is the
4690
+ * tool-scoped canonical block, which `RulesyncPermissions.forTarget` strips out
4691
+ * of the override before it ever reaches the patch. The allowlist is what keeps
4692
+ * the override from clobbering a feature-owned key: `mcp_servers.*` per-MCP
4693
+ * gating is owned by the MCP feature (`codexcli-mcp.ts` already writes the
4694
+ * `mcp_servers` tables in the same `config.toml`), and `permissions` /
4695
+ * `default_permissions` are owned by the canonical model. The schema itself is
4696
+ * `looseObject` so an unmodeled key parses (and is then reported rather than
4697
+ * rejected outright); supporting a new top-level Codex config key means adding
4698
+ * it to `CODEXCLI_OVERRIDE_KEYS`.
4676
4699
  *
4677
4700
  * @see https://developers.openai.com/codex/config-reference
4678
4701
  * @see https://developers.openai.com/codex/permissions
@@ -5147,7 +5170,11 @@ const RulesyncSkillFrontmatterSchema = z.looseObject({
5147
5170
  })),
5148
5171
  copilot: z.optional(z.looseObject({
5149
5172
  license: z.optional(z.string()),
5150
- "allowed-tools": z.optional(z.union([z.string(), z.array(z.string())]))
5173
+ "allowed-tools": z.optional(z.union([z.string(), z.array(z.string())])),
5174
+ "argument-hint": z.optional(z.string()),
5175
+ "user-invocable": z.optional(z.boolean()),
5176
+ "disable-model-invocation": z.optional(z.boolean()),
5177
+ context: z.optional(z.string())
5151
5178
  })),
5152
5179
  copilotcli: z.optional(z.looseObject({
5153
5180
  license: z.optional(z.string()),
@@ -5205,7 +5232,9 @@ const RulesyncSkillFrontmatterSchema = z.looseObject({
5205
5232
  })),
5206
5233
  factorydroid: z.optional(z.looseObject({
5207
5234
  "disable-model-invocation": z.optional(z.boolean()),
5208
- "user-invocable": z.optional(z.boolean())
5235
+ "user-invocable": z.optional(z.boolean()),
5236
+ enabled: z.optional(z.boolean()),
5237
+ "allowed-tools": z.optional(z.union([z.string(), z.array(z.string())]))
5209
5238
  })),
5210
5239
  grokcli: z.optional(z.looseObject({
5211
5240
  "disable-model-invocation": z.optional(z.boolean()),
@@ -5418,8 +5447,8 @@ async function getLocalSkillDirNames(outputRoot) {
5418
5447
  * Resolve the effective `disable-model-invocation` value for a tool skill.
5419
5448
  *
5420
5449
  * The rulesync skill frontmatter exposes a root-level `disable-model-invocation`
5421
- * default that applies to every tool supporting the flag (claudecode, cursor,
5422
- * zed, pi, qwencode, grokcli, factorydroid). Each tool's own section may override that
5450
+ * default that applies to every tool supporting the flag (claudecode, copilot,
5451
+ * copilotcli, cursor, zed, pi, qwencode, grokcli, factorydroid). Each tool's own section may override that
5423
5452
  * default with a per-target value. A defined section value (including `false`)
5424
5453
  * always wins over the root default.
5425
5454
  *
@@ -5432,8 +5461,8 @@ function resolveDisableModelInvocation({ rootFrontmatter, section }) {
5432
5461
  * Resolve the effective `user-invocable` value for a tool skill.
5433
5462
  *
5434
5463
  * The rulesync skill frontmatter exposes a root-level `user-invocable` default
5435
- * that applies to every tool supporting the flag (claudecode, qwencode, vibe,
5436
- * grokcli, factorydroid). Each tool's own section may override that default with a
5464
+ * that applies to every tool supporting the flag (claudecode, copilot,
5465
+ * copilotcli, qwencode, vibe, grokcli, factorydroid). Each tool's own section may override that default with a
5437
5466
  * per-target value. A defined section value (including `false`) always wins
5438
5467
  * over the root default.
5439
5468
  *
@@ -5646,6 +5675,31 @@ function fileContentsEquivalent({ filePath, expected, existing }) {
5646
5675
  if (structured !== void 0) return structured;
5647
5676
  return addTrailingNewline(expected) === addTrailingNewline(existing);
5648
5677
  }
5678
+ /**
5679
+ * Whether an on-disk companion file is equivalent to the generated one.
5680
+ *
5681
+ * Companion files (everything beside a skill's `SKILL.md`) are written byte for
5682
+ * byte, so byte equality is the whole test for a user asset carried through
5683
+ * from the source directory: a CRLF fixture or a deliberately newline-less file
5684
+ * must compare equal to itself and unequal to a normalized copy, and a copy
5685
+ * that has drifted must be repaired rather than tolerated.
5686
+ *
5687
+ * A `composed` file is different — Rulesync builds it from frontmatter (Codex
5688
+ * CLI's `agents/openai.yaml`), so differing bytes fall back to the structured
5689
+ * comparison and a formatter re-indenting it is not reported as a change on
5690
+ * every generate. Only the structured verdict counts: there is deliberately no
5691
+ * text fallback, since trailing-whitespace-insensitive text equality is exactly
5692
+ * the normalization companion files no longer get.
5693
+ */
5694
+ function companionFileContentsEquivalent({ filePath, expected, existing, composed = false }) {
5695
+ if (existing === null) return false;
5696
+ if (existing.equals(expected)) return true;
5697
+ if (!composed) return false;
5698
+ const expectedText = expected.toString("utf-8");
5699
+ const existingText = existing.toString("utf-8");
5700
+ if (!Buffer.from(expectedText, "utf-8").equals(expected) || !Buffer.from(existingText, "utf-8").equals(existing)) return false;
5701
+ return tryFileContentsEquivalent(filePath, expectedText, existingText) ?? false;
5702
+ }
5649
5703
  //#endregion
5650
5704
  //#region src/types/feature-processor.ts
5651
5705
  var FeatureProcessor = class {
@@ -7046,6 +7100,14 @@ const SHARED_CONFIG_OWNERSHIP = {
7046
7100
  ownedKeys: ["mcp", "tools"]
7047
7101
  } }
7048
7102
  },
7103
+ ".config/goose/config.yaml": {
7104
+ format: "yaml",
7105
+ invalidRootPolicy: "error",
7106
+ features: { mcp: {
7107
+ kind: "replace-owned-keys",
7108
+ ownedKeys: ["extensions"]
7109
+ } }
7110
+ },
7049
7111
  [CODEXCLI_CONFIG_SHARED_FILE_KEY]: {
7050
7112
  format: "toml",
7051
7113
  features: {
@@ -9284,9 +9346,10 @@ const FACTORYDROID_HOOKS_FILE_NAME = "hooks.json";
9284
9346
  //#region src/features/commands/factorydroid-command.ts
9285
9347
  const FactorydroidCommandFrontmatterSchema = z.looseObject({
9286
9348
  description: z.optional(z.string()),
9287
- "argument-hint": z.optional(z.string()),
9288
- "allowed-tools": z.optional(z.union([z.string(), z.array(z.string())]))
9349
+ "argument-hint": z.optional(z.string())
9289
9350
  });
9351
+ /** Not a Droid command surface; see the schema comment above. */
9352
+ const FACTORYDROID_UNSUPPORTED_COMMAND_FIELDS = ["allowed-tools"];
9290
9353
  var FactorydroidCommand = class FactorydroidCommand extends ToolCommand {
9291
9354
  frontmatter;
9292
9355
  body;
@@ -9313,6 +9376,7 @@ var FactorydroidCommand = class FactorydroidCommand extends ToolCommand {
9313
9376
  }
9314
9377
  toRulesyncCommand() {
9315
9378
  const { description, ...restFields } = this.frontmatter;
9379
+ for (const field of FACTORYDROID_UNSUPPORTED_COMMAND_FIELDS) delete restFields[field];
9316
9380
  const rulesyncFrontmatter = {
9317
9381
  targets: ["*"],
9318
9382
  description,
@@ -9336,6 +9400,7 @@ var FactorydroidCommand = class FactorydroidCommand extends ToolCommand {
9336
9400
  description: rulesyncFrontmatter.description,
9337
9401
  ...factorydroidFields
9338
9402
  };
9403
+ for (const field of FACTORYDROID_UNSUPPORTED_COMMAND_FIELDS) delete factorydroidFrontmatter[field];
9339
9404
  const body = rulesyncCommand.getBody();
9340
9405
  const paths = this.getSettablePaths({ global });
9341
9406
  return new FactorydroidCommand({
@@ -13047,16 +13112,28 @@ function buildEffectiveHooks$1({ config, toolOverrideHooks, supportedEvents }) {
13047
13112
  };
13048
13113
  }
13049
13114
  /**
13050
- * Group a list of hook definitions by their `matcher` (empty string when absent),
13051
- * preserving insertion order of both keys and grouped definitions.
13115
+ * Group a list of hook definitions by their `matcher` (empty string when
13116
+ * absent), preserving insertion order of both keys and grouped definitions.
13117
+ * Definitions that disagree on a `subdividesGroup` passthrough field are split
13118
+ * into separate groups, so a restricting field is never inherited by a hook
13119
+ * that did not ask for it.
13052
13120
  */
13053
- function groupDefinitionsByMatcher(definitions) {
13121
+ function groupDefinitionsByMatcher({ definitions, converterConfig }) {
13122
+ const subdividingFields = (converterConfig.groupPassthroughFields ?? []).filter(({ subdividesGroup }) => subdividesGroup);
13054
13123
  const byMatcher = /* @__PURE__ */ new Map();
13055
13124
  for (const def of definitions) {
13056
- const key = def.matcher ?? "";
13057
- const list = byMatcher.get(key);
13058
- if (list) list.push(def);
13059
- else byMatcher.set(key, [def]);
13125
+ const rawMatcher = def.matcher ?? "";
13126
+ const matcher = converterConfig.wildcardMatcherMeansAll && rawMatcher === "*" ? "" : rawMatcher;
13127
+ const key = [matcher, ...subdividingFields.map(({ canonical, valueType }) => {
13128
+ const value = def[canonical];
13129
+ return isGroupPassthroughValue(value, valueType) ? stableJson(value) : "";
13130
+ })].join("\0");
13131
+ const group = byMatcher.get(key);
13132
+ if (group) group.defs.push(def);
13133
+ else byMatcher.set(key, {
13134
+ matcher,
13135
+ defs: [def]
13136
+ });
13060
13137
  }
13061
13138
  return byMatcher;
13062
13139
  }
@@ -13101,6 +13178,24 @@ function importBooleanPassthroughFields({ h, converterConfig }) {
13101
13178
  return Object.fromEntries((converterConfig.booleanPassthroughFields ?? []).filter(({ tool }) => typeof h[tool] === "boolean").map(({ canonical, tool }) => [canonical, h[tool]]));
13102
13179
  }
13103
13180
  /**
13181
+ * Emit the configured number passthrough fields on the tool side, mapping each
13182
+ * canonical field name to its (possibly renamed) tool field name. Only finite
13183
+ * numbers are carried through.
13184
+ */
13185
+ function emitNumberPassthroughFields({ def, hookType, converterConfig }) {
13186
+ return Object.fromEntries((converterConfig.numberPassthroughFields ?? []).filter(({ canonical, commandOnly }) => {
13187
+ if (commandOnly === true && hookType !== "command") return false;
13188
+ return Number.isFinite(def[canonical]);
13189
+ }).map(({ canonical, tool }) => [tool, def[canonical]]));
13190
+ }
13191
+ /**
13192
+ * Import the configured number passthrough fields back into canonical fields,
13193
+ * reversing {@link emitNumberPassthroughFields}. Only finite numbers are read.
13194
+ */
13195
+ function importNumberPassthroughFields({ h, converterConfig }) {
13196
+ return Object.fromEntries((converterConfig.numberPassthroughFields ?? []).filter(({ tool }) => Number.isFinite(h[tool])).map(({ canonical, tool }) => [canonical, h[tool]]));
13197
+ }
13198
+ /**
13104
13199
  * Emit the configured string passthrough fields on the tool side, mapping each
13105
13200
  * canonical field name to its (possibly renamed) tool field name. Only non-empty
13106
13201
  * string values are carried through.
@@ -13138,17 +13233,45 @@ function importArrayPassthroughFields({ h, converterConfig, logger }) {
13138
13233
  return Object.fromEntries(fields.filter(({ tool }) => isSafeStringArray(h[tool])).map(({ canonical, tool }) => [canonical, h[tool]]));
13139
13234
  }
13140
13235
  /**
13236
+ * Emit the configured string-map passthrough fields on the tool side. Only maps
13237
+ * whose values are all control-character-free strings are carried through.
13238
+ */
13239
+ function emitRecordPassthroughFields({ def, hookType, converterConfig }) {
13240
+ return Object.fromEntries((converterConfig.recordPassthroughFields ?? []).filter(({ canonical, commandOnly }) => {
13241
+ if (commandOnly === true && hookType !== "command") return false;
13242
+ return isSafeStringRecord(def[canonical]);
13243
+ }).map(({ canonical, tool }) => [tool, def[canonical]]));
13244
+ }
13245
+ /**
13246
+ * Import the configured string-map passthrough fields, reversing
13247
+ * {@link emitRecordPassthroughFields}.
13248
+ */
13249
+ function importRecordPassthroughFields({ h, hookType, converterConfig, logger }) {
13250
+ const fields = (converterConfig.recordPassthroughFields ?? []).filter(({ commandOnly }) => commandOnly !== true || hookType === "command");
13251
+ for (const { tool } of fields) if (h[tool] !== void 0 && !isSafeStringRecord(h[tool])) logger?.warn(`Dropping "${tool}" while importing a hook: it must be a map of strings whose keys are non-empty and free of "=", and whose keys and values carry no newline, carriage return or NUL characters.`);
13252
+ return Object.fromEntries(fields.filter(({ tool }) => isSafeStringRecord(h[tool])).map(({ canonical, tool }) => [canonical, h[tool]]));
13253
+ }
13254
+ /**
13255
+ * Check a value against the shape its field documents. A string field also
13256
+ * rejects control characters, matching the canonical `safeString` so an
13257
+ * imported value cannot fail validation on the next generate.
13258
+ */
13259
+ function isGroupPassthroughValue(value, valueType = "object") {
13260
+ if (valueType === "string") return typeof value === "string" && !CONTROL_CHARS.some((char) => value.includes(char));
13261
+ return isPlainObject$1(value);
13262
+ }
13263
+ /**
13141
13264
  * Emit the configured group-level passthrough fields, taken from the first
13142
13265
  * definition of the group that carries one.
13143
13266
  */
13144
13267
  function emitGroupPassthroughFields({ defs, eventName, converterConfig, logger }) {
13145
13268
  const emitted = {};
13146
- for (const { canonical, tool } of converterConfig.groupPassthroughFields ?? []) {
13269
+ for (const { canonical, tool, valueType } of converterConfig.groupPassthroughFields ?? []) {
13147
13270
  const carried = defs.map((def) => def[canonical]);
13148
- const first = carried.find((value) => isPlainObject$1(value));
13271
+ const first = carried.find((value) => isGroupPassthroughValue(value, valueType));
13149
13272
  if (first === void 0) continue;
13150
13273
  const firstStable = stableJson(first);
13151
- const agrees = (value) => isPlainObject$1(value) && stableJson(value) === firstStable;
13274
+ const agrees = (value) => isGroupPassthroughValue(value, valueType) && stableJson(value) === firstStable;
13152
13275
  if (!carried.every(agrees)) logger?.warn(`"${tool}" belongs to the whole matcher group on "${eventName}" hooks, so every hook in this group gets ${JSON.stringify(first)} — including any that asked for something else, or for nothing.`);
13153
13276
  emitted[tool] = first;
13154
13277
  }
@@ -13160,7 +13283,7 @@ function emitGroupPassthroughFields({ defs, eventName, converterConfig, logger }
13160
13283
  */
13161
13284
  function importGroupPassthroughFields({ rawEntry, converterConfig }) {
13162
13285
  const entry = rawEntry;
13163
- return Object.fromEntries((converterConfig.groupPassthroughFields ?? []).filter(({ tool }) => isPlainObject$1(entry[tool])).map(({ canonical, tool }) => [canonical, entry[tool]]));
13286
+ return Object.fromEntries((converterConfig.groupPassthroughFields ?? []).filter(({ tool, valueType }) => isGroupPassthroughValue(entry[tool], valueType)).map(({ canonical, tool }) => [canonical, entry[tool]]));
13164
13287
  }
13165
13288
  /**
13166
13289
  * Emit the payload fields specific to a hook type — `url`/`headers`/
@@ -13206,6 +13329,11 @@ function buildToolHooks({ defs, converterConfig }) {
13206
13329
  hookType,
13207
13330
  converterConfig
13208
13331
  }),
13332
+ ...emitNumberPassthroughFields({
13333
+ def,
13334
+ hookType,
13335
+ converterConfig
13336
+ }),
13209
13337
  ...emitStringPassthroughFields({
13210
13338
  def,
13211
13339
  hookType,
@@ -13216,6 +13344,11 @@ function buildToolHooks({ defs, converterConfig }) {
13216
13344
  hookType,
13217
13345
  converterConfig
13218
13346
  }),
13347
+ ...emitRecordPassthroughFields({
13348
+ def,
13349
+ hookType,
13350
+ converterConfig
13351
+ }),
13219
13352
  type: hookType,
13220
13353
  ...command !== void 0 && command !== null && { command },
13221
13354
  ...def.timeout !== void 0 && def.timeout !== null && { timeout: def.timeout },
@@ -13246,10 +13379,13 @@ function canonicalToToolHooks({ config, toolOverrideHooks, converterConfig, logg
13246
13379
  const result = {};
13247
13380
  for (const [eventName, definitions] of Object.entries(effectiveHooks)) {
13248
13381
  const toolEventName = converterConfig.canonicalToToolEventNames[eventName] ?? eventName;
13249
- const byMatcher = groupDefinitionsByMatcher(definitions);
13382
+ const byMatcher = groupDefinitionsByMatcher({
13383
+ definitions,
13384
+ converterConfig
13385
+ });
13250
13386
  const entries = [];
13251
13387
  const isNoMatcherEvent = converterConfig.noMatcherEvents?.has(eventName) ?? false;
13252
- for (const [matcherKey, defs] of byMatcher) {
13388
+ for (const { matcher: matcherKey, defs } of byMatcher.values()) {
13253
13389
  if (isNoMatcherEvent && matcherKey) logger?.warn(`matcher "${matcherKey}" on "${eventName}" hook will be ignored — this event does not support matchers`);
13254
13390
  const hooks = buildToolHooks({
13255
13391
  defs,
@@ -13329,9 +13465,27 @@ function isStringArray(value) {
13329
13465
  }
13330
13466
  /** Compare object values without letting key order decide the answer. */
13331
13467
  function stableJson(value) {
13468
+ if (typeof value === "string") return JSON.stringify(value);
13332
13469
  return JSON.stringify(Object.fromEntries(Object.entries(value).toSorted(([a], [b]) => a.localeCompare(b))));
13333
13470
  }
13334
13471
  /**
13472
+ * A string map safe to hand a tool as a hook's environment block. On top of
13473
+ * {@link isStringRecord} it rejects a non-plain object (a class instance is not
13474
+ * data) and applies the control-character rule to the values, as
13475
+ * {@link isSafeStringArray} does for `args`.
13476
+ *
13477
+ * The keys are checked more strictly than the values. A tool builds each entry
13478
+ * back into a `KEY=VALUE` string for the spawned process, so a key holding `=`
13479
+ * (or a control character, or nothing at all) names a different variable than
13480
+ * it appears to — `PATH=/tmp/evil` written as a key would set `PATH`. An
13481
+ * authored `.rulesync/hooks.*` can arrive via `rulesync fetch`, so that is not
13482
+ * a shape to pass along.
13483
+ */
13484
+ function isSafeStringRecord(value) {
13485
+ if (!isPlainObject$1(value) || !isStringRecord(value)) return false;
13486
+ return Object.entries(value).every(([key, entry]) => key !== "" && !key.includes("=") && !CONTROL_CHARS.some((char) => key.includes(char) || entry.includes(char)));
13487
+ }
13488
+ /**
13335
13489
  * Control characters cannot ride from an existing tool config into a canonical
13336
13490
  * field the schema guards with `safeString`, or the next generate fails
13337
13491
  * validation on a file this import itself wrote — and the hooks feature is
@@ -13384,6 +13538,10 @@ function toolHookToCanonical({ h, rawEntry, converterConfig, logger }) {
13384
13538
  h,
13385
13539
  converterConfig
13386
13540
  }),
13541
+ ...importNumberPassthroughFields({
13542
+ h,
13543
+ converterConfig
13544
+ }),
13387
13545
  ...importStringPassthroughFields({
13388
13546
  h,
13389
13547
  converterConfig
@@ -13393,6 +13551,12 @@ function toolHookToCanonical({ h, rawEntry, converterConfig, logger }) {
13393
13551
  converterConfig,
13394
13552
  logger
13395
13553
  }),
13554
+ ...importRecordPassthroughFields({
13555
+ h,
13556
+ hookType,
13557
+ converterConfig,
13558
+ logger
13559
+ }),
13396
13560
  ...importGroupPassthroughFields({
13397
13561
  rawEntry,
13398
13562
  converterConfig
@@ -13910,6 +14074,14 @@ var ClaudecodeHooks = class extends ToolHooks {
13910
14074
  isDeletable() {
13911
14075
  return false;
13912
14076
  }
14077
+ /**
14078
+ * The converter config used for both directions. Exposed as a static hook so
14079
+ * plugin-scoped subclasses can swap tool-specific details (e.g. the project
14080
+ * directory variable) without duplicating the rest of the config.
14081
+ */
14082
+ static getConverterConfig() {
14083
+ return CLAUDE_CONVERTER_CONFIG;
14084
+ }
13913
14085
  static getSettablePaths(_options = {}) {
13914
14086
  return {
13915
14087
  relativeDirPath: CLAUDECODE_DIR,
@@ -13935,7 +14107,7 @@ var ClaudecodeHooks = class extends ToolHooks {
13935
14107
  const claudeHooks = canonicalToToolHooks({
13936
14108
  config,
13937
14109
  toolOverrideHooks: config.claudecode?.hooks,
13938
- converterConfig: CLAUDE_CONVERTER_CONFIG,
14110
+ converterConfig: this.getConverterConfig(),
13939
14111
  logger
13940
14112
  });
13941
14113
  const fileContent = applySharedConfigPatch({
@@ -13962,7 +14134,7 @@ var ClaudecodeHooks = class extends ToolHooks {
13962
14134
  }
13963
14135
  const hooks = toolHooksToCanonical({
13964
14136
  hooks: settings.hooks,
13965
- converterConfig: CLAUDE_CONVERTER_CONFIG
14137
+ converterConfig: this.constructor.getConverterConfig()
13966
14138
  });
13967
14139
  return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
13968
14140
  hooks,
@@ -13991,6 +14163,21 @@ var ClaudecodePluginHooks = class extends ClaudecodeHooks {
13991
14163
  isDeletable() {
13992
14164
  return true;
13993
14165
  }
14166
+ /**
14167
+ * Plugin hook scripts ship inside the plugin, so their commands must resolve
14168
+ * against the plugin install directory rather than the consumer's project
14169
+ * root. Upstream documents `"${CLAUDE_PLUGIN_ROOT}"/scripts/format-code.sh`;
14170
+ * `$CLAUDE_PROJECT_DIR` would expand to a path in the consumer's own repo,
14171
+ * where the bundled script does not exist.
14172
+ *
14173
+ * @see https://code.claude.com/docs/en/plugins-reference
14174
+ */
14175
+ static getConverterConfig() {
14176
+ return {
14177
+ ...super.getConverterConfig(),
14178
+ projectDirVar: "$CLAUDE_PLUGIN_ROOT"
14179
+ };
14180
+ }
13994
14181
  static getSettablePaths() {
13995
14182
  return {
13996
14183
  relativeDirPath: CLAUDECODE_PLUGIN_HOOKS_DIR,
@@ -14013,6 +14200,10 @@ const CODEXCLI_CONVERTER_CONFIG = {
14013
14200
  }, {
14014
14201
  canonical: "statusMessage",
14015
14202
  tool: "statusMessage"
14203
+ }],
14204
+ numberPassthroughFields: [{
14205
+ canonical: "additionalContextLimit",
14206
+ tool: "additionalContextLimit"
14016
14207
  }]
14017
14208
  };
14018
14209
  /**
@@ -15056,7 +15247,13 @@ const FACTORYDROID_CONVERTER_CONFIG = {
15056
15247
  toolToCanonicalEventNames: FACTORYDROID_TO_CANONICAL_EVENT_NAMES,
15057
15248
  projectDirVar: "$FACTORY_PROJECT_DIR",
15058
15249
  prefixDotRelativeCommandsOnly: true,
15059
- supportedHookTypes: /* @__PURE__ */ new Set(["command"])
15250
+ supportedHookTypes: /* @__PURE__ */ new Set(["command"]),
15251
+ groupPassthroughFields: [{
15252
+ canonical: "commandRegex",
15253
+ tool: "commandRegex",
15254
+ valueType: "string",
15255
+ subdividesGroup: true
15256
+ }]
15060
15257
  };
15061
15258
  var FactorydroidHooks = class FactorydroidHooks extends ToolHooks {
15062
15259
  constructor(params) {
@@ -15152,7 +15349,8 @@ const GOOSE_CONVERTER_CONFIG = {
15152
15349
  canonicalToToolEventNames: CANONICAL_TO_GOOSE_EVENT_NAMES,
15153
15350
  toolToCanonicalEventNames: GOOSE_TO_CANONICAL_EVENT_NAMES,
15154
15351
  projectDirVar: "",
15155
- supportedHookTypes: /* @__PURE__ */ new Set(["command"])
15352
+ supportedHookTypes: /* @__PURE__ */ new Set(["command"]),
15353
+ wildcardMatcherMeansAll: true
15156
15354
  };
15157
15355
  /**
15158
15356
  * Represents a Goose lifecycle hooks file.
@@ -15247,6 +15445,11 @@ const GROKCLI_CONVERTER_CONFIG = {
15247
15445
  toolToCanonicalEventNames: GROKCLI_TO_CANONICAL_EVENT_NAMES,
15248
15446
  projectDirVar: "",
15249
15447
  supportedHookTypes: /* @__PURE__ */ new Set(["command", "http"]),
15448
+ recordPassthroughFields: [{
15449
+ canonical: "env",
15450
+ tool: "env",
15451
+ commandOnly: true
15452
+ }],
15250
15453
  noMatcherEvents: /* @__PURE__ */ new Set([
15251
15454
  "sessionStart",
15252
15455
  "sessionEnd",
@@ -21197,6 +21400,27 @@ function resolveGooseType(config, url) {
21197
21400
  return canonicalTransport(config) === "builtin" ? "builtin" : "stdio";
21198
21401
  }
21199
21402
  /**
21403
+ * The Goose extension types that carry an MCP server. Goose also documents
21404
+ * `builtin`, `platform`, `frontend` and `inline_python` extensions, which have
21405
+ * no canonical MCP counterpart: they name capabilities Goose provides itself
21406
+ * rather than a server rulesync could describe.
21407
+ */
21408
+ const GOOSE_MCP_EXTENSION_TYPES = /* @__PURE__ */ new Set([
21409
+ "stdio",
21410
+ "streamable_http",
21411
+ "sse"
21412
+ ]);
21413
+ /**
21414
+ * Resolves the Goose extension type of an existing `extensions:` entry the way
21415
+ * Goose itself reads it: the declared `type`, or the shape of the entry when
21416
+ * the key is absent.
21417
+ */
21418
+ function existingExtensionType(ext) {
21419
+ if (typeof ext.type === "string") return ext.type;
21420
+ if (typeof ext.cmd === "string") return "stdio";
21421
+ if (typeof ext.uri === "string") return "streamable_http";
21422
+ }
21423
+ /**
21200
21424
  * Resolves the canonical timeout for a server (`timeout` or `networkTimeout`).
21201
21425
  */
21202
21426
  function resolveGooseTimeout(config) {
@@ -21222,15 +21446,20 @@ function applyGooseStdioFields(ext, config) {
21222
21446
  /**
21223
21447
  * Converts a single rulesync canonical MCP server into a Goose `extensions:` entry.
21224
21448
  */
21225
- function convertServerToGooseExtension(name, config) {
21449
+ function convertServerToGooseExtension(name, config, logger) {
21226
21450
  const url = resolveGooseUrl(config);
21227
21451
  const gooseType = resolveGooseType(config, url);
21228
21452
  const ext = {
21229
21453
  name,
21230
21454
  type: gooseType
21231
21455
  };
21232
- if (gooseType === "stdio") applyGooseStdioFields(ext, config);
21233
- else if (gooseType === "sse" || gooseType === "streamable_http") {
21456
+ if (gooseType === "stdio") {
21457
+ applyGooseStdioFields(ext, config);
21458
+ if (typeof ext.cmd !== "string" || ext.cmd === "") {
21459
+ warnWithFallback(logger, `Goose extension "${name}" has no command to run; skipping it rather than writing a stdio extension Goose cannot start to ~/.config/goose/config.yaml.`);
21460
+ return;
21461
+ }
21462
+ } else if (gooseType === "sse" || gooseType === "streamable_http") {
21234
21463
  if (url !== void 0) ext.uri = url;
21235
21464
  if (isPlainObject$1(config.headers)) ext.headers = omitPrototypePollutionKeys(config.headers);
21236
21465
  }
@@ -21245,13 +21474,41 @@ function convertServerToGooseExtension(name, config) {
21245
21474
  * Goose uses a non-standard schema: `name`, `type` (`stdio` | `streamable_http`
21246
21475
  * | `sse` | `builtin`), `cmd`/`args`/`envs` for stdio, `uri`/`headers` for
21247
21476
  * remote, plus `enabled` and `timeout`.
21248
- */
21249
- function convertToGooseFormat(mcpServers) {
21250
- const extensions = {};
21477
+ *
21478
+ * `extensions:` is co-owned: alongside the MCP servers rulesync manages it also
21479
+ * holds Goose's own `builtin`/`platform`/`frontend`/`inline_python` extensions
21480
+ * (`developer`, `memory`, ...), which have no canonical MCP representation.
21481
+ * Those entries are carried over from `existingExtensions` untouched — removing
21482
+ * `developer` alone costs the agent its shell and text-editor tools. Only an
21483
+ * entry rulesync can positively identify as an MCP server is rulesync's to
21484
+ * replace, so a server deleted from `.rulesync/.mcp.json` is retracted (with a
21485
+ * warning naming it) while an entry of an unrecognized shape or a future
21486
+ * extension type is left alone rather than assumed to be ours.
21487
+ */
21488
+ function convertToGooseFormat({ mcpServers, existingExtensions, logger }) {
21489
+ const generated = {};
21251
21490
  for (const [name, config] of Object.entries(mcpServers)) {
21252
21491
  if (PROTOTYPE_POLLUTION_KEYS.has(name) || !isRecord(config)) continue;
21253
- extensions[name] = convertServerToGooseExtension(name, config);
21492
+ const ext = convertServerToGooseExtension(name, config, logger);
21493
+ if (ext !== void 0) generated[name] = ext;
21254
21494
  }
21495
+ const extensions = {};
21496
+ const retracted = [];
21497
+ for (const [name, ext] of Object.entries(existingExtensions)) {
21498
+ if (PROTOTYPE_POLLUTION_KEYS.has(name)) continue;
21499
+ const type = isRecord(ext) ? existingExtensionType(ext) : void 0;
21500
+ if (type !== void 0 && GOOSE_MCP_EXTENSION_TYPES.has(type)) {
21501
+ if (!Object.hasOwn(generated, name)) retracted.push(name);
21502
+ continue;
21503
+ }
21504
+ if (!Object.hasOwn(generated, name)) {
21505
+ extensions[name] = ext;
21506
+ continue;
21507
+ }
21508
+ if (generated[name]?.type !== type) warnWithFallback(logger, `Goose extension "${name}" already exists in config.yaml as a non-MCP extension; the MCP server of the same name replaces it.`);
21509
+ }
21510
+ if (retracted.length > 0) warnWithFallback(logger, `Removing MCP extension(s) ${retracted.map((name) => `"${name}"`).join(", ")} from ~/.config/goose/config.yaml: they are not in the generated rulesync MCP config. Import them first if they were added with \`goose configure\`.`);
21511
+ Object.assign(extensions, generated);
21255
21512
  return extensions;
21256
21513
  }
21257
21514
  /**
@@ -21262,16 +21519,27 @@ function convertToGooseFormat(mcpServers) {
21262
21519
  * so both `url` and the Claude-specific `httpUrl` alias come back as `url`; and
21263
21520
  * the `streamable_http` type maps back to canonical `http`. These are the
21264
21521
  * canonical/preferred forms, so re-generating produces an equivalent config.
21522
+ *
21523
+ * Non-MCP extension types (`builtin`, `platform`, `frontend`, `inline_python`)
21524
+ * are skipped: they describe capabilities Goose provides itself, and importing
21525
+ * one would strip the type that makes it work — a `builtin` entry came back as
21526
+ * a `stdio` extension with no `cmd` that Goose cannot start. They stay in `config.yaml`,
21527
+ * which generation preserves.
21265
21528
  */
21266
21529
  function convertFromGooseFormat(extensions) {
21267
21530
  const result = {};
21531
+ const skipped = [];
21268
21532
  for (const [name, ext] of Object.entries(extensions)) {
21269
21533
  if (PROTOTYPE_POLLUTION_KEYS.has(name) || !isRecord(ext)) continue;
21534
+ const type = existingExtensionType(ext);
21535
+ if (type === void 0 || !GOOSE_MCP_EXTENSION_TYPES.has(type)) {
21536
+ skipped.push(name);
21537
+ continue;
21538
+ }
21270
21539
  const server = {};
21271
- const type = typeof ext.type === "string" ? ext.type : void 0;
21272
21540
  if (type === "sse") server.type = "sse";
21273
21541
  else if (type === "streamable_http") server.type = "http";
21274
- else if (type === "stdio") server.type = "stdio";
21542
+ else server.type = "stdio";
21275
21543
  if (typeof ext.cmd === "string") server.command = ext.cmd;
21276
21544
  if (isStringArray$1(ext.args)) server.args = ext.args;
21277
21545
  if (isPlainObject$1(ext.envs)) server.env = omitPrototypePollutionKeys(ext.envs);
@@ -21281,6 +21549,7 @@ function convertFromGooseFormat(extensions) {
21281
21549
  if (typeof ext.timeout === "number") server.timeout = ext.timeout;
21282
21550
  result[name] = server;
21283
21551
  }
21552
+ if (skipped.length > 0) warnWithFallback(void 0, `Skipping ${skipped.length} non-MCP Goose extension(s) (${skipped.map((name) => `"${name}"`).join(", ")}): they describe capabilities Goose provides itself and have no rulesync representation.`);
21284
21553
  return result;
21285
21554
  }
21286
21555
  /**
@@ -21402,15 +21671,25 @@ var GooseMcp = class GooseMcp extends ToolMcp {
21402
21671
  global
21403
21672
  });
21404
21673
  }
21405
- const merged = {
21406
- ...parseGooseConfig(await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "", paths.relativeDirPath, paths.relativeFilePath),
21407
- extensions: convertToGooseFormat(rulesyncMcp.getMcpServers())
21408
- };
21674
+ const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
21675
+ const existingContent = await readFileContentOrNull(filePath) ?? "";
21676
+ const config = parseGooseConfig(existingContent, paths.relativeDirPath, paths.relativeFilePath);
21677
+ const existingExtensions = isRecord(config.extensions) ? config.extensions : {};
21409
21678
  return new GooseMcp({
21410
21679
  outputRoot,
21411
21680
  relativeDirPath: paths.relativeDirPath,
21412
21681
  relativeFilePath: paths.relativeFilePath,
21413
- fileContent: dump(merged),
21682
+ fileContent: applySharedConfigPatch({
21683
+ fileKey: sharedConfigFileKey(paths),
21684
+ feature: "mcp",
21685
+ existingContent,
21686
+ patch: { extensions: convertToGooseFormat({
21687
+ mcpServers: rulesyncMcp.getMcpServers(),
21688
+ existingExtensions,
21689
+ logger
21690
+ }) },
21691
+ filePath
21692
+ }),
21414
21693
  validate,
21415
21694
  global
21416
21695
  });
@@ -28119,7 +28398,8 @@ const FACTORYDROID_OVERRIDE_KEYS = [
28119
28398
  "interactionMode",
28120
28399
  "extraKnownMarketplaces",
28121
28400
  "enabledPlugins",
28122
- "hooksDisabled"
28401
+ "hooksDisabled",
28402
+ "disabledSkills"
28123
28403
  ];
28124
28404
  /**
28125
28405
  * Permissions adapter for Factory Droid.
@@ -32270,6 +32550,30 @@ const CANONICAL_TO_ZED_TOOL_NAMES = {
32270
32550
  webfetch: "fetch",
32271
32551
  websearch: "search_web"
32272
32552
  };
32553
+ /**
32554
+ * Canonical categories whose Zed tool is not permission-gated. Zed's gated list
32555
+ * is `terminal`, `edit_file`, `write_file`, `delete_path`, `move_path`,
32556
+ * `copy_path`, `create_directory`, `fetch`, `search_web` and `skill`; the
32557
+ * read-only tools (`read_file`, `grep`, `find_path`, `list_directory`) sit in
32558
+ * Zed's own `EXCLUDED_TOOLS` and never call `decide_permission_from_settings`,
32559
+ * so a `tools.<name>` entry for one is config Zed never consults. Zed's real
32560
+ * read-denial surface is `private_files`, which the ignore feature owns.
32561
+ *
32562
+ * @see https://zed.dev/docs/ai/tool-permissions#supported-tools
32563
+ */
32564
+ const ZED_EXCLUDED_CANONICAL_CATEGORIES = /* @__PURE__ */ new Set([
32565
+ "read",
32566
+ "grep",
32567
+ "glob"
32568
+ ]);
32569
+ /** The Zed-side spellings of the same tools, for a category that names one directly. */
32570
+ const ZED_EXCLUDED_TOOL_NAMES = /* @__PURE__ */ new Set([
32571
+ "read_file",
32572
+ "grep",
32573
+ "find_path",
32574
+ "list_directory"
32575
+ ]);
32576
+ const isZedExcludedCategory = (category) => ZED_EXCLUDED_CANONICAL_CATEGORIES.has(category) || ZED_EXCLUDED_TOOL_NAMES.has(toZedToolName(category));
32273
32577
  const ZED_TO_CANONICAL_TOOL_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_ZED_TOOL_NAMES).map(([k, v]) => [v, k]));
32274
32578
  function toZedToolName(canonical) {
32275
32579
  return CANONICAL_TO_ZED_TOOL_NAMES[canonical] ?? canonical;
@@ -32391,15 +32695,21 @@ var ZedPermissions = class ZedPermissions extends ToolPermissions {
32391
32695
  const existingTools = asRecord(toolPermissions.tools);
32392
32696
  let managedDefault;
32393
32697
  const managedTools = {};
32698
+ const excludedCategories = [];
32394
32699
  for (const [category, rules] of Object.entries(config.permission)) {
32395
32700
  if (category === "*") {
32396
32701
  for (const [pattern, action] of Object.entries(rules)) if (pattern === "*") managedDefault = CANONICAL_TO_ZED_ACTION[action];
32397
32702
  else logger?.warn(`Zed permissions: dropping the "*" category rule for pattern "${pattern}" — Zed's global tool-permission default takes no patterns; scope the rule to a tool category instead.`);
32398
32703
  continue;
32399
32704
  }
32705
+ if (isZedExcludedCategory(category)) {
32706
+ if (Object.values(rules).some((action) => action === "deny" || action === "ask")) excludedCategories.push(category);
32707
+ continue;
32708
+ }
32400
32709
  const tool = buildZedToolPermission(rules);
32401
32710
  if (tool) managedTools[toZedToolName(category)] = tool;
32402
32711
  }
32712
+ if (excludedCategories.length > 0) logger?.warn(`Zed permissions: dropping the ${excludedCategories.map((category) => `"${category}"`).join(", ")} ${excludedCategories.length === 1 ? "category" : "categories"} — Zed does not gate its read-only tools, so the entries would never be consulted. Zed's read-denial surface is \`private_files\`, which the ignore feature writes from \`.rulesync/.aiignore\`.`);
32403
32713
  const managedToolNames = new Set(Object.keys(managedTools));
32404
32714
  if ("*" in config.permission) managedToolNames.add("*");
32405
32715
  const preservedTools = Object.fromEntries(Object.entries(existingTools).filter(([toolName]) => !managedToolNames.has(toolName)));
@@ -33191,18 +33501,16 @@ var DirFeatureProcessor = class {
33191
33501
  })) dirHasChanges = true;
33192
33502
  }
33193
33503
  const otherFiles = aiDir.getOtherFiles();
33194
- const otherFileContents = [];
33195
33504
  for (const file of otherFiles) {
33196
- const contentWithNewline = addTrailingNewline(file.fileBuffer.toString("utf-8"));
33197
- otherFileContents.push(contentWithNewline);
33198
- if (!dirHasChanges) {
33199
- const filePath = join(dirPath, file.relativeFilePathToDirPath);
33200
- if (!fileContentsEquivalent({
33201
- filePath,
33202
- expected: contentWithNewline,
33203
- existing: await readFileContentOrNull(filePath)
33204
- })) dirHasChanges = true;
33205
- }
33505
+ if (dirHasChanges) break;
33506
+ const filePath = join(dirPath, file.relativeFilePathToDirPath);
33507
+ const existingBuffer = await readFileBufferOrNull(filePath);
33508
+ if (!companionFileContentsEquivalent({
33509
+ filePath,
33510
+ expected: file.fileBuffer,
33511
+ existing: existingBuffer,
33512
+ composed: file.composed
33513
+ })) dirHasChanges = true;
33206
33514
  }
33207
33515
  if (!dirHasChanges) continue;
33208
33516
  const relativeDir = aiDir.getRelativePathFromCwd();
@@ -33222,11 +33530,8 @@ var DirFeatureProcessor = class {
33222
33530
  await writeFileContent(join(dirPath, mainFile.name), mainFileContent);
33223
33531
  changedPaths.push(join(relativeDir, mainFile.name));
33224
33532
  }
33225
- for (const [i, file] of otherFiles.entries()) {
33226
- const filePath = join(dirPath, file.relativeFilePathToDirPath);
33227
- const content = otherFileContents[i];
33228
- if (content === void 0) throw new Error(`Internal error: content for file ${file.relativeFilePathToDirPath} is undefined. This indicates a synchronization issue between otherFiles and otherFileContents arrays.`);
33229
- await writeFileContent(filePath, content);
33533
+ for (const file of otherFiles) {
33534
+ await writeFileBuffer(join(dirPath, file.relativeFilePathToDirPath), file.fileBuffer);
33230
33535
  changedPaths.push(join(relativeDir, file.relativeFilePathToDirPath));
33231
33536
  }
33232
33537
  }
@@ -33952,6 +34257,39 @@ function buildClaudecodeSkillFrontmatter({ rulesyncFrontmatter, resolvedDisableM
33952
34257
  return frontmatter;
33953
34258
  }
33954
34259
  /**
34260
+ * Escapes the glob metacharacters in a directory path so it matches literally.
34261
+ * A real directory name may contain them — `app/[slug]` in a Next.js tree is
34262
+ * the common case, and unescaped `[slug]` reads as a bracket expression that
34263
+ * matches a different subtree (or nothing at all).
34264
+ *
34265
+ * @see https://code.claude.com/docs/en/memory
34266
+ */
34267
+ function escapeGlobLiteral(dirPath) {
34268
+ return dirPath.replaceAll(/[\\*?[\]{}()!]/g, "\\$&");
34269
+ }
34270
+ /**
34271
+ * Claude Code scopes a nested skill by its location: a skill living in
34272
+ * `apps/web/.claude/skills/deploy` only activates while working under
34273
+ * `apps/web`. rulesync generates every imported skill into the project-root
34274
+ * `.claude/skills/`, so on import that location-based scoping has to be
34275
+ * re-expressed as an explicit `paths` glob — otherwise the round-trip silently
34276
+ * promotes a subtree skill to global activation.
34277
+ *
34278
+ * Returns the derived glob for a nested discovery root, or `undefined` for the
34279
+ * project-root `.claude/skills` (and for any root whose subtree cannot be
34280
+ * determined), where no scoping is implied.
34281
+ *
34282
+ * @see https://code.claude.com/docs/en/skills
34283
+ */
34284
+ function deriveNestedSkillPaths(relativeDirPath) {
34285
+ const posixDirPath = toPosixPath(relativeDirPath);
34286
+ const skillsDirSuffix = `/${toPosixPath(CLAUDECODE_SKILLS_DIR_PATH)}`;
34287
+ if (!posixDirPath.endsWith(skillsDirSuffix)) return;
34288
+ const subtree = posixDirPath.slice(0, -skillsDirSuffix.length);
34289
+ if (subtree === "" || subtree === ".") return;
34290
+ return [`${escapeGlobLiteral(subtree)}/**`];
34291
+ }
34292
+ /**
33955
34293
  * Represents a Claude Code skill directory.
33956
34294
  * Unlike subagents and commands, skills are directories containing SKILL.md and other files.
33957
34295
  * Extends ToolSkill to inherit directory management and security features from AiDir.
@@ -34039,6 +34377,7 @@ var ClaudecodeSkill = class extends ToolSkill {
34039
34377
  }
34040
34378
  toRulesyncSkill() {
34041
34379
  const frontmatter = this.getFrontmatter();
34380
+ const resolvedPaths = frontmatter.paths !== void 0 ? frontmatter.paths : deriveNestedSkillPaths(this.relativeDirPath);
34042
34381
  const claudecodeSection = {
34043
34382
  ...frontmatter.when_to_use && { when_to_use: frontmatter.when_to_use },
34044
34383
  ...frontmatter["allowed-tools"] && { "allowed-tools": frontmatter["allowed-tools"] },
@@ -34055,7 +34394,7 @@ var ClaudecodeSkill = class extends ToolSkill {
34055
34394
  ...frontmatter["disable-model-invocation"] !== void 0 && { "disable-model-invocation": frontmatter["disable-model-invocation"] },
34056
34395
  ...frontmatter["user-invocable"] !== void 0 && { "user-invocable": frontmatter["user-invocable"] },
34057
34396
  ...this.relativeDirPath === CLAUDECODE_SCHEDULED_TASKS_DIR_PATH && { "scheduled-task": true },
34058
- ...frontmatter.paths !== void 0 && { paths: frontmatter.paths }
34397
+ ...resolvedPaths !== void 0 && { paths: resolvedPaths }
34059
34398
  };
34060
34399
  const rulesyncFrontmatter = {
34061
34400
  name: frontmatter.name,
@@ -34453,7 +34792,8 @@ var CodexCliSkill = class CodexCliSkill extends ToolSkill {
34453
34792
  fileBuffer: Buffer.from(dump(openaiObject, {
34454
34793
  lineWidth: -1,
34455
34794
  noRefs: true
34456
- }))
34795
+ })),
34796
+ composed: true
34457
34797
  }] : baseOtherFiles;
34458
34798
  return new CodexCliSkill({
34459
34799
  outputRoot,
@@ -34513,7 +34853,11 @@ const CopilotSkillFrontmatterSchema = z.looseObject({
34513
34853
  name: z.string(),
34514
34854
  description: z.string(),
34515
34855
  license: z.optional(z.string()),
34516
- "allowed-tools": z.optional(z.union([z.string(), z.array(z.string())]))
34856
+ "allowed-tools": z.optional(z.union([z.string(), z.array(z.string())])),
34857
+ "argument-hint": z.optional(z.string()),
34858
+ "user-invocable": z.optional(z.boolean()),
34859
+ "disable-model-invocation": z.optional(z.boolean()),
34860
+ context: z.optional(z.string())
34517
34861
  });
34518
34862
  /**
34519
34863
  * Represents a GitHub Copilot skill directory.
@@ -34571,7 +34915,11 @@ var CopilotSkill = class CopilotSkill extends ToolSkill {
34571
34915
  const frontmatter = this.getFrontmatter();
34572
34916
  const copilotSection = {
34573
34917
  ...frontmatter.license !== void 0 && { license: frontmatter.license },
34574
- ...frontmatter["allowed-tools"] !== void 0 && { "allowed-tools": frontmatter["allowed-tools"] }
34918
+ ...frontmatter["allowed-tools"] !== void 0 && { "allowed-tools": frontmatter["allowed-tools"] },
34919
+ ...frontmatter["argument-hint"] !== void 0 && { "argument-hint": frontmatter["argument-hint"] },
34920
+ ...frontmatter["user-invocable"] !== void 0 && { "user-invocable": frontmatter["user-invocable"] },
34921
+ ...frontmatter["disable-model-invocation"] !== void 0 && { "disable-model-invocation": frontmatter["disable-model-invocation"] },
34922
+ ...frontmatter.context !== void 0 && { context: frontmatter.context }
34575
34923
  };
34576
34924
  const rulesyncFrontmatter = {
34577
34925
  name: frontmatter.name,
@@ -34593,11 +34941,24 @@ var CopilotSkill = class CopilotSkill extends ToolSkill {
34593
34941
  static fromRulesyncSkill({ outputRoot = process.cwd(), rulesyncSkill, validate = true, global = false }) {
34594
34942
  const settablePaths = CopilotSkill.getSettablePaths({ global });
34595
34943
  const rulesyncFrontmatter = rulesyncSkill.getFrontmatter();
34944
+ const copilotSection = rulesyncFrontmatter.copilot;
34945
+ const resolvedUserInvocable = resolveUserInvocable({
34946
+ rootFrontmatter: rulesyncFrontmatter,
34947
+ section: copilotSection
34948
+ });
34949
+ const resolvedDisableModelInvocation = resolveDisableModelInvocation({
34950
+ rootFrontmatter: rulesyncFrontmatter,
34951
+ section: copilotSection
34952
+ });
34596
34953
  const copilotFrontmatter = {
34597
34954
  name: rulesyncFrontmatter.name,
34598
34955
  description: rulesyncFrontmatter.description,
34599
- ...rulesyncFrontmatter.copilot?.license !== void 0 && { license: rulesyncFrontmatter.copilot.license },
34600
- ...rulesyncFrontmatter.copilot?.["allowed-tools"] !== void 0 && { "allowed-tools": rulesyncFrontmatter.copilot["allowed-tools"] }
34956
+ ...copilotSection?.license !== void 0 && { license: copilotSection.license },
34957
+ ...copilotSection?.["allowed-tools"] !== void 0 && { "allowed-tools": copilotSection["allowed-tools"] },
34958
+ ...copilotSection?.["argument-hint"] !== void 0 && { "argument-hint": copilotSection["argument-hint"] },
34959
+ ...resolvedUserInvocable !== void 0 && { "user-invocable": resolvedUserInvocable },
34960
+ ...resolvedDisableModelInvocation !== void 0 && { "disable-model-invocation": resolvedDisableModelInvocation },
34961
+ ...copilotSection?.context !== void 0 && { context: copilotSection.context }
34601
34962
  };
34602
34963
  return new CopilotSkill({
34603
34964
  outputRoot,
@@ -34744,14 +35105,23 @@ var CopilotcliSkill = class CopilotcliSkill extends ToolSkill {
34744
35105
  static fromRulesyncSkill({ outputRoot = process.cwd(), rulesyncSkill, validate = true, global = false }) {
34745
35106
  const settablePaths = CopilotcliSkill.getSettablePaths({ global });
34746
35107
  const rulesyncFrontmatter = rulesyncSkill.getFrontmatter();
35108
+ const copilotcliSection = rulesyncFrontmatter.copilotcli;
35109
+ const resolvedUserInvocable = resolveUserInvocable({
35110
+ rootFrontmatter: rulesyncFrontmatter,
35111
+ section: copilotcliSection
35112
+ });
35113
+ const resolvedDisableModelInvocation = resolveDisableModelInvocation({
35114
+ rootFrontmatter: rulesyncFrontmatter,
35115
+ section: copilotcliSection
35116
+ });
34747
35117
  const copilotcliFrontmatter = {
34748
35118
  name: rulesyncFrontmatter.name,
34749
35119
  description: rulesyncFrontmatter.description,
34750
- ...rulesyncFrontmatter.copilotcli?.license !== void 0 && { license: rulesyncFrontmatter.copilotcli.license },
34751
- ...rulesyncFrontmatter.copilotcli?.["allowed-tools"] !== void 0 && { "allowed-tools": rulesyncFrontmatter.copilotcli["allowed-tools"] },
34752
- ...rulesyncFrontmatter.copilotcli?.["argument-hint"] !== void 0 && { "argument-hint": rulesyncFrontmatter.copilotcli["argument-hint"] },
34753
- ...rulesyncFrontmatter.copilotcli?.["user-invocable"] !== void 0 && { "user-invocable": rulesyncFrontmatter.copilotcli["user-invocable"] },
34754
- ...rulesyncFrontmatter.copilotcli?.["disable-model-invocation"] !== void 0 && { "disable-model-invocation": rulesyncFrontmatter.copilotcli["disable-model-invocation"] }
35120
+ ...copilotcliSection?.license !== void 0 && { license: copilotcliSection.license },
35121
+ ...copilotcliSection?.["allowed-tools"] !== void 0 && { "allowed-tools": copilotcliSection["allowed-tools"] },
35122
+ ...copilotcliSection?.["argument-hint"] !== void 0 && { "argument-hint": copilotcliSection["argument-hint"] },
35123
+ ...resolvedUserInvocable !== void 0 && { "user-invocable": resolvedUserInvocable },
35124
+ ...resolvedDisableModelInvocation !== void 0 && { "disable-model-invocation": resolvedDisableModelInvocation }
34755
35125
  };
34756
35126
  return new CopilotcliSkill({
34757
35127
  outputRoot,
@@ -35309,7 +35679,9 @@ const FactorydroidSkillFrontmatterSchema = z.looseObject({
35309
35679
  name: z.string(),
35310
35680
  description: z.string(),
35311
35681
  "user-invocable": z.optional(z.boolean()),
35312
- "disable-model-invocation": z.optional(z.boolean())
35682
+ "disable-model-invocation": z.optional(z.boolean()),
35683
+ enabled: z.optional(z.boolean()),
35684
+ "allowed-tools": z.optional(z.union([z.string(), z.array(z.string())]))
35313
35685
  });
35314
35686
  /**
35315
35687
  * Represents a Factory Droid skill directory.
@@ -35365,7 +35737,9 @@ var FactorydroidSkill = class FactorydroidSkill extends ToolSkill {
35365
35737
  const frontmatter = this.getFrontmatter();
35366
35738
  const factorydroidBlock = {
35367
35739
  ...frontmatter["disable-model-invocation"] !== void 0 && { "disable-model-invocation": frontmatter["disable-model-invocation"] },
35368
- ...frontmatter["user-invocable"] !== void 0 && { "user-invocable": frontmatter["user-invocable"] }
35740
+ ...frontmatter["user-invocable"] !== void 0 && { "user-invocable": frontmatter["user-invocable"] },
35741
+ ...frontmatter.enabled !== void 0 && { enabled: frontmatter.enabled },
35742
+ ...frontmatter["allowed-tools"] !== void 0 && { "allowed-tools": frontmatter["allowed-tools"] }
35369
35743
  };
35370
35744
  const rulesyncFrontmatter = {
35371
35745
  name: frontmatter.name,
@@ -35387,19 +35761,22 @@ var FactorydroidSkill = class FactorydroidSkill extends ToolSkill {
35387
35761
  static fromRulesyncSkill({ outputRoot = process.cwd(), rulesyncSkill, validate = true, global = false }) {
35388
35762
  const settablePaths = FactorydroidSkill.getSettablePaths({ global });
35389
35763
  const rulesyncFrontmatter = rulesyncSkill.getFrontmatter();
35764
+ const factorydroidSection = rulesyncFrontmatter.factorydroid;
35390
35765
  const resolvedDisableModelInvocation = resolveDisableModelInvocation({
35391
35766
  rootFrontmatter: rulesyncFrontmatter,
35392
- section: rulesyncFrontmatter.factorydroid
35767
+ section: factorydroidSection
35393
35768
  });
35394
35769
  const resolvedUserInvocable = resolveUserInvocable({
35395
35770
  rootFrontmatter: rulesyncFrontmatter,
35396
- section: rulesyncFrontmatter.factorydroid
35771
+ section: factorydroidSection
35397
35772
  });
35398
35773
  const factorydroidFrontmatter = {
35399
35774
  name: rulesyncFrontmatter.name,
35400
35775
  description: rulesyncFrontmatter.description,
35401
35776
  ...resolvedDisableModelInvocation !== void 0 && { "disable-model-invocation": resolvedDisableModelInvocation },
35402
- ...resolvedUserInvocable !== void 0 && { "user-invocable": resolvedUserInvocable }
35777
+ ...resolvedUserInvocable !== void 0 && { "user-invocable": resolvedUserInvocable },
35778
+ ...factorydroidSection?.enabled !== void 0 && { enabled: factorydroidSection.enabled },
35779
+ ...factorydroidSection?.["allowed-tools"] !== void 0 && { "allowed-tools": factorydroidSection["allowed-tools"] }
35403
35780
  };
35404
35781
  return new FactorydroidSkill({
35405
35782
  outputRoot,
@@ -39420,7 +39797,17 @@ var ClaudecodeSubagent = class ClaudecodeSubagent extends ToolSubagent {
39420
39797
  validate: true
39421
39798
  });
39422
39799
  }
39423
- static fromRulesyncSubagent({ outputRoot = process.cwd(), rulesyncSubagent, validate = true, global = false }) {
39800
+ /**
39801
+ * Last chance to adjust the tool frontmatter before it is written. The base
39802
+ * implementation only warns about names Claude Code rejects; plugin-scoped
39803
+ * subclasses extend it to drop fields Claude Code refuses to honor for
39804
+ * plugin-shipped agents.
39805
+ */
39806
+ static sanitizeFrontmatter({ frontmatter, relativeFilePath, logger }) {
39807
+ if (frontmatter.name.includes(":")) logger?.warn(`Claude Code will reject the subagent in ${relativeFilePath}: the name "${frontmatter.name}" contains ":", which is reserved for plugin namespacing.`);
39808
+ return frontmatter;
39809
+ }
39810
+ static fromRulesyncSubagent({ outputRoot = process.cwd(), rulesyncSubagent, validate = true, global = false, logger }) {
39424
39811
  const rulesyncFrontmatter = rulesyncSubagent.getFrontmatter();
39425
39812
  const claudecodeSection = this.filterToolSpecificSection(rulesyncFrontmatter.claudecode ?? {}, ["name", "description"]);
39426
39813
  const rawClaudecodeFrontmatter = {
@@ -39430,7 +39817,11 @@ var ClaudecodeSubagent = class ClaudecodeSubagent extends ToolSubagent {
39430
39817
  };
39431
39818
  const result = ClaudecodeSubagentFrontmatterSchema.safeParse(rawClaudecodeFrontmatter);
39432
39819
  if (!result.success) throw new Error(`Invalid claudecode subagent frontmatter in ${rulesyncSubagent.getRelativeFilePath()}: ${formatError(result.error)}`);
39433
- const claudecodeFrontmatter = result.data;
39820
+ const claudecodeFrontmatter = this.sanitizeFrontmatter({
39821
+ frontmatter: result.data,
39822
+ relativeFilePath: rulesyncSubagent.getRelativeFilePath(),
39823
+ logger
39824
+ });
39434
39825
  const body = rulesyncSubagent.getBody();
39435
39826
  const fileContent = stringifyFrontmatter(body, claudecodeFrontmatter);
39436
39827
  const paths = this.getSettablePaths({ global });
@@ -39499,6 +39890,21 @@ var ClaudecodeSubagent = class ClaudecodeSubagent extends ToolSubagent {
39499
39890
  };
39500
39891
  //#endregion
39501
39892
  //#region src/features/subagents/claudecode-plugin-subagent.ts
39893
+ /**
39894
+ * Claude Code refuses these for plugin-shipped agents "for security reasons",
39895
+ * so emitting them leaves the author believing the agent is constrained when it
39896
+ * is not. Only these three are dropped: the other fields upstream does not list
39897
+ * (e.g. `color`) are merely ignored, with no misleading security posture.
39898
+ *
39899
+ * @see https://code.claude.com/docs/en/plugins-reference
39900
+ */
39901
+ const PLUGIN_FORBIDDEN_FIELDS = [
39902
+ "hooks",
39903
+ "mcpServers",
39904
+ "permissionMode"
39905
+ ];
39906
+ /** The only `isolation` value plugin agents accept. */
39907
+ const PLUGIN_ISOLATION_VALUE = "worktree";
39502
39908
  var ClaudecodePluginSubagent = class extends ClaudecodeSubagent {
39503
39909
  static isTargetedByRulesyncSubagent(rulesyncSubagent) {
39504
39910
  const targets = rulesyncSubagent.getFrontmatter().targets;
@@ -39507,6 +39913,21 @@ var ClaudecodePluginSubagent = class extends ClaudecodeSubagent {
39507
39913
  static getSettablePaths() {
39508
39914
  return { relativeDirPath: CLAUDECODE_PLUGIN_AGENTS_DIR };
39509
39915
  }
39916
+ static sanitizeFrontmatter({ frontmatter, relativeFilePath, logger }) {
39917
+ const sanitized = { ...super.sanitizeFrontmatter({
39918
+ frontmatter,
39919
+ relativeFilePath,
39920
+ logger
39921
+ }) };
39922
+ const dropped = PLUGIN_FORBIDDEN_FIELDS.filter((field) => sanitized[field] !== void 0);
39923
+ for (const field of PLUGIN_FORBIDDEN_FIELDS) delete sanitized[field];
39924
+ if (dropped.length > 0) logger?.warn(`Dropping ${dropped.join(", ")} from claudecode-plugin subagent ${relativeFilePath}: Claude Code does not support these fields for plugin-shipped agents.`);
39925
+ if (sanitized.isolation !== void 0 && sanitized.isolation !== PLUGIN_ISOLATION_VALUE) {
39926
+ logger?.warn(`Dropping isolation "${sanitized.isolation}" from claudecode-plugin subagent ${relativeFilePath}: "${PLUGIN_ISOLATION_VALUE}" is the only value Claude Code accepts for plugin-shipped agents.`);
39927
+ delete sanitized.isolation;
39928
+ }
39929
+ return sanitized;
39930
+ }
39510
39931
  };
39511
39932
  //#endregion
39512
39933
  //#region src/features/subagents/cline-subagent.ts
@@ -43160,7 +43581,8 @@ var SubagentsProcessor = class extends FeatureProcessor {
43160
43581
  outputRoot: this.outputRoot,
43161
43582
  relativeDirPath: RulesyncSubagent.getSettablePaths().relativeDirPath,
43162
43583
  rulesyncSubagent,
43163
- global: this.global
43584
+ global: this.global,
43585
+ logger: this.logger
43164
43586
  }));
43165
43587
  }
43166
43588
  async convertToolFilesToRulesyncFiles(toolFiles) {
@@ -45113,7 +45535,7 @@ var CodexcliRule = class CodexcliRule extends ToolRule {
45113
45535
  };
45114
45536
  //#endregion
45115
45537
  //#region src/features/rules/copilot-rule.ts
45116
- const CopilotRuleFrontmatterSchema = z.object({
45538
+ const CopilotRuleFrontmatterSchema = z.looseObject({
45117
45539
  description: z.optional(z.string()),
45118
45540
  applyTo: z.optional(z.string()),
45119
45541
  name: z.optional(z.string()),
@@ -45169,15 +45591,13 @@ var CopilotRule = class CopilotRule extends ToolRule {
45169
45591
  toRulesyncRule() {
45170
45592
  let globs;
45171
45593
  if (this.frontmatter.applyTo) globs = this.frontmatter.applyTo.split(",").map((g) => g.trim());
45594
+ const { description, applyTo: _applyTo, ...copilotFields } = this.frontmatter;
45172
45595
  const rulesyncFrontmatter = {
45173
45596
  targets: ["*"],
45174
45597
  root: this.isRoot(),
45175
- description: this.frontmatter.description,
45598
+ description,
45176
45599
  globs,
45177
- ...(this.frontmatter.excludeAgent || this.frontmatter.name) && { copilot: {
45178
- ...this.frontmatter.excludeAgent && { excludeAgent: this.frontmatter.excludeAgent },
45179
- ...this.frontmatter.name && { name: this.frontmatter.name }
45180
- } }
45600
+ ...Object.keys(copilotFields).length > 0 && { copilot: copilotFields }
45181
45601
  };
45182
45602
  const relativeFilePath = this.getRelativeFilePath().replace(/\.instructions\.md$/, ".md");
45183
45603
  return new RulesyncRule({
@@ -45194,10 +45614,9 @@ var CopilotRule = class CopilotRule extends ToolRule {
45194
45614
  const root = rulesyncFrontmatter.root;
45195
45615
  const paths = this.getSettablePaths({ global });
45196
45616
  const copilotFrontmatter = {
45617
+ ...rulesyncFrontmatter.copilot,
45197
45618
  description: rulesyncFrontmatter.description,
45198
- applyTo: rulesyncFrontmatter.globs?.length ? rulesyncFrontmatter.globs.join(",") : void 0,
45199
- excludeAgent: rulesyncFrontmatter.copilot?.excludeAgent,
45200
- name: rulesyncFrontmatter.copilot?.name
45619
+ applyTo: rulesyncFrontmatter.globs?.length ? rulesyncFrontmatter.globs.join(",") : void 0
45201
45620
  };
45202
45621
  const body = rulesyncRule.getBody();
45203
45622
  if (root) return new CopilotRule({
@@ -50894,6 +51313,6 @@ async function importChecksCore(params) {
50894
51313
  return writtenCount;
50895
51314
  }
50896
51315
  //#endregion
50897
- export { JsonLogger as $, RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH as $t, RulesyncRuleFrontmatterSchema as A, PACKAGING_TOOL_TARGETS as At, RulesyncCheck as B, RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH as Bt, CODEXCLI_DIR as C, removeTempDirectory as Ct, RulesyncSkill as D, writeFileContent as Dt, RulesyncSubagentFrontmatterSchema as E, toPosixPath as Et, getRulesyncSourceCandidates as F, RULESYNC_CHECKS_RELATIVE_DIR_PATH as Ft, SKILL_FILE_NAME as G, RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH as Gt, stringifyFrontmatter as H, RULESYNC_HOOKS_LEGACY_FILE_NAME as Ht, resolveRulesyncSourceWritePath as I, RULESYNC_COMMANDS_RELATIVE_DIR_PATH as It, ConfigFileSchema as J, RULESYNC_MCP_RELATIVE_FILE_PATH as Jt, ConfigResolver as K, RULESYNC_MCP_FILE_NAME as Kt, parseJsonc as L, RULESYNC_CONFIG_RELATIVE_FILE_PATH as Lt, RulesyncMcp as M, MAX_FILE_SIZE as Mt, RulesyncIgnore as N, RULESYNC_AIIGNORE_FILE_NAME as Nt, RulesyncSkillFrontmatterSchema as O, ALL_TOOL_TARGETS as Ot, RulesyncHooks as P, RULESYNC_AIIGNORE_RELATIVE_FILE_PATH as Pt, ConsoleLogger as Q, RULESYNC_PERMISSIONS_LEGACY_FILE_NAME as Qt, RulesyncCommand as R, RULESYNC_CONFIG_SCHEMA_URL as Rt, CODEXCLI_BASH_RULES_FILE_NAME as S, removeFileStrict as St, RulesyncSubagent as T, runWithDirectoryRollback as Tt, loadYaml as U, RULESYNC_HOOKS_RELATIVE_FILE_PATH as Ut, RulesyncCheckFrontmatterSchema as V, RULESYNC_HOOKS_FILE_NAME as Vt, SHARED_USER_MANAGED_CONFIG_PATHS as W, RULESYNC_IGNORE_RELATIVE_FILE_PATH as Wt, SourceEntrySchema as X, RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH as Xt, GITIGNORE_DESTINATION_KEY as Y, RULESYNC_MCP_SCHEMA_URL as Yt, findControlCharacter as Z, RULESYNC_PERMISSIONS_FILE_NAME as Zt, CLAUDECODE_LOCAL_RULE_FILE_NAME as _, readFileContent as _t, convertFromTool as a, RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH as an, assertTreeContainsNoSymlinks as at, CLAUDECODE_SKILLS_DIR_PATH as b, removeDirectoryStrict as bt, SubagentsProcessor as c, DEPRECATED_FEATURE_REPLACEMENTS as cn, createTempDirectory as ct, IgnoreProcessor as d, fileExists as dt, RULESYNC_PERMISSIONS_SCHEMA_URL as en, fallbackLogger as et, HooksProcessor as f, findFilesByGlobs as ft, CLAUDECODE_DIR as g, listDirectoryFiles as gt, QWENCODE_LOCAL_RULE_FILE_NAME as h, isSymlink as ht, getProcessorRegistryEntry as i, RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH as in, assertDirectoryIfExists as it, RulesyncPermissions as j, ToolTargetSchema as jt, RulesyncRule as k, ALL_TOOL_TARGETS_WITH_WILDCARD as kt, SkillsProcessor as l, formatError as ln, directoryExists as lt, QWENCODE_DIR as m, getHomeDirectory as mt, checkRulesyncDirExists as n, RULESYNC_RULES_RELATIVE_DIR_PATH as nn, CLIError as nt, isPackagingToolTarget as o, ALL_FEATURES as on, assertWritablePathInsideRoot as ot, CommandsProcessor as p, getFileSize as pt, CONFLICTING_TARGET_PAIRS as q, RULESYNC_MCP_LEGACY_FILE_NAME as qt, generate as r, RULESYNC_SKILLS_RELATIVE_DIR_PATH as rn, ErrorCodes as rt, RulesProcessor as s, ALL_FEATURES_WITH_WILDCARD as sn, checkPathTraversal as st, importFromTool as t, RULESYNC_RELATIVE_DIR_PATH as tn, warnOnConflictingFlags as tt, McpProcessor as u, ensureDir as ut, CLAUDECODE_MEMORIES_DIR_NAME as v, readFileContentOrNull as vt, getLocalSkillDirNames as w, resolvePath as wt, ChecksProcessor as x, removeFile as xt, CLAUDECODE_SETTINGS_LOCAL_FILE_NAME as y, removeDirectory as yt, RulesyncCommandFrontmatterSchema as z, RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH as zt };
51316
+ export { JsonLogger as $, RULESYNC_PERMISSIONS_LEGACY_FILE_NAME as $t, RulesyncRuleFrontmatterSchema as A, ALL_TOOL_TARGETS_WITH_WILDCARD as At, RulesyncCheck as B, RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH as Bt, CODEXCLI_DIR as C, removeTempDirectory as Ct, RulesyncSkill as D, writeFileBuffer as Dt, RulesyncSubagentFrontmatterSchema as E, toPosixPath as Et, getRulesyncSourceCandidates as F, RULESYNC_AIIGNORE_RELATIVE_FILE_PATH as Ft, SKILL_FILE_NAME as G, RULESYNC_IGNORE_RELATIVE_FILE_PATH as Gt, stringifyFrontmatter as H, RULESYNC_HOOKS_FILE_NAME as Ht, resolveRulesyncSourceWritePath as I, RULESYNC_CHECKS_RELATIVE_DIR_PATH as It, ConfigFileSchema as J, RULESYNC_MCP_LEGACY_FILE_NAME as Jt, ConfigResolver as K, RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH as Kt, parseJsonc as L, RULESYNC_COMMANDS_RELATIVE_DIR_PATH as Lt, RulesyncMcp as M, ToolTargetSchema as Mt, RulesyncIgnore as N, MAX_FILE_SIZE as Nt, RulesyncSkillFrontmatterSchema as O, writeFileContent as Ot, RulesyncHooks as P, RULESYNC_AIIGNORE_FILE_NAME as Pt, ConsoleLogger as Q, RULESYNC_PERMISSIONS_FILE_NAME as Qt, RulesyncCommand as R, RULESYNC_CONFIG_RELATIVE_FILE_PATH as Rt, CODEXCLI_BASH_RULES_FILE_NAME as S, removeFileStrict as St, RulesyncSubagent as T, runWithDirectoryRollback as Tt, loadYaml as U, RULESYNC_HOOKS_LEGACY_FILE_NAME as Ut, RulesyncCheckFrontmatterSchema as V, RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH as Vt, SHARED_USER_MANAGED_CONFIG_PATHS as W, RULESYNC_HOOKS_RELATIVE_FILE_PATH as Wt, SourceEntrySchema as X, RULESYNC_MCP_SCHEMA_URL as Xt, GITIGNORE_DESTINATION_KEY as Y, RULESYNC_MCP_RELATIVE_FILE_PATH as Yt, findControlCharacter as Z, RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH as Zt, CLAUDECODE_LOCAL_RULE_FILE_NAME as _, readFileContent as _t, convertFromTool as a, RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH as an, assertTreeContainsNoSymlinks as at, CLAUDECODE_SKILLS_DIR_PATH as b, removeDirectoryStrict as bt, SubagentsProcessor as c, ALL_FEATURES_WITH_WILDCARD as cn, createTempDirectory as ct, IgnoreProcessor as d, fileExists as dt, RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH as en, fallbackLogger as et, HooksProcessor as f, findFilesByGlobs as ft, CLAUDECODE_DIR as g, listDirectoryFiles as gt, QWENCODE_LOCAL_RULE_FILE_NAME as h, isSymlink as ht, getProcessorRegistryEntry as i, RULESYNC_SKILLS_RELATIVE_DIR_PATH as in, assertDirectoryIfExists as it, RulesyncPermissions as j, PACKAGING_TOOL_TARGETS as jt, RulesyncRule as k, ALL_TOOL_TARGETS as kt, SkillsProcessor as l, DEPRECATED_FEATURE_REPLACEMENTS as ln, directoryExists as lt, QWENCODE_DIR as m, getHomeDirectory as mt, checkRulesyncDirExists as n, RULESYNC_RELATIVE_DIR_PATH as nn, CLIError as nt, isPackagingToolTarget as o, RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH as on, assertWritablePathInsideRoot as ot, CommandsProcessor as p, getFileSize as pt, CONFLICTING_TARGET_PAIRS as q, RULESYNC_MCP_FILE_NAME as qt, generate as r, RULESYNC_RULES_RELATIVE_DIR_PATH as rn, ErrorCodes as rt, RulesProcessor as s, ALL_FEATURES as sn, checkPathTraversal as st, importFromTool as t, RULESYNC_PERMISSIONS_SCHEMA_URL as tn, warnOnConflictingFlags as tt, McpProcessor as u, formatError as un, ensureDir as ut, CLAUDECODE_MEMORIES_DIR_NAME as v, readFileContentOrNull as vt, getLocalSkillDirNames as w, resolvePath as wt, ChecksProcessor as x, removeFile as xt, CLAUDECODE_SETTINGS_LOCAL_FILE_NAME as y, removeDirectory as yt, RulesyncCommandFrontmatterSchema as z, RULESYNC_CONFIG_SCHEMA_URL as zt };
50898
51317
 
50899
- //# sourceMappingURL=import-BlwypG9v.js.map
51318
+ //# sourceMappingURL=import-zyfw1Cq_.js.map