rulesync 32.0.0 → 34.0.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.
@@ -386,7 +386,11 @@ const DeprecatedIgnoreFeatureSchema = z.literal("ignore").check(meta({
386
386
  const FeatureSchema = z.union([z.enum(ACTIVE_FEATURES), DeprecatedIgnoreFeatureSchema]);
387
387
  z.array(FeatureSchema);
388
388
  const FeatureWithWildcardSchema = z.union([z.enum([...ACTIVE_FEATURES, "*"]), DeprecatedIgnoreFeatureSchema]);
389
- const GitignoreDestinationSchema = z.enum(["gitignore", "gitattributes"]);
389
+ const GitignoreDestinationSchema = z.enum([
390
+ "gitignore",
391
+ "gitattributes",
392
+ "gitexclude"
393
+ ]);
390
394
  const FlattenedCommandNamingSchema = z.enum(["basename", "path"]);
391
395
  const FeatureOptionsSchema = z.record(z.string(), z.unknown());
392
396
  const FeatureValueSchema = z.union([
@@ -506,6 +510,7 @@ const rulesProcessorToolTargetTuple = [
506
510
  "claudecode-legacy",
507
511
  "cline",
508
512
  "codebuddy",
513
+ "codebuff",
509
514
  "codewhale",
510
515
  "codexcli",
511
516
  "commandcode",
@@ -1385,6 +1390,13 @@ async function getFileSize(filepath) {
1385
1390
  throw new Error(`Failed to get file size for "${filepath}": ${formatError(error)}`, { cause: error });
1386
1391
  }
1387
1392
  }
1393
+ /**
1394
+ * Whether a file carries any execute bit. Windows reports none, so a file
1395
+ * there is never seen as executable.
1396
+ */
1397
+ async function hasExecutableBit(filepath) {
1398
+ return ((await stat(filepath)).mode & 73) !== 0;
1399
+ }
1388
1400
  async function isSymlink(filepath) {
1389
1401
  try {
1390
1402
  return (await lstat(filepath)).isSymbolicLink();
@@ -3157,6 +3169,9 @@ var Config = class Config {
3157
3169
  const featureValue = value[feature];
3158
3170
  if (featureValue && typeof featureValue === "object" && isFeatureValueEnabled(featureValue)) return featureValue;
3159
3171
  }
3172
+ getRootGitignoreDestination() {
3173
+ return this.gitignoreDestination;
3174
+ }
3160
3175
  getGitignoreDestination(target, feature) {
3161
3176
  const rootLevel = this.gitignoreDestination;
3162
3177
  if (!isRulesyncConfigTargetsObject(this.targets)) return rootLevel;
@@ -3174,7 +3189,8 @@ var Config = class Config {
3174
3189
  return toolLevel ?? rootLevel;
3175
3190
  }
3176
3191
  static parseGitignoreDestination(value) {
3177
- if (value === "gitignore" || value === "gitattributes") return value;
3192
+ const result = GitignoreDestinationSchema.safeParse(value);
3193
+ return result.success ? result.data : void 0;
3178
3194
  }
3179
3195
  /**
3180
3196
  * Check if per-target features configuration is being used.
@@ -5244,12 +5260,20 @@ const JUNIE_HOOK_EVENTS = [
5244
5260
  "sessionEnd"
5245
5261
  ];
5246
5262
  /**
5247
- * Hook events supported by the GitLab Duo CLI. Only `SessionStart` is
5248
- * documented; its optional regex matcher filters on the session source
5249
- * (`startup`, `resume`). Only `type: "command"` hooks are supported.
5263
+ * Hook events supported by the GitLab Duo CLI. `SessionStart` is documented;
5264
+ * `PreToolUse` and `SessionEnd` ship in gitlab-lsp (`HOOK_EVENT_NAMES`, v9.28.0+)
5265
+ * but are not on docs.gitlab.com yet. The optional regex matcher filters on the
5266
+ * session source (`startup`, `resume`) for `SessionStart`, on `tool_name` for
5267
+ * `PreToolUse`, and on the end reason for `SessionEnd`. Only `type: "command"`
5268
+ * hooks are supported.
5250
5269
  * @see https://docs.gitlab.com/user/gitlab_duo_cli/customize/
5270
+ * @see https://gitlab.com/gitlab-org/editor-extensions/gitlab-lsp/-/blob/main/packages/core/hooks/src/schema.ts
5251
5271
  */
5252
- const GITLABDUO_HOOK_EVENTS = ["sessionStart"];
5272
+ const GITLABDUO_HOOK_EVENTS = [
5273
+ "sessionStart",
5274
+ "preToolUse",
5275
+ "sessionEnd"
5276
+ ];
5253
5277
  /**
5254
5278
  * Hook events supported by Qwen Code.
5255
5279
  *
@@ -5590,7 +5614,10 @@ const CORTEXCODE_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CA
5590
5614
  * or `agent` hooks with a `timeout` in seconds. The hooks reference documents ten events; the
5591
5615
  * plugins reference lists the full set, stating that plugin hooks "respond to
5592
5616
  * the same lifecycle events as user-defined hooks", so every event listed
5593
- * there is supported here.
5617
+ * there is supported here. The hooks reference also names `Setup`
5618
+ * (bootstrap/maintenance), and the published package's settings schema
5619
+ * (`@tencent-ai/codebuddy-code` 2.167.0) accepts `UserPromptExpansion`, whose
5620
+ * matcher filters on `command_name` as in Claude Code.
5594
5621
  *
5595
5622
  * @see https://www.codebuddy.ai/docs/cli/hooks
5596
5623
  * @see https://www.codebuddy.ai/docs/cli/plugins-reference
@@ -5621,7 +5648,9 @@ const CODEBUDDY_HOOK_EVENTS = [
5621
5648
  "postCompact",
5622
5649
  "elicitation",
5623
5650
  "elicitationResult",
5624
- "sessionEnd"
5651
+ "sessionEnd",
5652
+ "setup",
5653
+ "userPromptExpansion"
5625
5654
  ];
5626
5655
  /**
5627
5656
  * CodeBuddy Code events that fire on every occurrence and take no `matcher`.
@@ -5670,7 +5699,9 @@ const CANONICAL_TO_CODEBUDDY_EVENT_NAMES = {
5670
5699
  postCompact: "PostCompact",
5671
5700
  elicitation: "Elicitation",
5672
5701
  elicitationResult: "ElicitationResult",
5673
- sessionEnd: "SessionEnd"
5702
+ sessionEnd: "SessionEnd",
5703
+ setup: "Setup",
5704
+ userPromptExpansion: "UserPromptExpansion"
5674
5705
  };
5675
5706
  const CODEBUDDY_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_CODEBUDDY_EVENT_NAMES).map(([k, v]) => [v, k]));
5676
5707
  /**
@@ -6524,7 +6555,11 @@ const KIRO_IDE_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANO
6524
6555
  /**
6525
6556
  * Map canonical camelCase event names to GitLab Duo CLI PascalCase.
6526
6557
  */
6527
- const CANONICAL_TO_GITLABDUO_EVENT_NAMES = { sessionStart: "SessionStart" };
6558
+ const CANONICAL_TO_GITLABDUO_EVENT_NAMES = {
6559
+ sessionStart: "SessionStart",
6560
+ preToolUse: "PreToolUse",
6561
+ sessionEnd: "SessionEnd"
6562
+ };
6528
6563
  /**
6529
6564
  * Map GitLab Duo CLI PascalCase event names to canonical camelCase.
6530
6565
  */
@@ -8265,6 +8300,48 @@ const ReasonixPermissionsOverrideSchema = z.looseObject({
8265
8300
  ]))
8266
8301
  });
8267
8302
  /**
8303
+ * Tool-scoped override block for Qoder. `outputStyle` is written to the
8304
+ * top-level `outputStyle` key of `.qoder/settings.json` (project) /
8305
+ * `~/.qoder/settings.json` (global): the name of the active output style, a
8306
+ * custom style from `.qoder/output-styles/` (see the `qoder.channel:
8307
+ * output-style` rule option) or a `plugin-name:style-name` reference. The
8308
+ * shared `permission` block continues to drive `permissions.allow`/`ask`/`deny`.
8309
+ *
8310
+ * @see https://docs.qoder.com/cli/output-styles
8311
+ * @see https://docs.qoder.com/cli/settings-reference
8312
+ * @example
8313
+ * { "outputStyle": "concise-review" }
8314
+ */
8315
+ const QoderPermissionsOverrideSchema = z.looseObject({
8316
+ permission: z.optional(ToolScopedPermissionSchema),
8317
+ outputStyle: z.optional(z.string().check(z.trim(), z.minLength(1)))
8318
+ });
8319
+ /**
8320
+ * One native Factory Droid permission rule. `match.prefix` positions are either
8321
+ * a single token or a non-empty list of alternative tokens.
8322
+ *
8323
+ * @see https://docs.factory.com/autonomy-and-safety/permission-rules
8324
+ */
8325
+ const FactorydroidPermissionRuleSchema = z.looseObject({
8326
+ id: z.string(),
8327
+ decision: z.enum([
8328
+ "allow",
8329
+ "ask",
8330
+ "block"
8331
+ ]),
8332
+ match: z.looseObject({ prefix: z.optional(z.array(z.union([z.string(), z.array(z.string()).check(z.minLength(1))])).check(z.minLength(1))) }),
8333
+ tests: z.optional(z.looseObject({
8334
+ match: z.optional(z.array(z.string())),
8335
+ noMatch: z.optional(z.array(z.string()))
8336
+ })),
8337
+ reason: z.optional(z.string()),
8338
+ enabled: z.optional(z.boolean())
8339
+ });
8340
+ const FactorydroidPermissionRulesSchema = z.looseObject({
8341
+ version: z.number(),
8342
+ rules: z.array(FactorydroidPermissionRuleSchema)
8343
+ });
8344
+ /**
8268
8345
  * Tool-scoped override block for Factory Droid. Factory Droid's `settings.json`
8269
8346
  * exposes security controls with no canonical per-command allow/ask/deny slot —
8270
8347
  * `commandBlocklist` (a hard-block tier that can never be approved, distinct from
@@ -8272,7 +8349,8 @@ const ReasonixPermissionsOverrideSchema = z.looseObject({
8272
8349
  * (`enabled`/`mode`/`filesystem`/`network`), `mcpPolicy`,
8273
8350
  * `mcpAutonomyOverrides` (per-MCP-tool autonomy levels), `enableDroidShield`,
8274
8351
  * and autonomy settings (`sessionDefaultSettings`, `maxAutonomyLevel`,
8275
- * `subagentAutonomyLevel`, `interactionMode`). Fields placed here are merged
8352
+ * `subagentAutonomyLevel`, `interactionMode`), and the native versioned
8353
+ * `permissionRules` (allow/ask/block). Fields placed here are merged
8276
8354
  * into `settings.json` and
8277
8355
  * emitted only for Factory Droid, while the shared `permission` block continues
8278
8356
  * to drive `commandAllowlist`/`commandDenylist`. Kept `looseObject` passthrough.
@@ -8283,7 +8361,8 @@ const ReasonixPermissionsOverrideSchema = z.looseObject({
8283
8361
  const FactorydroidPermissionsOverrideSchema = z.looseObject({
8284
8362
  permission: z.optional(ToolScopedPermissionSchema),
8285
8363
  commandBlocklist: z.optional(z.array(z.string())),
8286
- disabledSkills: z.optional(z.array(z.string()))
8364
+ disabledSkills: z.optional(z.array(z.string())),
8365
+ permissionRules: z.optional(FactorydroidPermissionRulesSchema)
8287
8366
  });
8288
8367
  /**
8289
8368
  * Tool-scoped override block for Warp. Warp's `[agents.profiles]` table exposes
@@ -9088,6 +9167,7 @@ const PermissionsConfigSchema = z.looseObject({
9088
9167
  codewhale: z.optional(CodewhalePermissionsOverrideSchema),
9089
9168
  zed: z.optional(ZedPermissionsOverrideSchema),
9090
9169
  devin: z.optional(DevinPermissionsOverrideSchema),
9170
+ qoder: z.optional(QoderPermissionsOverrideSchema),
9091
9171
  "antigravity-ide": z.optional(CanonicalPermissionsOverrideSchema),
9092
9172
  continue: z.optional(CanonicalPermissionsOverrideSchema),
9093
9173
  copilot: z.optional(CopilotPermissionsOverrideSchema),
@@ -9099,7 +9179,6 @@ const PermissionsConfigSchema = z.looseObject({
9099
9179
  goose: z.optional(CanonicalPermissionsOverrideSchema),
9100
9180
  omp: z.optional(OmpPermissionsOverrideSchema),
9101
9181
  pool: z.optional(CanonicalPermissionsOverrideSchema),
9102
- qoder: z.optional(CanonicalPermissionsOverrideSchema),
9103
9182
  grokcli: z.optional(CanonicalPermissionsOverrideSchema),
9104
9183
  "kimi-code": z.optional(KimiCodePermissionsOverrideSchema),
9105
9184
  roo: z.optional(CanonicalPermissionsOverrideSchema),
@@ -9620,7 +9699,9 @@ const RulesyncRuleFrontmatterSchema = z.object({
9620
9699
  qoder: z.optional(z.looseObject({
9621
9700
  trigger: z.optional(z.string()),
9622
9701
  glob: z.optional(z.union([z.string(), z.array(z.string())])),
9623
- description: z.optional(z.string())
9702
+ description: z.optional(z.string()),
9703
+ channel: z.optional(z.enum(["output-style"])),
9704
+ name: z.optional(z.string().check(z.trim(), z.minLength(1)))
9624
9705
  })),
9625
9706
  augmentcode: z.optional(z.looseObject({
9626
9707
  type: z.optional(z.string()),
@@ -12989,7 +13070,7 @@ const SHARED_CONFIG_OWNERSHIP = {
12989
13070
  },
12990
13071
  permissions: {
12991
13072
  kind: "replace-owned-keys",
12992
- ownedKeys: ["permissions"]
13073
+ ownedKeys: ["permissions", "outputStyle"]
12993
13074
  }
12994
13075
  }
12995
13076
  },
@@ -21744,22 +21825,29 @@ function mergeOpencodeV2Entries({ config, legacyKey, pluralKey, lower }) {
21744
21825
  const entry = asOpencodeEntries(value);
21745
21826
  const lowered = entry === null ? null : lower(entry);
21746
21827
  if (Object.hasOwn(merged, name) || lowered === null) continue;
21747
- Object.defineProperty(merged, name, {
21748
- value: lowered,
21749
- enumerable: true,
21750
- configurable: true,
21751
- writable: true
21752
- });
21828
+ setOwnEntry(merged, name, lowered);
21753
21829
  }
21754
21830
  return Object.keys(merged).length > 0 ? merged : legacy;
21755
21831
  }
21756
21832
  /**
21833
+ * Sets `target[key]` as an own, enumerable property. A plain assignment to a
21834
+ * key named `__proto__` would set the prototype instead.
21835
+ */
21836
+ function setOwnEntry(target, key, value) {
21837
+ Object.defineProperty(target, key, {
21838
+ value,
21839
+ enumerable: true,
21840
+ configurable: true,
21841
+ writable: true
21842
+ });
21843
+ }
21844
+ /**
21757
21845
  * Returns the inline agent entries of an OpenCode config in the V1 `agent`
21758
21846
  * shape. Since v1.18.24 OpenCode V1 also reads the V2 `agents` spelling and
21759
21847
  * lowers it, so a layout that does so (`readsV2ConfigSpellings`) gets those
21760
21848
  * entries too.
21761
21849
  *
21762
- * @see https://github.com/anomalyco/opencode/blob/v1.18.34/packages/opencode/src/config/v2-compat.ts
21850
+ * @see https://github.com/anomalyco/opencode/blob/v1.18.35/packages/opencode/src/config/v2-compat.ts
21763
21851
  */
21764
21852
  function getOpencodeAgentEntries({ config, layout = OPENCODE_LAYOUT }) {
21765
21853
  if (!layout.readsV2ConfigSpellings) return asOpencodeEntries(config.agent);
@@ -21778,7 +21866,7 @@ function getOpencodeAgentEntries({ config, layout = OPENCODE_LAYOUT }) {
21778
21866
  * Other fields are kept: V1 drops a V2 `subagent`, but rulesync reads it as
21779
21867
  * `subtask` everywhere (as V2 does), so it is left for the command importer.
21780
21868
  *
21781
- * @see https://github.com/anomalyco/opencode/blob/v1.18.34/packages/opencode/src/config/v2-compat.ts
21869
+ * @see https://github.com/anomalyco/opencode/blob/v1.18.35/packages/opencode/src/config/v2-compat.ts
21782
21870
  */
21783
21871
  function getOpencodeCommandEntries({ config, layout = OPENCODE_LAYOUT }) {
21784
21872
  if (!layout.readsV2ConfigSpellings) return asOpencodeEntries(config.command);
@@ -21801,7 +21889,7 @@ function getOpencodeCommandEntries({ config, layout = OPENCODE_LAYOUT }) {
21801
21889
  * reads V2 spellings, a flat V2 `skills` array is accepted too: its entries
21802
21890
  * that are not `http(s)://` URLs are the paths, as OpenCode V1 lowers them.
21803
21891
  *
21804
- * @see https://github.com/anomalyco/opencode/blob/v1.18.34/packages/opencode/src/config/v2-compat.ts
21892
+ * @see https://github.com/anomalyco/opencode/blob/v1.18.35/packages/opencode/src/config/v2-compat.ts
21805
21893
  */
21806
21894
  function getOpencodeSkillPaths({ config, layout = OPENCODE_LAYOUT }) {
21807
21895
  if (Array.isArray(config.skills)) {
@@ -21811,6 +21899,160 @@ function getOpencodeSkillPaths({ config, layout = OPENCODE_LAYOUT }) {
21811
21899
  const skills = asOpencodeEntries(config.skills);
21812
21900
  return skills !== null && Array.isArray(skills.paths) ? skills.paths : [];
21813
21901
  }
21902
+ /**
21903
+ * Whether a record is itself an MCP server entry rather than a V2 container
21904
+ * (`mcp.servers`, `mcp.timeout`): a server — or a server literally named
21905
+ * `type` / `enabled` — carries a scalar `type` or `enabled`. Mirrors OpenCode
21906
+ * V1's `isDirectServer`.
21907
+ */
21908
+ function isDirectOpencodeMcpServer(value) {
21909
+ return ["type", "enabled"].some((key) => Object.hasOwn(value, key) && (value[key] === null || typeof value[key] !== "object" || Array.isArray(value[key])));
21910
+ }
21911
+ const OPENCODE_V2_TIMEOUT_KEYS = [
21912
+ "startup",
21913
+ "catalog",
21914
+ "execution"
21915
+ ];
21916
+ /**
21917
+ * Whether an MCP server entry uses a V2-only per-server spelling, so that
21918
+ * OpenCode V1 lowers it: `disabled`, `codemode`, an object `timeout`, or a
21919
+ * snake_case `oauth` key.
21920
+ */
21921
+ function isOpencodeV2NativeServer(value) {
21922
+ const oauth = value.oauth;
21923
+ return Object.hasOwn(value, "disabled") || Object.hasOwn(value, "codemode") || isRecord$1(value.timeout) || isRecord$1(oauth) && [
21924
+ "client_id",
21925
+ "client_secret",
21926
+ "callback_port",
21927
+ "redirect_uri"
21928
+ ].some((key) => Object.hasOwn(oauth, key));
21929
+ }
21930
+ /**
21931
+ * Lowers a V2 timeout object (`{ startup, catalog, execution }`) to V1's single
21932
+ * number, the way OpenCode V1 does: only a `catalog` equal to `execution`, with
21933
+ * no `startup`, has a V1 equivalent. Anything else — including a value that is
21934
+ * not a positive integer — is `undefined` (dropped).
21935
+ */
21936
+ function lowerOpencodeV2Timeout(value) {
21937
+ if (!hasPositiveIntegerTimeouts(value) || value.startup !== void 0 || typeof value.catalog !== "number") return;
21938
+ return value.catalog === value.execution ? value.catalog : void 0;
21939
+ }
21940
+ const OPENCODE_V2_OAUTH_KEYS = /* @__PURE__ */ new Map([
21941
+ ["client_id", "clientId"],
21942
+ ["client_secret", "clientSecret"],
21943
+ ["callback_port", "callbackPort"],
21944
+ ["redirect_uri", "redirectUri"]
21945
+ ]);
21946
+ /**
21947
+ * V2 `oauth` keys that have no V1 spelling. OpenCode V1 drops them when it
21948
+ * lowers a server, so they are dropped (and reported) here too rather than
21949
+ * being written back as a snake_case key among camelCase ones.
21950
+ */
21951
+ const OPENCODE_V2_UNSUPPORTED_OAUTH_KEYS = /* @__PURE__ */ new Set(["auth_server_metadata_url"]);
21952
+ function lowerOpencodeV2OAuth(oauth, path, onDropped) {
21953
+ const lowered = {};
21954
+ for (const [key, value] of Object.entries(oauth)) {
21955
+ if (OPENCODE_V2_UNSUPPORTED_OAUTH_KEYS.has(key)) {
21956
+ onDropped?.(`${path}.${key}`, "unsupported");
21957
+ continue;
21958
+ }
21959
+ setOwnEntry(lowered, OPENCODE_V2_OAUTH_KEYS.get(key) ?? key, value);
21960
+ }
21961
+ return lowered;
21962
+ }
21963
+ /**
21964
+ * Lowers one V2-spelled MCP server entry to V1's shape, as OpenCode V1 does:
21965
+ * `disabled` becomes `enabled` (an explicit `enabled` wins), an object
21966
+ * `timeout` becomes a number or is dropped, and snake_case `oauth` keys become
21967
+ * camelCase. `codemode` is kept: Rulesync passes it through where V1 drops it.
21968
+ *
21969
+ * Like V1, only an entry that is a local or remote server is lowered, and one
21970
+ * whose `enabled` or `disabled` is not a boolean is left as it is. Unlike V1,
21971
+ * such an entry is not dropped but passed on, so the schema rejects it loudly.
21972
+ */
21973
+ function lowerOpencodeV2Server(value, path, onDropped) {
21974
+ if (!isRecord$1(value) || value.type !== "local" && value.type !== "remote" || Object.hasOwn(value, "enabled") && typeof value.enabled !== "boolean" || Object.hasOwn(value, "disabled") && typeof value.disabled !== "boolean") return value;
21975
+ const { disabled, timeout, ...rest } = value;
21976
+ const lowered = { ...rest };
21977
+ if (!Object.hasOwn(value, "enabled")) lowered.enabled = disabled !== true;
21978
+ else if (value.enabled === disabled) onDropped?.(`${path}.disabled`, "conflict");
21979
+ const lowTimeout = isRecord$1(timeout) ? lowerOpencodeV2Timeout(timeout) : timeout;
21980
+ if (lowTimeout !== void 0) lowered.timeout = lowTimeout;
21981
+ else if (isRecord$1(timeout) && Object.keys(timeout).length > 0) onDropped?.(`${path}.timeout`, "unsupported");
21982
+ if (isRecord$1(value.oauth)) lowered.oauth = lowerOpencodeV2OAuth(value.oauth, `${path}.oauth`, onDropped);
21983
+ return lowered;
21984
+ }
21985
+ function isPositiveInteger(value) {
21986
+ return typeof value === "number" && Number.isInteger(value) && value > 0;
21987
+ }
21988
+ /** Whether each V2 timeout value a record carries is a positive integer. */
21989
+ function hasPositiveIntegerTimeouts(value) {
21990
+ return OPENCODE_V2_TIMEOUT_KEYS.every((key) => value[key] === void 0 || isPositiveInteger(value[key]));
21991
+ }
21992
+ /**
21993
+ * Whether `mcp.timeout` is V2's global timeout object rather than a server
21994
+ * named `timeout`. As in OpenCode V1, each of its values must be a positive
21995
+ * integer; anything else is read as a server, which the schema then rejects.
21996
+ */
21997
+ function isOpencodeV2GlobalTimeout(value) {
21998
+ return isRecord$1(value) && !isDirectOpencodeMcpServer(value) && (Object.keys(value).length === 0 || OPENCODE_V2_TIMEOUT_KEYS.some((key) => Object.hasOwn(value, key))) && hasPositiveIntegerTimeouts(value);
21999
+ }
22000
+ function getOpencodeV2McpEnvelope(mcp) {
22001
+ const nested = mcp.servers;
22002
+ return isRecord$1(nested) && !isDirectOpencodeMcpServer(nested) ? nested : null;
22003
+ }
22004
+ /**
22005
+ * Returns the paths of the V2-only containers in an OpenCode `mcp` value that
22006
+ * a write in V1's flat shape does not keep: a non-empty `mcp.servers`
22007
+ * envelope, and a non-empty global `mcp.timeout`.
22008
+ */
22009
+ function findOpencodeV2McpContainers(mcp) {
22010
+ if (!isRecord$1(mcp)) return [];
22011
+ const envelope = getOpencodeV2McpEnvelope(mcp);
22012
+ return [...envelope !== null && Object.keys(envelope).length > 0 ? ["mcp.servers"] : [], ...isOpencodeV2GlobalTimeout(mcp.timeout) && Object.keys(mcp.timeout).length > 0 ? ["mcp.timeout"] : []];
22013
+ }
22014
+ /**
22015
+ * Lowers OpenCode V2 `mcp` spellings found in a V1 config to the flat V1 map,
22016
+ * the way OpenCode V1 itself does since v1.18.24 (`normalizeMcp`):
22017
+ *
22018
+ * - entries of the `mcp.servers` envelope are merged into the map, a flat entry
22019
+ * of the same name winning;
22020
+ * - V2-spelled server entries (`disabled`, an object `timeout`, snake_case
22021
+ * `oauth` keys) are lowered by {@link lowerOpencodeV2Server};
22022
+ * - the global `mcp.timeout` object is skipped. V1 moves it to
22023
+ * `experimental.mcp_timeout`, a default request timeout that has no
22024
+ * canonical counterpart, so it is not read as a server either.
22025
+ *
22026
+ * Unlike V1, an envelope entry that is not a valid server is not dropped: it
22027
+ * is passed on, so the schema rejects it loudly rather than it vanishing
22028
+ * without a word. Each value that is dropped — a non-empty global timeout, a
22029
+ * per-server timeout object or `oauth` key with no V1 equivalent, or an
22030
+ * envelope entry shadowed by a different flat entry — is reported through
22031
+ * `onDropped`.
22032
+ *
22033
+ * @see https://github.com/anomalyco/opencode/blob/v1.18.35/packages/opencode/src/config/v2-compat.ts
22034
+ * @see https://opencode.ai/v2/docs/mcp-servers/
22035
+ */
22036
+ function lowerOpencodeV2Mcp(mcp, onDropped) {
22037
+ if (!isRecord$1(mcp)) return mcp;
22038
+ const envelope = getOpencodeV2McpEnvelope(mcp);
22039
+ const globalTimeout = isOpencodeV2GlobalTimeout(mcp.timeout);
22040
+ if (findOpencodeV2McpContainers(mcp).includes("mcp.timeout")) onDropped?.("mcp.timeout", "unsupported");
22041
+ const servers = {};
22042
+ for (const [name, value] of Object.entries(mcp)) {
22043
+ if (name === "servers" && envelope !== null || name === "timeout" && globalTimeout) continue;
22044
+ setOwnEntry(servers, name, isRecord$1(value) && isOpencodeV2NativeServer(value) ? lowerOpencodeV2Server(value, `mcp.${name}`, onDropped) : value);
22045
+ }
22046
+ for (const [name, value] of Object.entries(envelope ?? {})) {
22047
+ const path = `mcp.servers.${name}`;
22048
+ if (Object.hasOwn(servers, name)) {
22049
+ if (!isDeepStrictEqual(servers[name], value)) onDropped?.(path, "conflict");
22050
+ continue;
22051
+ }
22052
+ setOwnEntry(servers, name, lowerOpencodeV2Server(value, path, onDropped));
22053
+ }
22054
+ return servers;
22055
+ }
21814
22056
  /** Matches a string whose entire value is an OpenCode `{file:...}` reference. */
21815
22057
  const OPENCODE_FILE_TEMPLATE_PATTERN = /^\s*\{file:(.+?)\}\s*$/;
21816
22058
  /**
@@ -22242,6 +22484,7 @@ const QODER_DIR = ".qoder";
22242
22484
  const QODER_RULE_FILE_NAME = "AGENTS.md";
22243
22485
  const QODER_LOCAL_RULE_FILE_NAME = "AGENTS.local.md";
22244
22486
  const QODER_RULES_DIR_PATH = join(QODER_DIR, "rules");
22487
+ const QODER_OUTPUT_STYLES_DIR_PATH = join(QODER_DIR, "output-styles");
22245
22488
  const QODER_SETTINGS_FILE_NAME = "settings.json";
22246
22489
  const QODER_PROJECT_MCP_FILE_NAME = ".mcp.json";
22247
22490
  const QODER_COMMANDS_DIR_PATH = join(QODER_DIR, "commands");
@@ -29616,6 +29859,11 @@ const CORTEXCODE_SPEC = {
29616
29859
  canonical: "enabled",
29617
29860
  tool: "enabled"
29618
29861
  }],
29862
+ stringPassthroughFields: [{
29863
+ canonical: "statusMessage",
29864
+ tool: "statusMessage",
29865
+ commandOnly: true
29866
+ }],
29619
29867
  objectPassthroughFields: [{
29620
29868
  canonical: "source",
29621
29869
  tool: "source",
@@ -29630,7 +29878,7 @@ const CORTEXCODE_SPEC = {
29630
29878
  * (project scope) and of the dedicated `~/.snowflake/cortex/hooks.json` (user
29631
29879
  * scope), both in the Claude-Code shape: `{ "<Event>": [{ "matcher"?:
29632
29880
  * "<regex>", "hooks": [{ "type": "command" | "prompt", ..., "timeout"?:
29633
- * <seconds>, "enabled"?: <boolean>, "source"?: { "source", "ref"? } }] }] }`.
29881
+ * <seconds>, "enabled"?: <boolean>, "statusMessage"?: <string> (command only), "source"?: { "source", "ref"? } }] }] }`.
29634
29882
  * The project file also holds settings rulesync does not own, so generation
29635
29883
  * merges the `hooks` key into
29636
29884
  * either file (see `SHARED_CONFIG_OWNERSHIP`) instead of overwriting it, and
@@ -30569,7 +30817,8 @@ const FACTORYDROID_OVERRIDE_KEYS = [
30569
30817
  "allowManagedHooksOnly",
30570
30818
  "disabledSkills",
30571
30819
  "modelPolicy",
30572
- "missionPolicy"
30820
+ "missionPolicy",
30821
+ "permissionRules"
30573
30822
  ];
30574
30823
  //#endregion
30575
30824
  //#region src/utils/factorydroid-settings.ts
@@ -30784,7 +31033,7 @@ const GITLABDUO_CONVERTER_CONFIG = {
30784
31033
  *
30785
31034
  * Hooks live in a dedicated file, `<project>/.gitlab/duo/hooks.json` (project,
30786
31035
  * opt-in via `--enable-project-hooks` / `GITLAB_ENABLE_PROJECT_HOOKS=true`) or
30787
- * `~/.gitlab/duo/hooks.json` (user), shaped as `{ "hooks": { "SessionStart": [...] } }`.
31036
+ * `~/.gitlab/duo/hooks.json` (user), shaped as `{ "hooks": { "SessionStart": [...], "PreToolUse": [...] } }`.
30788
31037
  * rulesync owns the whole file, so it is regenerated and deleted as a unit.
30789
31038
  */
30790
31039
  var GitlabduoHooks = class GitlabduoHooks extends ToolHooks {
@@ -36438,6 +36687,7 @@ var ClineIgnore = class ClineIgnore extends ToolIgnore {
36438
36687
  const CODEBUFF_IGNORE_FILE_NAME = ".codebuffignore";
36439
36688
  const CODEBUFF_AGENTS_DIR = ".agents";
36440
36689
  const CODEBUFF_MCP_FILE_NAME = "mcp.json";
36690
+ const CODEBUFF_GLOBAL_RULE_FILE_NAME = ".AGENTS.md";
36441
36691
  //#endregion
36442
36692
  //#region src/features/ignore/codebuff-ignore.ts
36443
36693
  /**
@@ -43339,11 +43589,45 @@ const CANONICAL_TO_GITLABDUO_TRANSPORT = {
43339
43589
  sse: "sse"
43340
43590
  };
43341
43591
  /**
43592
+ * Map the canonical `timeout` (milliseconds) onto GitLab Duo's `timeoutMs`, a
43593
+ * per-server tool-call inactivity timeout (gitlab-lsp v9.25.0+) that must be a
43594
+ * positive integer. An explicit `timeoutMs` on the same server wins; a
43595
+ * `timeout` Duo would reject is dropped with a warning.
43596
+ *
43597
+ * @see https://gitlab.com/gitlab-org/editor-extensions/gitlab-lsp/-/commit/e8c4a76a66e8f37d4c97fc03ffe983bfadf43f76
43598
+ */
43599
+ function toGitlabduoTimeout({ serverName, server, logger }) {
43600
+ const { timeout, ...rest } = server;
43601
+ if (timeout === void 0 || rest.timeoutMs !== void 0) return rest;
43602
+ if (typeof timeout !== "number" || !Number.isInteger(timeout) || timeout <= 0) {
43603
+ logger?.warn(`GitLab Duo CLI MCP server "${serverName}": dropping timeout ${JSON.stringify(timeout)}, as timeoutMs must be a positive integer of milliseconds.`);
43604
+ return rest;
43605
+ }
43606
+ return {
43607
+ ...rest,
43608
+ timeoutMs: timeout
43609
+ };
43610
+ }
43611
+ /**
43612
+ * Read GitLab Duo's `timeoutMs` back into the canonical `timeout`
43613
+ * (milliseconds). A server that already carries a `timeout` is left as is, so
43614
+ * both keys round-trip and `timeoutMs` keeps winning on the next generate.
43615
+ */
43616
+ function fromGitlabduoTimeout(server) {
43617
+ if (typeof server !== "object" || server === null || Array.isArray(server)) return server;
43618
+ const { timeoutMs, ...rest } = server;
43619
+ if (timeoutMs === void 0 || rest.timeout !== void 0) return server;
43620
+ return {
43621
+ ...rest,
43622
+ timeout: timeoutMs
43623
+ };
43624
+ }
43625
+ /**
43342
43626
  * Convert one canonical server into GitLab Duo's shape. Every documented
43343
43627
  * example spells the transport out as `type` (`stdio`, `http` or `sse`), so it
43344
43628
  * is always written, inferred from `command` / `url` when the source leaves it
43345
- * implicit. Every other field — `args`, `env`, `approvedTools`, ... — is carried
43346
- * through as authored.
43629
+ * implicit. The canonical `timeout` becomes `timeoutMs`; every other field —
43630
+ * `args`, `env`, `approvedTools`, ... — is carried through as authored.
43347
43631
  */
43348
43632
  function toGitlabduoServer({ serverName, serverConfig, logger }) {
43349
43633
  if (declaresNoTransport(serverConfig)) return warnAndSkipMcpServer({
@@ -43352,7 +43636,7 @@ function toGitlabduoServer({ serverName, serverConfig, logger }) {
43352
43636
  reason: "no command or url",
43353
43637
  logger
43354
43638
  });
43355
- const { type, transport, httpUrl: _httpUrl, url: _url, ...rest } = serverConfig;
43639
+ const { type, transport, httpUrl: _httpUrl, url: _url, ...others } = serverConfig;
43356
43640
  const declared = type ?? transport;
43357
43641
  const mapped = declared ? CANONICAL_TO_GITLABDUO_TRANSPORT[declared] : isRemoteMcpServer(serverConfig) ? "http" : "stdio";
43358
43642
  if (!mapped) return warnAndSkipMcpServer({
@@ -43361,8 +43645,13 @@ function toGitlabduoServer({ serverName, serverConfig, logger }) {
43361
43645
  reason: `the unsupported transport "${declared}" (GitLab Duo CLI accepts stdio, http and sse)`,
43362
43646
  logger
43363
43647
  });
43648
+ const rest = toGitlabduoTimeout({
43649
+ serverName,
43650
+ server: others,
43651
+ logger
43652
+ });
43364
43653
  if (mapped === "stdio") {
43365
- const { command, args } = rest;
43654
+ const { command, args } = serverConfig;
43366
43655
  if (Array.isArray(command)) {
43367
43656
  const [executable, ...leadingArgs] = command;
43368
43657
  return {
@@ -43451,7 +43740,13 @@ var GitlabduoMcp = class GitlabduoMcp extends ToolMcp {
43451
43740
  });
43452
43741
  }
43453
43742
  toRulesyncMcp() {
43454
- return this.toRulesyncMcpDefault();
43743
+ const { mcpServers, ...others } = this.json;
43744
+ if (typeof mcpServers !== "object" || mcpServers === null || Array.isArray(mcpServers)) return this.toRulesyncMcpDefault();
43745
+ const servers = Object.fromEntries(Object.entries(mcpServers).map(([name, server]) => [name, fromGitlabduoTimeout(server)]));
43746
+ return this.toRulesyncMcpDefault({ fileContent: JSON.stringify({
43747
+ ...others,
43748
+ mcpServers: servers
43749
+ }) });
43455
43750
  }
43456
43751
  validate() {
43457
43752
  return {
@@ -45559,18 +45854,21 @@ var KiroMcp = class KiroMcp extends ToolMcp {
45559
45854
  };
45560
45855
  //#endregion
45561
45856
  //#region src/features/mcp/opencode-mcp.ts
45857
+ const OpencodeMcpV2DisabledSchema = z.optional(z.boolean());
45562
45858
  const OpencodeMcpLocalServerSchema = z.looseObject({
45563
45859
  type: z.literal("local"),
45564
45860
  command: z.array(z.string()),
45565
45861
  environment: z.optional(z.record(z.string(), z.string())),
45566
45862
  enabled: z._default(z.boolean(), true),
45863
+ disabled: OpencodeMcpV2DisabledSchema,
45567
45864
  cwd: z.optional(z.string())
45568
45865
  });
45569
45866
  const OpencodeMcpRemoteServerSchema = z.looseObject({
45570
45867
  type: z.literal("remote"),
45571
45868
  url: z.string(),
45572
45869
  headers: z.optional(z.record(z.string(), z.string())),
45573
- enabled: z._default(z.boolean(), true)
45870
+ enabled: z._default(z.boolean(), true),
45871
+ disabled: OpencodeMcpV2DisabledSchema
45574
45872
  });
45575
45873
  const OPENCODE_PASSTHROUGH_SERVER_FIELDS = [
45576
45874
  "timeout",
@@ -45582,7 +45880,7 @@ const OPENCODE_MCP_TRANSPORT_KEYS = [
45582
45880
  ...Object.keys(OpencodeMcpLocalServerSchema.def.shape),
45583
45881
  ...Object.keys(OpencodeMcpRemoteServerSchema.def.shape),
45584
45882
  ...OPENCODE_PASSTHROUGH_SERVER_FIELDS
45585
- ].filter((key) => key !== "enabled");
45883
+ ].filter((key) => key !== "enabled" && key !== "disabled");
45586
45884
  /**
45587
45885
  * A bare toggle entry: `{"enabled": <bool>}` with no transport of its own,
45588
45886
  * disabling a server another config layer defines. It is the third member of
@@ -45710,7 +46008,7 @@ const OPENCODE_TOGGLE_KEPT_KEYS = /* @__PURE__ */ new Set([
45710
46008
  function warnAboutToggleDroppedKeys(serverName, serverConfig, logger) {
45711
46009
  const dropped = Object.keys(serverConfig).filter((key) => !OPENCODE_TOGGLE_KEPT_KEYS.has(key));
45712
46010
  if (dropped.length === 0) return;
45713
- logger?.warn(`OpenCode MCP: "${serverName}" declares no transport, so it is written as a toggle entry and ${dropped.toSorted().join(", ")} ${dropped.length === 1 ? "is" : "are"} dropped.`);
46011
+ logger?.warn(`OpenCode MCP: ${quoteValueForWarning(serverName)} declares no transport, so it is written as a toggle entry and ${dropped.toSorted().map(quoteValueForWarning).join(", ")} ${dropped.length === 1 ? "is" : "are"} dropped.`);
45714
46012
  }
45715
46013
  /**
45716
46014
  * Convert standard MCP format to OpenCode native format
@@ -45790,9 +46088,35 @@ var OpencodeMcp = class extends ToolMcp {
45790
46088
  /** Directory layout; OpenCode forks (MiMo Code) override it. */
45791
46089
  static layout = OPENCODE_LAYOUT;
45792
46090
  json;
45793
- constructor(params) {
46091
+ /**
46092
+ * With a `logger`, the V2 values that lowering the content drops are warned
46093
+ * about (see {@link OpencodeMcp.lowerConfig}); `fromFile` passes one so the
46094
+ * warnings come from the lowering that builds the parsed view rather than
46095
+ * from an extra pass of their own. (`validate()`, which the parent
46096
+ * constructor calls before this view exists, lowers separately and silently.)
46097
+ */
46098
+ constructor({ logger, ...params }) {
45794
46099
  super(params);
45795
- this.json = OpencodeConfigSchema.parse(parse$1(this.fileContent || "{}"));
46100
+ this.json = OpencodeConfigSchema.parse(this.constructor.lowerConfig(parse$1(this.fileContent || "{}"), logger));
46101
+ }
46102
+ /**
46103
+ * Lowers the OpenCode V2 `mcp` spellings of a parsed config for a layout
46104
+ * that reads them (see {@link lowerOpencodeV2Mcp}), so every reader — import,
46105
+ * the toggle lookup on generate, and the rules feature's `instructions`
46106
+ * write to the same file — accepts a V2-shaped `mcp`. Only the parsed view
46107
+ * is lowered; content written back keeps whatever `mcp` a feature that does
46108
+ * not own the key found there. With a `logger`, each V2 value the lowering
46109
+ * drops is warned about.
46110
+ */
46111
+ static lowerConfig(json, logger) {
46112
+ if (!this.layout.readsV2ConfigSpellings || !isRecord$1(json) || json.mcp === void 0) return json;
46113
+ return {
46114
+ ...json,
46115
+ mcp: lowerOpencodeV2Mcp(json.mcp, (path, reason) => {
46116
+ const quoted = quoteValueForWarning(path);
46117
+ logger?.warn(reason === "conflict" ? `OpenCode MCP: ${quoted} conflicts with a V1 value that takes precedence (the flat "mcp" entry of the same name, or "enabled"), so it is dropped on import.` : `OpenCode MCP: ${quoted} has no Rulesync equivalent, so it is dropped on import.`);
46118
+ })
46119
+ };
45796
46120
  }
45797
46121
  getJson() {
45798
46122
  return this.json;
@@ -45809,7 +46133,7 @@ var OpencodeMcp = class extends ToolMcp {
45809
46133
  relativeFilePath: this.layout.jsonFileName
45810
46134
  };
45811
46135
  }
45812
- static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
46136
+ static async fromFile({ outputRoot = process.cwd(), validate = true, global = false, logger }) {
45813
46137
  const basePaths = this.getSettablePaths({ global });
45814
46138
  const jsonDir = join(outputRoot, basePaths.relativeDirPath);
45815
46139
  let fileContent = null;
@@ -45831,7 +46155,8 @@ var OpencodeMcp = class extends ToolMcp {
45831
46155
  relativeDirPath: basePaths.relativeDirPath,
45832
46156
  relativeFilePath,
45833
46157
  fileContent: JSON.stringify(newJson, null, 2),
45834
- validate
46158
+ validate,
46159
+ logger
45835
46160
  });
45836
46161
  }
45837
46162
  static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false, logger }) {
@@ -45850,7 +46175,12 @@ var OpencodeMcp = class extends ToolMcp {
45850
46175
  mcpServers: rulesyncMcp.getMcpServers(),
45851
46176
  replacement: "{env:$1}"
45852
46177
  });
45853
- const existingMcp = OpencodeConfigSchema.safeParse(parse$1(fileContent || "{}"));
46178
+ const existingJson = parse$1(fileContent || "{}");
46179
+ const existingMcp = OpencodeConfigSchema.safeParse(this.lowerConfig(existingJson));
46180
+ if (this.layout.readsV2ConfigSpellings && isRecord$1(existingJson)) {
46181
+ const containers = findOpencodeV2McpContainers(existingJson.mcp);
46182
+ if (containers.length > 0) logger?.warn(`OpenCode MCP: the whole "mcp" key is rewritten in the flat V1 shape, so ${containers.map(quoteValueForWarning).join(" and ")} ${containers.length === 1 ? "is" : "are"} not kept.`);
46183
+ }
45854
46184
  const { mcp: convertedMcp, tools: mcpTools } = convertToOpencodeFormat(transformedServers, (existingMcp.success ? existingMcp.data.mcp : void 0) ?? {}, logger);
45855
46185
  return new this({
45856
46186
  outputRoot,
@@ -45950,7 +46280,7 @@ var OpencodeMcp = class extends ToolMcp {
45950
46280
  }
45951
46281
  validate() {
45952
46282
  const json = parseJsonc(this.fileContent || "{}");
45953
- const result = OpencodeConfigSchema.safeParse(json);
46283
+ const result = OpencodeConfigSchema.safeParse(this.constructor.lowerConfig(json));
45954
46284
  if (!result.success) return {
45955
46285
  success: false,
45956
46286
  error: result.error
@@ -46523,6 +46853,15 @@ const CONSUMED_CANONICAL_KEYS = /* @__PURE__ */ new Set([
46523
46853
  ]);
46524
46854
  const MILLISECONDS_PER_SECOND = 1e3;
46525
46855
  /**
46856
+ * The only keys a project override entry for a user-level server carries:
46857
+ * Pi reads a `.pi/mcp.json` entry without `command`, `url` or `type` as an
46858
+ * override of `enabled`, `exposure` and `toolExposure` of the user-level server
46859
+ * with the same name, and keeps the rest of that server.
46860
+ *
46861
+ * @see https://github.com/earendil-works/pi/blob/v1.1.0/packages/coding-agent/docs/mcp.md
46862
+ */
46863
+ const PI_OVERRIDE_PASSTHROUGH_KEYS = ["exposure", "toolExposure"];
46864
+ /**
46526
46865
  * Pi rejects a server whose name has any other character, and treats names
46527
46866
  * that differ only in `-` and `_` as one server (its tools share the
46528
46867
  * `mcp__<name>` namespace with `-` replaced by `_`), rejecting the second.
@@ -46558,17 +46897,12 @@ function hasUnexpandedEnvVarRef(serverConfig) {
46558
46897
  /**
46559
46898
  * The transport fields Pi reads for one server: `type` `stdio` with a string
46560
46899
  * `command` plus `args`, or `type` `http` with a `url`. A server Pi cannot
46561
- * start or reach — no transport, a remote transport without a URL, an SSE or
46562
- * WebSocket server, or a stdio entry without a command — is rejected by Pi, so
46563
- * it is skipped with a warning (`null`).
46900
+ * start or reach — a remote transport without a URL, an SSE or WebSocket
46901
+ * server, or a stdio entry without a command — is rejected by Pi, so it is
46902
+ * skipped with a warning (`null`). A server with no transport at all is a
46903
+ * project override instead (see `convertOverrideToPi`).
46564
46904
  */
46565
46905
  function convertTransportToPi({ serverName, serverConfig, logger }) {
46566
- if (declaresNoTransport(serverConfig)) return warnAndSkipMcpServer({
46567
- toolName: "Pi",
46568
- serverName,
46569
- reason: "no transport",
46570
- logger
46571
- });
46572
46906
  if (isRemoteMcpServer(serverConfig)) {
46573
46907
  const url = resolveRemoteMcpUrl(serverConfig);
46574
46908
  const stated = serverConfig.type ?? serverConfig.transport;
@@ -46609,56 +46943,117 @@ function convertTransportToPi({ serverName, serverConfig, logger }) {
46609
46943
  };
46610
46944
  }
46611
46945
  /**
46612
- * Convert the canonical server map to Pi's `mcpServers` shape. Besides the
46613
- * transport fields, Pi switches a server off with `enabled: false` and reads
46614
- * `timeout` in seconds (canonical: milliseconds). A server name Pi rejects is
46615
- * skipped with a warning, and so is `auth` in the project file, which Pi
46616
- * accepts only in the global one (it drops the whole server otherwise).
46946
+ * A transport-less server in the project file becomes a Pi override entry for
46947
+ * the user-level server of the same name. `disabled` maps to `enabled` both
46948
+ * ways (an explicit `disabled: false` is "Enable in this project"), and any key
46949
+ * Pi ignores on an override is dropped with a warning. The global file has no
46950
+ * layer below it to override, so the entry is skipped there.
46951
+ */
46952
+ function convertOverrideToPi({ serverName, serverConfig, global, logger }) {
46953
+ if (global) return warnAndSkipMcpServer({
46954
+ toolName: "Pi",
46955
+ serverName,
46956
+ reason: "no transport",
46957
+ logger
46958
+ });
46959
+ const record = serverConfig;
46960
+ const override = {};
46961
+ if (typeof serverConfig.disabled === "boolean") override.enabled = !serverConfig.disabled;
46962
+ for (const key of PI_OVERRIDE_PASSTHROUGH_KEYS) if (record[key] !== void 0) override[key] = record[key];
46963
+ if (Object.keys(override).length === 0) return warnAndSkipMcpServer({
46964
+ toolName: "Pi",
46965
+ serverName,
46966
+ reason: "no transport and nothing a Pi project override sets (disabled, exposure, toolExposure)",
46967
+ logger
46968
+ });
46969
+ const droppedKeys = Object.keys(record).filter((key) => key !== "disabled" && !PI_OVERRIDE_PASSTHROUGH_KEYS.includes(key) && !PROTOTYPE_POLLUTION_KEYS.has(key));
46970
+ if (droppedKeys.length > 0) logger?.warn(`Pi MCP: dropping ${droppedKeys.map((key) => quoteValueForWarning(key)).join(", ")} of the project override ${quoteValueForWarning(serverName)}: Pi reads only enabled, exposure and toolExposure from an entry without a transport.`);
46971
+ return override;
46972
+ }
46973
+ /**
46974
+ * Whether Pi rejects the server name, either outright or because an earlier
46975
+ * server already took its `mcp__<name>` namespace; warns when it does.
46976
+ */
46977
+ function isRejectedServerName({ serverName, namespaces, logger }) {
46978
+ if (!PI_SERVER_NAME_PATTERN.test(serverName)) {
46979
+ warnAndSkipMcpServer({
46980
+ toolName: "Pi",
46981
+ serverName,
46982
+ reason: "a name Pi rejects (use letters, digits, \"_\" and \"-\")",
46983
+ logger
46984
+ });
46985
+ return true;
46986
+ }
46987
+ const clash = namespaces.get(toPiNamespace(serverName));
46988
+ if (clash !== void 0) {
46989
+ warnAndSkipMcpServer({
46990
+ toolName: "Pi",
46991
+ serverName,
46992
+ reason: `a name Pi treats as the same server as ${quoteValueForWarning(clash)} ("-" and "_" are equivalent)`,
46993
+ logger
46994
+ });
46995
+ return true;
46996
+ }
46997
+ return false;
46998
+ }
46999
+ /**
47000
+ * Convert one canonical server to its Pi entry, or `null` when it is skipped.
47001
+ * Besides the transport fields, Pi switches a server off with `enabled: false`
47002
+ * and reads `timeout` in seconds (canonical: milliseconds). `auth` is dropped
47003
+ * with a warning from the project file, since Pi accepts it only in the global
47004
+ * one (it drops the whole server otherwise). A server without a transport is a
47005
+ * project override entry instead.
47006
+ */
47007
+ function convertServerToPi({ serverName, serverConfig, global, logger }) {
47008
+ if (declaresNoTransport(serverConfig)) return convertOverrideToPi({
47009
+ serverName,
47010
+ serverConfig,
47011
+ global,
47012
+ logger
47013
+ });
47014
+ const converted = convertTransportToPi({
47015
+ serverName,
47016
+ serverConfig,
47017
+ logger
47018
+ });
47019
+ if (!converted) return null;
47020
+ for (const [key, value] of Object.entries(serverConfig)) {
47021
+ if (CONSUMED_CANONICAL_KEYS.has(key) || PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
47022
+ converted[key] = value;
47023
+ }
47024
+ if (serverConfig.timeout !== void 0) {
47025
+ if (Number.isFinite(serverConfig.timeout) && serverConfig.timeout > 0) converted.timeout = serverConfig.timeout / MILLISECONDS_PER_SECOND;
47026
+ else logger?.warn(`Pi MCP: dropping the "timeout" of ${quoteValueForWarning(serverName)}: expected a positive number of milliseconds.`);
47027
+ }
47028
+ if (serverConfig.disabled === true) converted.enabled = false;
47029
+ if (!global && "auth" in converted) {
47030
+ delete converted.auth;
47031
+ logger?.warn(`Pi MCP: dropping the "auth" of ${quoteValueForWarning(serverName)}: Pi accepts it only in the global ~/.pi/agent/mcp.json.`);
47032
+ }
47033
+ if (hasUnexpandedEnvVarRef(converted)) logger?.warn(`Pi MCP: ${quoteValueForWarning(serverName)} carries an environment variable reference Pi does not expand. Pi expands only \${VAR} and $VAR in env and headers values, and only a leading ~/ in command, args and cwd.`);
47034
+ return converted;
47035
+ }
47036
+ /**
47037
+ * Convert the canonical server map to Pi's `mcpServers` shape. A server name
47038
+ * Pi rejects is skipped with a warning.
46617
47039
  */
46618
47040
  function convertToPiFormat({ mcpServers, global, logger }) {
46619
47041
  const result = {};
46620
47042
  const namespaces = /* @__PURE__ */ new Map();
46621
47043
  for (const [serverName, serverConfig] of Object.entries(mcpServers)) {
46622
47044
  if (PROTOTYPE_POLLUTION_KEYS.has(serverName) || !isRecord$1(serverConfig)) continue;
46623
- if (!PI_SERVER_NAME_PATTERN.test(serverName)) {
46624
- warnAndSkipMcpServer({
46625
- toolName: "Pi",
46626
- serverName,
46627
- reason: "a name Pi rejects (use letters, digits, \"_\" and \"-\")",
46628
- logger
46629
- });
46630
- continue;
46631
- }
46632
- const clash = namespaces.get(toPiNamespace(serverName));
46633
- if (clash !== void 0) {
46634
- warnAndSkipMcpServer({
46635
- toolName: "Pi",
46636
- serverName,
46637
- reason: `a name Pi treats as the same server as ${quoteValueForWarning(clash)} ("-" and "_" are equivalent)`,
46638
- logger
46639
- });
46640
- continue;
46641
- }
46642
- const converted = convertTransportToPi({
47045
+ if (isRejectedServerName({
47046
+ serverName,
47047
+ namespaces,
47048
+ logger
47049
+ })) continue;
47050
+ const converted = convertServerToPi({
46643
47051
  serverName,
46644
47052
  serverConfig,
47053
+ global,
46645
47054
  logger
46646
47055
  });
46647
47056
  if (!converted) continue;
46648
- for (const [key, value] of Object.entries(serverConfig)) {
46649
- if (CONSUMED_CANONICAL_KEYS.has(key) || PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
46650
- converted[key] = value;
46651
- }
46652
- if (serverConfig.timeout !== void 0) {
46653
- if (Number.isFinite(serverConfig.timeout) && serverConfig.timeout > 0) converted.timeout = serverConfig.timeout / MILLISECONDS_PER_SECOND;
46654
- else logger?.warn(`Pi MCP: dropping the "timeout" of ${quoteValueForWarning(serverName)}: expected a positive number of milliseconds.`);
46655
- }
46656
- if (serverConfig.disabled === true) converted.enabled = false;
46657
- if (!global && "auth" in converted) {
46658
- delete converted.auth;
46659
- logger?.warn(`Pi MCP: dropping the "auth" of ${quoteValueForWarning(serverName)}: Pi accepts it only in the global ~/.pi/agent/mcp.json.`);
46660
- }
46661
- if (hasUnexpandedEnvVarRef(converted)) logger?.warn(`Pi MCP: ${quoteValueForWarning(serverName)} carries an environment variable reference Pi does not expand. Pi expands only \${VAR} and $VAR in env and headers values, and only a leading ~/ in command, args and cwd.`);
46662
47057
  namespaces.set(toPiNamespace(serverName), serverName);
46663
47058
  result[serverName] = converted;
46664
47059
  }
@@ -46667,16 +47062,19 @@ function convertToPiFormat({ mcpServers, global, logger }) {
46667
47062
  /**
46668
47063
  * Convert Pi's `mcpServers` back to the canonical shape: `enabled: false`
46669
47064
  * becomes `disabled: true` and `timeout` (seconds) becomes milliseconds; every
46670
- * other key is already canonical.
47065
+ * other key is already canonical. On a transport-less override entry an
47066
+ * explicit `enabled: true` is kept as `disabled: false`, since it switches on a
47067
+ * user-level server the user turned off.
46671
47068
  */
46672
47069
  function convertFromPiFormat(mcpServers) {
46673
47070
  const result = {};
46674
47071
  for (const [serverName, serverConfig] of Object.entries(mcpServers)) {
46675
47072
  if (PROTOTYPE_POLLUTION_KEYS.has(serverName) || !isRecord$1(serverConfig)) continue;
46676
47073
  const { enabled, timeout, ...rest } = serverConfig;
46677
- const converted = enabled === false ? {
47074
+ const keepsEnabled = enabled === true && declaresNoTransport(rest);
47075
+ const converted = enabled === false || keepsEnabled ? {
46678
47076
  ...rest,
46679
- disabled: true
47077
+ disabled: !enabled
46680
47078
  } : rest;
46681
47079
  if (typeof timeout === "number") converted.timeout = timeout * MILLISECONDS_PER_SECOND;
46682
47080
  else if (timeout !== void 0) converted.timeout = timeout;
@@ -46752,8 +47150,11 @@ var PiMcp = class PiMcp extends ToolMcp {
46752
47150
  });
46753
47151
  }
46754
47152
  toRulesyncMcp() {
46755
- const mcpServers = isMcpServers(this.json.mcpServers) ? this.json.mcpServers : {};
46756
- return this.toRulesyncMcpDefault({ fileContent: JSON.stringify({ mcpServers: convertFromPiFormat(mcpServers) }, null, 2) });
47153
+ const { shared, toolOnly } = splitMcpServersByTransport(convertFromPiFormat(isMcpServers(this.json.mcpServers) ? this.json.mcpServers : {}));
47154
+ return this.toRulesyncMcpDefault({ fileContent: JSON.stringify({
47155
+ mcpServers: shared,
47156
+ ...Object.keys(toolOnly).length > 0 && { pi: { mcpServers: toolOnly } }
47157
+ }, null, 2) });
46757
47158
  }
46758
47159
  validate() {
46759
47160
  return {
@@ -64546,9 +64947,11 @@ const DEFAULT_TOOLS_KEY = "defaultTools";
64546
64947
  * the built-in tools enabled at startup (v0.84.2) — authored through the `pi`
64547
64948
  * permissions override.
64548
64949
  *
64549
- * Scope semantics are the **opposite** of the union-merge most targets use:
64550
- * a project `defaultTools` array *replaces* the global array rather than adding
64551
- * to it, so the two scopes are written independently and never combined.
64950
+ * Scope layering is Pi's own rather than the union-merge most targets use:
64951
+ * a project `defaultTools` array with a plain name *replaces* the global
64952
+ * array, while one made only of `+name` / `-name` entries (Pi v0.99.0+) changes
64953
+ * the global selection instead. Entries are written verbatim, so the two
64954
+ * scopes are written independently and never combined by rulesync.
64552
64955
  * Project scope writes `.pi/settings.json`; global scope writes
64553
64956
  * `~/.pi/agent/settings.json`.
64554
64957
  *
@@ -65208,6 +65611,13 @@ function convertPoolToRulesync({ tools, paths }) {
65208
65611
  //#region src/features/permissions/qoder-permissions.ts
65209
65612
  /** Top-level key of `.qoder/settings.json` that rulesync owns. */
65210
65613
  const QODER_PERMISSIONS_KEY = "permissions";
65614
+ /**
65615
+ * Top-level key the `qoder` override authors: the active output style. Qoder
65616
+ * also reads `general.outputStyle`, but the top-level key wins, so that is the
65617
+ * one written and imported.
65618
+ * @see https://docs.qoder.com/cli/settings-reference
65619
+ */
65620
+ const QODER_OUTPUT_STYLE_KEY = "outputStyle";
65211
65621
  const CATCH_ALL_PATTERN$2 = "*";
65212
65622
  /** Prefix of the fully qualified MCP tool names (`mcp__<server>__<tool>`). */
65213
65623
  const MCP_TOOL_PREFIX = "mcp__";
@@ -65367,9 +65777,11 @@ function buildQoderRuleLists({ config, existingPermissions, logger }) {
65367
65777
  * through the shared-config gateway, the sibling keys of `permissions`
65368
65778
  * (`additionalDirectories`, `trustDirectories`) are kept, and the file is
65369
65779
  * never deleted. `.qoder/settings.local.json` (where `/allow` and `/deny`
65370
- * persist) is left to the user.
65780
+ * persist) is left to the user. The `qoder` override's `outputStyle` is
65781
+ * written to (and imported from) the top-level `outputStyle` key.
65371
65782
  *
65372
65783
  * @see https://docs.qoder.com/en/cli/permissions
65784
+ * @see https://docs.qoder.com/cli/output-styles
65373
65785
  */
65374
65786
  var QoderPermissions = class QoderPermissions extends ToolPermissions {
65375
65787
  constructor(params) {
@@ -65408,11 +65820,14 @@ var QoderPermissions = class QoderPermissions extends ToolPermissions {
65408
65820
  relativePath: join(paths.relativeDirPath, paths.relativeFilePath)
65409
65821
  });
65410
65822
  const existingPermissions = isRecord$1(existing[QODER_PERMISSIONS_KEY]) ? existing[QODER_PERMISSIONS_KEY] : {};
65823
+ const config = rulesyncPermissions.getJson();
65411
65824
  const lists = buildQoderRuleLists({
65412
- config: rulesyncPermissions.getJson(),
65825
+ config,
65413
65826
  existingPermissions,
65414
65827
  logger
65415
65828
  });
65829
+ const outputStyle = config.qoder?.outputStyle?.trim();
65830
+ if (outputStyle !== void 0) logger?.warn(`Qoder permissions: qoder.outputStyle = ${JSON.stringify(outputStyle)} was written to ${join(paths.relativeDirPath, paths.relativeFilePath)}${global ? " in the home directory" : ""}, so Qoder appends that style's instructions to the system prompt of every session it applies to.`);
65416
65831
  const permissions = { ...existingPermissions };
65417
65832
  for (const action of ACTIONS) if (lists[action].length > 0) permissions[action] = lists[action];
65418
65833
  else delete permissions[action];
@@ -65424,7 +65839,10 @@ var QoderPermissions = class QoderPermissions extends ToolPermissions {
65424
65839
  fileKey: sharedConfigFileKey(paths),
65425
65840
  feature: "permissions",
65426
65841
  existingContent,
65427
- patch: { [QODER_PERMISSIONS_KEY]: Object.keys(permissions).length > 0 ? permissions : void 0 },
65842
+ patch: {
65843
+ [QODER_PERMISSIONS_KEY]: Object.keys(permissions).length > 0 ? permissions : void 0,
65844
+ ...outputStyle !== void 0 && { [QODER_OUTPUT_STYLE_KEY]: outputStyle }
65845
+ },
65428
65846
  filePath
65429
65847
  }),
65430
65848
  validate: true,
@@ -65445,7 +65863,12 @@ var QoderPermissions = class QoderPermissions extends ToolPermissions {
65445
65863
  const existing = rules[parsed.pattern];
65446
65864
  if (existing === void 0 || ACTION_RANK[action] > ACTION_RANK[existing]) rules[parsed.pattern] = action;
65447
65865
  }
65448
- return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify({ permission }, null, 2) });
65866
+ const outputStyle = settings[QODER_OUTPUT_STYLE_KEY];
65867
+ const hasOutputStyle = typeof outputStyle === "string" && outputStyle.trim() !== "";
65868
+ return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify({
65869
+ permission,
65870
+ ...hasOutputStyle && { qoder: { outputStyle } }
65871
+ }, null, 2) });
65449
65872
  }
65450
65873
  validate() {
65451
65874
  return {
@@ -84493,8 +84916,8 @@ var DevinSubagent = class extends ToolSubagent {
84493
84916
  * Derive the subagent name from this instance's relative file path.
84494
84917
  *
84495
84918
  * The tool-side layout is `<name>/AGENT.md`, so the name is the parent
84496
- * directory of the file. If the path is unexpectedly flat (e.g. a legacy
84497
- * `<name>.md`), fall back to the basename without extension.
84919
+ * directory of the file. Devin also reads a flat `<name>.md`, which import
84920
+ * picks up too; its name is the basename without extension.
84498
84921
  */
84499
84922
  getSubagentName() {
84500
84923
  const relativeFilePath = this.getRelativeFilePath();
@@ -84587,8 +85010,9 @@ var DevinSubagent = class extends ToolSubagent {
84587
85010
  /**
84588
85011
  * Subagent inside a Devin plugin bundle (`<plugin>/agents/<name>/AGENT.md`).
84589
85012
  * Plugin subagents use the same profile format as project ones and are
84590
- * exposed as `<plugin>:<name>`. Devin also reads a flat `agents/<name>.md`;
84591
- * the directory form is written to match the `devin` target.
85013
+ * exposed as `<plugin>:<name>`. Devin also reads a flat `agents/<name>.md`,
85014
+ * which import picks up too; the directory form is written to match the
85015
+ * `devin` target.
84592
85016
  *
84593
85017
  * @see https://docs.devin.ai/cli/extensibility/plugins/overview
84594
85018
  */
@@ -88150,6 +88574,17 @@ var ZoocodeSubagent = class extends RooSubagent {
88150
88574
  };
88151
88575
  //#endregion
88152
88576
  //#region src/features/subagents/subagents-processor.ts
88577
+ /**
88578
+ * The globs import scans for a tool: its import-only patterns when it reads
88579
+ * more layouts than Rulesync writes, otherwise the generation pattern.
88580
+ */
88581
+ function importFilePatternsOf(meta) {
88582
+ return meta.importFilePatterns ?? meta.filePattern;
88583
+ }
88584
+ /** Whether unparseable files are left out of this import or orphan sweep. */
88585
+ function skipsInvalidFiles(meta, forDeletion) {
88586
+ return meta.skipInvalidFiles === true || !forDeletion && meta.skipInvalidImportFiles === true;
88587
+ }
88153
88588
  const SubagentsProcessorToolTargetSchema = z.enum(subagentsProcessorToolTargetTuple);
88154
88589
  /**
88155
88590
  * Factory Map mapping tool targets to their subagent factories.
@@ -88346,7 +88781,9 @@ const toolSubagentFactories = /* @__PURE__ */ new Map([
88346
88781
  supportsProject: true,
88347
88782
  supportsSimulated: false,
88348
88783
  supportsGlobal: true,
88349
- filePattern: "*/AGENT.md"
88784
+ filePattern: "*/AGENT.md",
88785
+ importFilePatterns: ["*.md", "*/AGENT.md"],
88786
+ skipInvalidImportFiles: true
88350
88787
  }
88351
88788
  }],
88352
88789
  ["devin-plugin", {
@@ -88355,7 +88792,9 @@ const toolSubagentFactories = /* @__PURE__ */ new Map([
88355
88792
  supportsProject: true,
88356
88793
  supportsSimulated: false,
88357
88794
  supportsGlobal: false,
88358
- filePattern: "*/AGENT.md"
88795
+ filePattern: "*/AGENT.md",
88796
+ importFilePatterns: ["*.md", "*/AGENT.md"],
88797
+ skipInvalidImportFiles: true
88359
88798
  }
88360
88799
  }],
88361
88800
  ["factorydroid", {
@@ -88834,6 +89273,7 @@ var SubagentsProcessor = class extends FeatureProcessor {
88834
89273
  const factory = this.getFactory(this.toolTarget);
88835
89274
  const paths = factory.class.getSettablePaths({ global: this.global });
88836
89275
  const roots = forDeletion ? [paths.relativeDirPath] : [paths.relativeDirPath, ...paths.importDirPaths ?? []];
89276
+ const filePatterns = forDeletion ? factory.meta.filePattern : importFilePatternsOf(factory.meta);
88837
89277
  const toolSubagents = [];
88838
89278
  const claimedRelativeFilePaths = /* @__PURE__ */ new Map();
88839
89279
  const claimedImportIdentities = new ClaimedIdentities();
@@ -88850,7 +89290,7 @@ var SubagentsProcessor = class extends FeatureProcessor {
88850
89290
  this.logger.warn(`Skipping the orphan sweep for ${quoteForLog(baseDir)}; nothing under it is deleted: ${stripControlCharacters(formatError(error))}`);
88851
89291
  continue;
88852
89292
  }
88853
- const subagentFilePaths = (await findFilesByGlobs(factory.meta.filePattern, {
89293
+ const subagentFilePaths = (await findFilesByGlobs(filePatterns, {
88854
89294
  cwd: baseDir,
88855
89295
  followSymbolicLinks: !forDeletion
88856
89296
  })).toSorted((left, right) => compareRelativePathsShallowestFirst(relative(baseDir, left), relative(baseDir, right)));
@@ -88864,7 +89304,7 @@ var SubagentsProcessor = class extends FeatureProcessor {
88864
89304
  })));
88865
89305
  ownedFilePaths = subagentFilePaths.filter((_, index) => ownership[index]);
88866
89306
  }
88867
- const parsedFiles = factory.meta.skipInvalidFiles ? (await Promise.all(ownedFilePaths.map(async (path) => {
89307
+ const parsedFiles = skipsInvalidFiles(factory.meta, forDeletion) ? (await Promise.all(ownedFilePaths.map(async (path) => {
88868
89308
  try {
88869
89309
  return {
88870
89310
  path,
@@ -91740,6 +92180,85 @@ var CodebuddyRule = class CodebuddyRule extends ToolRule {
91740
92180
  }
91741
92181
  };
91742
92182
  //#endregion
92183
+ //#region src/features/rules/codebuff-rule.ts
92184
+ /**
92185
+ * Rule generator for Codebuff (Freebuff), global scope only.
92186
+ *
92187
+ * Codebuff reads one user-level instructions file from the home directory:
92188
+ * the first hidden file matching `AGENTS.md` or `CLAUDE.md` (case-insensitive,
92189
+ * in that priority order), so the root rule is written to `~/.AGENTS.md`. There
92190
+ * is no user-level rules directory, so non-root rules fold into that file.
92191
+ *
92192
+ * Project scope is not supported here: Codebuff reads the project-root
92193
+ * `AGENTS.md`, which the `agentsmd` target already writes. The RulesProcessor
92194
+ * keeps this target out of project mode (`supportsProject: false`), so the
92195
+ * settable paths are the home-directory ones regardless of `global`.
92196
+ *
92197
+ * @see https://github.com/CodebuffAI/freebuff/blob/main/sdk/src/run-state.ts
92198
+ * @see https://github.com/CodebuffAI/freebuff/blob/main/common/src/constants/knowledge.ts
92199
+ */
92200
+ var CodebuffRule = class CodebuffRule extends ToolRule {
92201
+ static getSettablePaths(_options = {}) {
92202
+ return { root: {
92203
+ relativeDirPath: ".",
92204
+ relativeFilePath: CODEBUFF_GLOBAL_RULE_FILE_NAME
92205
+ } };
92206
+ }
92207
+ static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true }) {
92208
+ const { root } = this.getSettablePaths({ global: true });
92209
+ if (relativeFilePath !== root.relativeFilePath) throw new Error(`CodebuffRule only supports the root rule: ${relativeFilePath}`);
92210
+ const fileContent = await readFileContent(join(outputRoot, root.relativeDirPath, root.relativeFilePath));
92211
+ return new CodebuffRule({
92212
+ outputRoot,
92213
+ relativeDirPath: root.relativeDirPath,
92214
+ relativeFilePath: root.relativeFilePath,
92215
+ fileContent,
92216
+ validate,
92217
+ root: true
92218
+ });
92219
+ }
92220
+ static fromRulesyncRule({ outputRoot = process.cwd(), rulesyncRule, validate = true }) {
92221
+ const { root } = this.getSettablePaths({ global: true });
92222
+ const frontmatter = rulesyncRule.getFrontmatter();
92223
+ return new CodebuffRule({
92224
+ outputRoot,
92225
+ relativeDirPath: root.relativeDirPath,
92226
+ relativeFilePath: root.relativeFilePath,
92227
+ fileContent: rulesyncRule.getBody(),
92228
+ validate,
92229
+ root: frontmatter.root ?? false,
92230
+ description: frontmatter.description,
92231
+ globs: frontmatter.globs
92232
+ });
92233
+ }
92234
+ toRulesyncRule() {
92235
+ return this.toRulesyncRuleDefault();
92236
+ }
92237
+ validate() {
92238
+ return {
92239
+ success: true,
92240
+ error: null
92241
+ };
92242
+ }
92243
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
92244
+ const { root } = this.getSettablePaths({ global: true });
92245
+ return new CodebuffRule({
92246
+ outputRoot,
92247
+ relativeDirPath,
92248
+ relativeFilePath,
92249
+ fileContent: "",
92250
+ validate: false,
92251
+ root: relativeDirPath === root.relativeDirPath && relativeFilePath === root.relativeFilePath
92252
+ });
92253
+ }
92254
+ static isTargetedByRulesyncRule(rulesyncRule) {
92255
+ return this.isTargetedByRulesyncRuleDefault({
92256
+ rulesyncRule,
92257
+ toolTarget: "codebuff"
92258
+ });
92259
+ }
92260
+ };
92261
+ //#endregion
91743
92262
  //#region src/features/rules/codewhale-rule.ts
91744
92263
  /**
91745
92264
  * Rule generator for Codewhale.
@@ -93377,7 +93896,7 @@ var DshRule = class extends NestedAgentsmdRule {
93377
93896
  * `getExtraFixedFiles`): Droid loads "direct `.md` children of each
93378
93897
  * `output-styles` directory".
93379
93898
  */
93380
- const OUTPUT_STYLE_FILE_GLOB = "*.md";
93899
+ const OUTPUT_STYLE_FILE_GLOB$1 = "*.md";
93381
93900
  /**
93382
93901
  * Rule generator for Factory Droid.
93383
93902
  *
@@ -93499,7 +94018,7 @@ var FactorydroidRule = class FactorydroidRule extends ToolRule {
93499
94018
  static getExtraFixedFiles({ global = false } = {}) {
93500
94019
  return [...this.getChannelPaths({ global }).map(({ path }) => path), {
93501
94020
  relativeDirPath: this.getOutputStylesDirPath(),
93502
- relativeFilePath: OUTPUT_STYLE_FILE_GLOB,
94021
+ relativeFilePath: OUTPUT_STYLE_FILE_GLOB$1,
93503
94022
  importOnly: true
93504
94023
  }];
93505
94024
  }
@@ -95354,6 +95873,12 @@ const UNIVERSAL_GLOBS$1 = /* @__PURE__ */ new Set([
95354
95873
  * Any other mode is kept in the `qoder` section so it survives a round trip.
95355
95874
  */
95356
95875
  const INFERABLE_TRIGGERS = /* @__PURE__ */ new Set(["always_on", "glob"]);
95876
+ /**
95877
+ * The file-name glob `getExtraFixedFiles` returns for the output-styles
95878
+ * directory, so import enumerates every style in it (never deletion: the
95879
+ * directory also holds hand-written styles).
95880
+ */
95881
+ const OUTPUT_STYLE_FILE_GLOB = "*.md";
95357
95882
  function normalizeGlobList(value) {
95358
95883
  if (value === void 0) return [];
95359
95884
  return (typeof value === "string" ? splitBraceAwareList(value) : value).map((glob) => glob.trim()).filter((glob) => glob !== "");
@@ -95387,22 +95912,59 @@ function resolveTrigger(frontmatter) {
95387
95912
  * A `qoder.trigger` (for example `model_decision` or `manual`) always takes
95388
95913
  * precedence and round-trips through import.
95389
95914
  *
95915
+ * A non-root rule with `qoder.channel: output-style` becomes a custom output
95916
+ * style instead, `.qoder/output-styles/<rule file name>.md` (both scopes),
95917
+ * with the rule's `description` and optional `qoder.name` as the style's
95918
+ * `description` / `name` frontmatter.
95919
+ *
95390
95920
  * @see https://docs.qoder.com/en/cli/04-extending-qoder-cli/memory
95921
+ * @see https://docs.qoder.com/cli/output-styles
95391
95922
  */
95392
95923
  var QoderRule = class QoderRule extends ToolRule {
95393
95924
  frontmatter;
95394
95925
  body;
95395
- constructor({ frontmatter, body, ...rest }) {
95926
+ outputStyle;
95927
+ constructor({ frontmatter, body, outputStyle = false, ...rest }) {
95396
95928
  if (rest.validate) {
95397
95929
  const result = QoderRuleFrontmatterSchema.safeParse(frontmatter);
95398
95930
  if (!result.success) throw new Error(`Invalid frontmatter in ${join(rest.relativeDirPath, rest.relativeFilePath)}: ${formatError(result.error)}`);
95399
95931
  }
95400
95932
  super({
95401
95933
  ...rest,
95402
- fileContent: rest.root ? body : stringifyFrontmatter(body, frontmatter)
95934
+ fileContent: rest.root || outputStyle && Object.keys(frontmatter).length === 0 ? body : stringifyFrontmatter(body, frontmatter)
95403
95935
  });
95404
95936
  this.frontmatter = frontmatter;
95405
95937
  this.body = body;
95938
+ this.outputStyle = outputStyle;
95939
+ }
95940
+ /**
95941
+ * Whether the given path is a custom output style: a direct `.md` child of
95942
+ * `.qoder/output-styles/`, at either scope.
95943
+ */
95944
+ static isOutputStylePath({ relativeDirPath, relativeFilePath }) {
95945
+ return relativeDirPath === QODER_OUTPUT_STYLES_DIR_PATH && extname(relativeFilePath) === ".md" && basename(relativeFilePath) === relativeFilePath;
95946
+ }
95947
+ /** Whether a rule opts into the `output-style` channel (non-root only). */
95948
+ static isOutputStyleRule(rulesyncRule) {
95949
+ const frontmatter = rulesyncRule.getFrontmatter();
95950
+ return !frontmatter.root && frontmatter.qoder?.channel === "output-style";
95951
+ }
95952
+ /**
95953
+ * The output-styles directory, enumerated for import only: it also holds
95954
+ * styles users write by hand (including personal ones in
95955
+ * `~/.qoder/output-styles/`) that rulesync cannot tell from its own, so
95956
+ * `--delete` leaves it alone.
95957
+ */
95958
+ static getExtraFixedFiles(_params = {}) {
95959
+ return [{
95960
+ relativeDirPath: QODER_OUTPUT_STYLES_DIR_PATH,
95961
+ relativeFilePath: OUTPUT_STYLE_FILE_GLOB,
95962
+ importOnly: true
95963
+ }];
95964
+ }
95965
+ /** Qoder loads output styles itself; they are not rules to reference. */
95966
+ isExcludedFromRootReferences() {
95967
+ return this.outputStyle;
95406
95968
  }
95407
95969
  static getSettablePaths({ global = false, excludeToolDir } = {}) {
95408
95970
  return {
@@ -95415,6 +95977,23 @@ var QoderRule = class QoderRule extends ToolRule {
95415
95977
  }
95416
95978
  static async fromFile({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, validate = true, global = false }) {
95417
95979
  const paths = this.getSettablePaths({ global });
95980
+ if (this.isOutputStylePath({
95981
+ relativeDirPath,
95982
+ relativeFilePath
95983
+ })) {
95984
+ const filePath = join(outputRoot, QODER_OUTPUT_STYLES_DIR_PATH, relativeFilePath);
95985
+ const { frontmatter, body } = parseFrontmatter(await readFileContent(filePath), filePath);
95986
+ return new QoderRule({
95987
+ outputRoot,
95988
+ relativeDirPath: QODER_OUTPUT_STYLES_DIR_PATH,
95989
+ relativeFilePath,
95990
+ frontmatter,
95991
+ body: body.trim(),
95992
+ validate,
95993
+ root: false,
95994
+ outputStyle: true
95995
+ });
95996
+ }
95418
95997
  if (relativeDirPath !== void 0 ? relativeDirPath !== QODER_RULES_DIR_PATH : relativeFilePath === paths.root.relativeFilePath) {
95419
95998
  const rootDirPath = relativeDirPath ?? paths.root.relativeDirPath;
95420
95999
  const fileContent = await readFileContent(join(outputRoot, rootDirPath, paths.root.relativeFilePath));
@@ -95447,6 +96026,11 @@ var QoderRule = class QoderRule extends ToolRule {
95447
96026
  const root = rulesyncFrontmatter.root ?? false;
95448
96027
  const paths = this.getSettablePaths({ global });
95449
96028
  const body = rulesyncRule.getBody();
96029
+ if (this.isOutputStyleRule(rulesyncRule)) return this.buildOutputStyle({
96030
+ outputRoot,
96031
+ rulesyncRule,
96032
+ validate
96033
+ });
95450
96034
  if (root) return new QoderRule({
95451
96035
  outputRoot,
95452
96036
  relativeDirPath: paths.root.relativeDirPath,
@@ -95466,8 +96050,32 @@ var QoderRule = class QoderRule extends ToolRule {
95466
96050
  root: false
95467
96051
  });
95468
96052
  }
96053
+ /**
96054
+ * Write an `output-style` rule as `.qoder/output-styles/<name>.md`. The file
96055
+ * name is the rule's own, so a rule nested under `.rulesync/rules/` is
96056
+ * refused: Qoder reads only the files directly in `output-styles/`.
96057
+ */
96058
+ static buildOutputStyle({ outputRoot, rulesyncRule, validate }) {
96059
+ const { description, qoder } = rulesyncRule.getFrontmatter();
96060
+ const sourcePath = rulesyncRule.getRelativeFilePath();
96061
+ if (basename(sourcePath) !== sourcePath) throw new Error(`Rule '${sourcePath}' sets qoder.channel: output-style but is nested in a subdirectory: Qoder reads only the files directly in .qoder/output-styles/, so move it to the top of ${RULESYNC_RULES_RELATIVE_DIR_PATH}.`);
96062
+ const name = qoder?.name;
96063
+ return new QoderRule({
96064
+ outputRoot,
96065
+ relativeDirPath: QODER_OUTPUT_STYLES_DIR_PATH,
96066
+ relativeFilePath: `${basename(sourcePath, extname(sourcePath))}.md`,
96067
+ frontmatter: {
96068
+ ...name !== void 0 && { name },
96069
+ ...description !== void 0 && { description }
96070
+ },
96071
+ body: rulesyncRule.getBody(),
96072
+ validate,
96073
+ root: false,
96074
+ outputStyle: true
96075
+ });
96076
+ }
95469
96077
  static buildFrontmatter(rulesyncFrontmatter, relativeFilePath) {
95470
- const { trigger: storedTrigger, glob: storedGlob, ...section } = rulesyncFrontmatter.qoder ?? {};
96078
+ const { trigger: storedTrigger, glob: storedGlob, channel: _channel, name: _name, ...section } = rulesyncFrontmatter.qoder ?? {};
95471
96079
  const specificGlobs = normalizeGlobList(storedGlob ?? rulesyncFrontmatter.globs).filter((glob) => !UNIVERSAL_GLOBS$1.has(glob));
95472
96080
  let trigger = storedTrigger ?? (specificGlobs.length > 0 ? "glob" : "always_on");
95473
96081
  const description = section.description ?? rulesyncFrontmatter.description;
@@ -95485,6 +96093,25 @@ var QoderRule = class QoderRule extends ToolRule {
95485
96093
  }
95486
96094
  toRulesyncRule() {
95487
96095
  if (this.isRoot()) return this.toRulesyncRuleDefault();
96096
+ if (this.outputStyle) {
96097
+ const { name, description } = this.frontmatter;
96098
+ return new RulesyncRule({
96099
+ outputRoot: this.getOutputRoot(),
96100
+ relativeDirPath: RULESYNC_RULES_RELATIVE_DIR_PATH,
96101
+ relativeFilePath: this.getRelativeFilePath(),
96102
+ frontmatter: {
96103
+ root: false,
96104
+ targets: ["qoder"],
96105
+ ...typeof description === "string" && { description },
96106
+ qoder: {
96107
+ channel: "output-style",
96108
+ ...typeof name === "string" && name.trim() !== "" && { name }
96109
+ }
96110
+ },
96111
+ body: this.body,
96112
+ validate: true
96113
+ });
96114
+ }
95488
96115
  const trigger = resolveTrigger(this.frontmatter);
95489
96116
  const { trigger: _trigger, alwaysApply: _alwaysApply, description, glob, paths, ...unmapped } = this.frontmatter;
95490
96117
  const fileGlobs = normalizeGlobList(glob ?? paths);
@@ -95533,6 +96160,10 @@ var QoderRule = class QoderRule extends ToolRule {
95533
96160
  return this.body;
95534
96161
  }
95535
96162
  static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
96163
+ const outputStyle = this.isOutputStylePath({
96164
+ relativeDirPath,
96165
+ relativeFilePath
96166
+ });
95536
96167
  return new QoderRule({
95537
96168
  outputRoot,
95538
96169
  relativeDirPath,
@@ -95540,7 +96171,8 @@ var QoderRule = class QoderRule extends ToolRule {
95540
96171
  frontmatter: {},
95541
96172
  body: "",
95542
96173
  validate: false,
95543
- root: relativeDirPath !== QODER_RULES_DIR_PATH && relativeFilePath === "AGENTS.md"
96174
+ root: !outputStyle && relativeDirPath !== QODER_RULES_DIR_PATH && relativeFilePath === "AGENTS.md",
96175
+ outputStyle
95544
96176
  });
95545
96177
  }
95546
96178
  static isTargetedByRulesyncRule(rulesyncRule) {
@@ -97227,6 +97859,16 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
97227
97859
  localRootFileName: CODEBUDDY_LOCAL_RULE_FILE_NAME
97228
97860
  }
97229
97861
  }],
97862
+ ["codebuff", {
97863
+ class: CodebuffRule,
97864
+ meta: {
97865
+ extension: "md",
97866
+ supportsProject: false,
97867
+ supportsGlobal: true,
97868
+ ruleDiscoveryMode: "auto",
97869
+ collisionPolicy: "fold"
97870
+ }
97871
+ }],
97230
97872
  ["codewhale", {
97231
97873
  class: CodewhaleRule,
97232
97874
  meta: {
@@ -97657,7 +98299,9 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
97657
98299
  }]
97658
98300
  ]);
97659
98301
  const allToolTargetKeys = [...toolRuleFactories.keys()];
97660
- const rulesProcessorToolTargets = allToolTargetKeys;
98302
+ const rulesProcessorToolTargets = allToolTargetKeys.filter((target) => {
98303
+ return toolRuleFactories.get(target)?.meta.supportsProject !== false;
98304
+ });
97661
98305
  const rulesProcessorToolTargetsGlobal = allToolTargetKeys.filter((target) => {
97662
98306
  return toolRuleFactories.get(target)?.meta.supportsGlobal ?? false;
97663
98307
  });
@@ -97736,7 +98380,7 @@ var RulesProcessor = class extends FeatureProcessor {
97736
98380
  const factory = this.getFactory(this.toolTarget);
97737
98381
  const { meta } = factory;
97738
98382
  this.warnForIncludeRootOption(meta);
97739
- this.warnForFactorydroidRuleOptions({
98383
+ this.warnForOutputStyleRuleOptions({
97740
98384
  rules: alignedRules,
97741
98385
  factory
97742
98386
  });
@@ -97970,22 +98614,25 @@ var RulesProcessor = class extends FeatureProcessor {
97970
98614
  }) !== "auto") this.logger.warn("The rules option `ruleDiscoveryMode` has no effect with `includeRoot: false` on claudecode: the rule references it adds are written into CLAUDE.md, which is not generated.");
97971
98615
  }
97972
98616
  /**
97973
- * Warn about `factorydroid` frontmatter that does not do what it looks like
97974
- * it does: an `output-style` rule that also targets other tools reaches them
97975
- * as an ordinary rule (so presentation instructions end up in their project
97976
- * rules), and `factorydroid.name` is read only with `channel: output-style`.
97977
- * Checked while generating for factorydroid only, so each warning prints once.
98617
+ * Warn about `factorydroid` / `qoder` frontmatter that does not do what it
98618
+ * looks like it does: an `output-style` rule that also targets other tools
98619
+ * reaches them as an ordinary rule (so presentation instructions end up in
98620
+ * their project rules), and `<tool>.name` is read only with
98621
+ * `channel: output-style`. Checked while generating for that tool only, so
98622
+ * each warning prints once.
97978
98623
  */
97979
- warnForFactorydroidRuleOptions({ rules, factory }) {
97980
- if (this.toolTarget !== "factorydroid") return;
98624
+ warnForOutputStyleRuleOptions({ rules, factory }) {
98625
+ const toolTarget = this.toolTarget;
98626
+ if (toolTarget !== "factorydroid" && toolTarget !== "qoder") return;
97981
98627
  for (const rule of rules) {
97982
98628
  const frontmatter = rule.getFrontmatter();
97983
- if (frontmatter.root || !frontmatter.factorydroid || !factory.class.isTargetedByRulesyncRule(rule)) continue;
97984
- const { channel, name } = frontmatter.factorydroid;
98629
+ const section = frontmatter[toolTarget];
98630
+ if (frontmatter.root || !section || !factory.class.isTargetedByRulesyncRule(rule)) continue;
98631
+ const { channel, name } = section;
97985
98632
  const fileName = stripControlCharacters(rule.getRelativeFilePath());
97986
98633
  const targets = frontmatter.targets ?? ["*"];
97987
- if (channel === "output-style" && targets.some((target) => target !== "factorydroid")) this.logger.warn(`${fileName} sets factorydroid.channel: output-style but also targets other tools, which receive it as an ordinary rule. Set \`targets: ["factorydroid"]\` to keep the output style to Factory Droid.`);
97988
- if (channel !== "output-style" && name !== void 0) this.logger.warn(`${fileName} sets factorydroid.name, which is read only with factorydroid.channel: output-style; ignoring it.`);
98634
+ if (channel === "output-style" && targets.some((target) => target !== toolTarget)) this.logger.warn(`${fileName} sets ${toolTarget}.channel: output-style but also targets other tools, which receive it as an ordinary rule. Set \`targets: ["${toolTarget}"]\` to keep the output style to ${toolTarget === "qoder" ? "Qoder" : "Factory Droid"}.`);
98635
+ if (channel !== "output-style" && name !== void 0) this.logger.warn(`${fileName} sets ${toolTarget}.name, which is read only with ${toolTarget}.channel: output-style; ignoring it.`);
97989
98636
  }
97990
98637
  }
97991
98638
  /**
@@ -101730,6 +102377,6 @@ async function importChecksCore(params) {
101730
102377
  return writtenCount;
101731
102378
  }
101732
102379
  //#endregion
101733
- export { RulesyncHooks as $, removeFile as $t, groupSpellingsByCaseFoldedIdentity as A, RULESYNC_RELATIVE_DIR_PATH as An, applyFileMode as At, QWENCODE_LOCAL_RULE_FILE_NAME as B, DEPRECATED_FEATURE_REPLACEMENTS as Bn, getFileSize as Bt, COMMANDCODE_SETTINGS_LOCAL_FILE_NAME as C, RULESYNC_IGNORE_RELATIVE_FILE_PATH as Cn, fallbackLogger as Ct, FACTORYDROID_DIR as D, RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH as Dn, withWarnOnceScope as Dt, ChecksProcessor as E, RULESYNC_MCP_SCHEMA_URL as En, resetRunWarningState as Et, CLAUDECODE_LOCAL_RULE_FILE_NAME as F, RULESYNC_USER_CONFIG_DIR_NAME as Fn, createTempDirectory as Ft, RulesyncSubagentFrontmatterSchema as G, quoteForLog as Gn, listDirectoryEntryNames as Gt, AUGMENTCODE_SETTINGS_LOCAL_FILE_NAME as H, truncateText as Hn, isFileNotFoundError as Ht, CLAUDECODE_MEMORIES_DIR_NAME as I, RULESYNC_XDG_CONFIG_HOME_DEFAULT_DIR_NAME as In, directoryExists as It, RulesyncRule as J, stripHiddenCharacters as Jn, pathEscapesRoot as Jt, RulesyncSkill as K, stripControlCharacters as Kn, listFilePathsRecursively as Kt, CLAUDECODE_SETTINGS_LOCAL_FILE_NAME as L, parseCommaSeparatedList as Ln, ensureDir as Lt, CODEXCLI_DIR as M, RULESYNC_SKILLS_RELATIVE_DIR_PATH as Mn, assertTreeContainsNoSymlinks as Mt, CLAUDECODE_AGENT_MEMORY_LOCAL_DIR_NAME as N, RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH as Nn, assertWritablePathInsideRoot as Nt, FACTORYDROID_SETTINGS_LOCAL_FILE_NAME as O, RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH as On, CLIError as Ot, CLAUDECODE_DIR as P, RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH as Pn, checkPathTraversal as Pt, RulesyncIgnore as Q, removeDirectoryStrict as Qt, CLAUDECODE_SKILLS_DIR_PATH as R, ALL_FEATURES as Rn, fileExists as Rt, COMMANDCODE_DIR as S, RULESYNC_HOOKS_RELATIVE_FILE_PATH as Sn, WarningCollectingLogger as St, CODEBUDDY_LOCAL_RULE_FILE_NAME as T, RULESYNC_MCP_RELATIVE_FILE_PATH as Tn, withFallbackLoggerTarget as Tt, getLocalSkillDirNames as U, hasDeceptiveHiddenCharacters as Un, isFileSystemError as Ut, AUGMENTCODE_DIR as V, formatError as Vn, getHomeDirectory as Vt, RulesyncSubagent as W, hasEnclosingMarkOutsideKeycap as Wn, isSymlink as Wt, RulesyncPermissions as X, readFileContentOrNull as Xt, RulesyncRuleFrontmatterSchema as Y, readFileContent as Yt, RulesyncMcp as Z, removeDirectory as Zt, LETTACODE_DIR as _, RULESYNC_COMMANDS_RELATIVE_DIR_PATH as _n, SourceEntrySchema as _t, getProcessorRegistryEntry as a, toPosixPath as an, RulesyncCheck as at, CommandsProcessor as b, RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH as bn, ConsoleLogger as bt, RulesProcessor as c, ALL_TOOL_TARGETS as cn, loadYaml as ct, ELLIPSIS_WIDTH as d, ToolTargetSchema as dn, ConfigResolver as dt, removeFileStrict as en, getRulesyncSourceCandidates as et, displayWidthOf as f, CURATED_RULES_FEATURE_SUBDIR as fn, mergeInputRootConfigs as ft, HooksProcessor as g, RULESYNC_CHECKS_RELATIVE_DIR_PATH as gn, GITIGNORE_DESTINATION_KEY as gt, IgnoreProcessor as h, RULESYNC_AIIGNORE_RELATIVE_FILE_PATH as hn, ConfigFileSchema as ht, inspectInputRoots as i, splitPathSegments as in, RulesyncCommandFrontmatterSchema as it, CODEXCLI_BASH_RULES_FILE_NAME as j, RULESYNC_RULES_RELATIVE_DIR_PATH as jn, assertDirectoryIfExists as jt, caseFoldIdentity as k, RULESYNC_PERMISSIONS_SCHEMA_URL as kn, ErrorCodes as kt, SubagentsProcessor as l, ALL_TOOL_TARGETS_WITH_WILDCARD as ln, SHARED_USER_MANAGED_CONFIG_PATHS as lt, McpProcessor as m, MAX_FILE_SIZE as mn, CONFLICTING_TARGET_PAIRS as mt, formatSourceLoadFailure as n, resolvePath as nn, parseJsonc as nt, convertFromTool as o, writeFileBuffer as on, RulesyncCheckFrontmatterSchema as ot, shortenToWidth as p, FEATURE_SOURCE_TREE_ENTRIES as pn, resolveEffectiveInputRoots as pt, RulesyncSkillFrontmatterSchema as q, stripControlCharactersKeepingLineFeeds as qn, listSubdirectoryNames as qt, generate as r, runWithDirectoryRollback as rn, RulesyncCommand as rt, isPackagingToolTarget as s, writeFileContent as sn, stringifyFrontmatter as st, importFromTool as t, removeTempDirectory as tn, resolveRulesyncSourceWritePath as tt, SkillsProcessor as u, PACKAGING_TOOL_TARGETS as un, SKILL_FILE_NAME as ut, LETTACODE_SETTINGS_LOCAL_FILE_NAME as v, RULESYNC_CONFIG_RELATIVE_FILE_PATH as vn, assertTargetsFeaturesExclusive as vt, CODEBUDDY_DIR as w, RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH as wn, warnOnConflictingFlags as wt, REASONIX_LOCAL_RULE_FILE_NAME as x, RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH as xn, JsonLogger as xt, CRUSH_LOCAL_RULE_FILE_NAME as y, RULESYNC_CONFIG_SCHEMA_URL as yn, findControlCharacter as yt, QWENCODE_DIR as z, ALL_FEATURES_WITH_WILDCARD as zn, findFilesByGlobs as zt };
102380
+ export { RulesyncHooks as $, removeDirectoryStrict as $t, groupSpellingsByCaseFoldedIdentity as A, RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH as An, applyFileMode as At, QWENCODE_LOCAL_RULE_FILE_NAME as B, ALL_FEATURES as Bn, getFileSize as Bt, COMMANDCODE_SETTINGS_LOCAL_FILE_NAME as C, RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH as Cn, fallbackLogger as Ct, FACTORYDROID_DIR as D, RULESYNC_MCP_RELATIVE_FILE_PATH as Dn, withWarnOnceScope as Dt, ChecksProcessor as E, RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH as En, resetRunWarningState as Et, CLAUDECODE_LOCAL_RULE_FILE_NAME as F, RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH as Fn, createTempDirectory as Ft, RulesyncSubagentFrontmatterSchema as G, hasDeceptiveHiddenCharacters as Gn, isSymlink as Gt, AUGMENTCODE_SETTINGS_LOCAL_FILE_NAME as H, DEPRECATED_FEATURE_REPLACEMENTS as Hn, hasExecutableBit as Ht, CLAUDECODE_MEMORIES_DIR_NAME as I, RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH as In, directoryExists as It, RulesyncRule as J, stripControlCharacters as Jn, listSubdirectoryNames as Jt, RulesyncSkill as K, hasEnclosingMarkOutsideKeycap as Kn, listDirectoryEntryNames as Kt, CLAUDECODE_SETTINGS_LOCAL_FILE_NAME as L, RULESYNC_USER_CONFIG_DIR_NAME as Ln, ensureDir as Lt, CODEXCLI_DIR as M, RULESYNC_RELATIVE_DIR_PATH as Mn, assertTreeContainsNoSymlinks as Mt, CLAUDECODE_AGENT_MEMORY_LOCAL_DIR_NAME as N, RULESYNC_RULES_RELATIVE_DIR_PATH as Nn, assertWritablePathInsideRoot as Nt, FACTORYDROID_SETTINGS_LOCAL_FILE_NAME as O, RULESYNC_MCP_SCHEMA_URL as On, CLIError as Ot, CLAUDECODE_DIR as P, RULESYNC_SKILLS_RELATIVE_DIR_PATH as Pn, checkPathTraversal as Pt, RulesyncIgnore as Q, removeDirectory as Qt, CLAUDECODE_SKILLS_DIR_PATH as R, RULESYNC_XDG_CONFIG_HOME_DEFAULT_DIR_NAME as Rn, fileExists as Rt, COMMANDCODE_DIR as S, RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH as Sn, WarningCollectingLogger as St, CODEBUDDY_LOCAL_RULE_FILE_NAME as T, RULESYNC_IGNORE_RELATIVE_FILE_PATH as Tn, withFallbackLoggerTarget as Tt, getLocalSkillDirNames as U, formatError as Un, isFileNotFoundError as Ut, AUGMENTCODE_DIR as V, ALL_FEATURES_WITH_WILDCARD as Vn, getHomeDirectory as Vt, RulesyncSubagent as W, truncateText as Wn, isFileSystemError as Wt, RulesyncPermissions as X, stripHiddenCharacters as Xn, readFileContent as Xt, RulesyncRuleFrontmatterSchema as Y, stripControlCharactersKeepingLineFeeds as Yn, pathEscapesRoot as Yt, RulesyncMcp as Z, readFileContentOrNull as Zt, LETTACODE_DIR as _, RULESYNC_AIIGNORE_RELATIVE_FILE_PATH as _n, SourceEntrySchema as _t, getProcessorRegistryEntry as a, runWithDirectoryRollback as an, RulesyncCheck as at, CommandsProcessor as b, RULESYNC_CONFIG_RELATIVE_FILE_PATH as bn, ConsoleLogger as bt, RulesProcessor as c, writeFileBuffer as cn, loadYaml as ct, ELLIPSIS_WIDTH as d, ALL_TOOL_TARGETS_WITH_WILDCARD as dn, ConfigResolver as dt, removeFile as en, getRulesyncSourceCandidates as et, displayWidthOf as f, PACKAGING_TOOL_TARGETS as fn, mergeInputRootConfigs as ft, HooksProcessor as g, MAX_FILE_SIZE as gn, GITIGNORE_DESTINATION_KEY as gt, IgnoreProcessor as h, FEATURE_SOURCE_TREE_ENTRIES as hn, ConfigFileSchema as ht, inspectInputRoots as i, resolvedPathEscapesRoot as in, RulesyncCommandFrontmatterSchema as it, CODEXCLI_BASH_RULES_FILE_NAME as j, RULESYNC_PERMISSIONS_SCHEMA_URL as jn, assertDirectoryIfExists as jt, caseFoldIdentity as k, RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH as kn, ErrorCodes as kt, SubagentsProcessor as l, writeFileContent as ln, SHARED_USER_MANAGED_CONFIG_PATHS as lt, McpProcessor as m, CURATED_RULES_FEATURE_SUBDIR as mn, CONFLICTING_TARGET_PAIRS as mt, formatSourceLoadFailure as n, removeTempDirectory as nn, parseJsonc as nt, convertFromTool as o, splitPathSegments as on, RulesyncCheckFrontmatterSchema as ot, shortenToWidth as p, ToolTargetSchema as pn, resolveEffectiveInputRoots as pt, RulesyncSkillFrontmatterSchema as q, quoteForLog as qn, listFilePathsRecursively as qt, generate as r, resolvePath as rn, RulesyncCommand as rt, isPackagingToolTarget as s, toPosixPath as sn, stringifyFrontmatter as st, importFromTool as t, removeFileStrict as tn, resolveRulesyncSourceWritePath as tt, SkillsProcessor as u, ALL_TOOL_TARGETS as un, SKILL_FILE_NAME as ut, LETTACODE_SETTINGS_LOCAL_FILE_NAME as v, RULESYNC_CHECKS_RELATIVE_DIR_PATH as vn, assertTargetsFeaturesExclusive as vt, CODEBUDDY_DIR as w, RULESYNC_HOOKS_RELATIVE_FILE_PATH as wn, warnOnConflictingFlags as wt, REASONIX_LOCAL_RULE_FILE_NAME as x, RULESYNC_CONFIG_SCHEMA_URL as xn, JsonLogger as xt, CRUSH_LOCAL_RULE_FILE_NAME as y, RULESYNC_COMMANDS_RELATIVE_DIR_PATH as yn, findControlCharacter as yt, QWENCODE_DIR as z, parseCommaSeparatedList as zn, findFilesByGlobs as zt };
101734
102381
 
101735
- //# sourceMappingURL=import-DgKWnDni.js.map
102382
+ //# sourceMappingURL=import-DFwX4TTE.js.map