rulesync 33.0.0 → 34.0.1

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.
@@ -408,7 +408,11 @@ const DeprecatedIgnoreFeatureSchema = zod_mini.z.literal("ignore").check((0, zod
408
408
  const FeatureSchema = zod_mini.z.union([zod_mini.z.enum(ACTIVE_FEATURES), DeprecatedIgnoreFeatureSchema]);
409
409
  zod_mini.z.array(FeatureSchema);
410
410
  const FeatureWithWildcardSchema = zod_mini.z.union([zod_mini.z.enum([...ACTIVE_FEATURES, "*"]), DeprecatedIgnoreFeatureSchema]);
411
- const GitignoreDestinationSchema = zod_mini.z.enum(["gitignore", "gitattributes"]);
411
+ const GitignoreDestinationSchema = zod_mini.z.enum([
412
+ "gitignore",
413
+ "gitattributes",
414
+ "gitexclude"
415
+ ]);
412
416
  const FlattenedCommandNamingSchema = zod_mini.z.enum(["basename", "path"]);
413
417
  const FeatureOptionsSchema = zod_mini.z.record(zod_mini.z.string(), zod_mini.z.unknown());
414
418
  const FeatureValueSchema = zod_mini.z.union([
@@ -453,36 +457,36 @@ const isFeatureValueEnabled = (value) => {
453
457
  const parseCommaSeparatedList = (value) => value.split(",").map((s) => s.trim()).filter(Boolean);
454
458
  //#endregion
455
459
  //#region src/constants/rulesync-paths.ts
456
- const { join: join$417 } = node_path.posix;
460
+ const { join: join$418 } = node_path.posix;
457
461
  const RULESYNC_CONFIG_RELATIVE_FILE_PATH = "rulesync.jsonc";
458
462
  const RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH = "rulesync.local.jsonc";
459
463
  const RULESYNC_USER_CONFIG_DIR_NAME = "rulesync";
460
464
  const RULESYNC_XDG_CONFIG_HOME_DEFAULT_DIR_NAME = ".config";
461
465
  const RULESYNC_RELATIVE_DIR_PATH = ".rulesync";
462
466
  const RULES_FEATURE_SUBDIR = "rules";
463
- const CURATED_RULES_FEATURE_SUBDIR = join$417(RULES_FEATURE_SUBDIR, ".curated");
467
+ const CURATED_RULES_FEATURE_SUBDIR = join$418(RULES_FEATURE_SUBDIR, ".curated");
464
468
  const COMMANDS_FEATURE_SUBDIR = "commands";
465
469
  const SUBAGENTS_FEATURE_SUBDIR = "subagents";
466
470
  const CHECKS_FEATURE_SUBDIR = "checks";
467
471
  const SKILLS_FEATURE_SUBDIR = "skills";
468
- const CURATED_SKILLS_FEATURE_SUBDIR = join$417(SKILLS_FEATURE_SUBDIR, ".curated");
469
- const RULESYNC_RULES_RELATIVE_DIR_PATH = join$417(RULESYNC_RELATIVE_DIR_PATH, RULES_FEATURE_SUBDIR);
470
- const RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH = join$417(RULESYNC_RELATIVE_DIR_PATH, CURATED_RULES_FEATURE_SUBDIR);
471
- const RULESYNC_COMMANDS_RELATIVE_DIR_PATH = join$417(RULESYNC_RELATIVE_DIR_PATH, COMMANDS_FEATURE_SUBDIR);
472
- const RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH = join$417(RULESYNC_RELATIVE_DIR_PATH, SUBAGENTS_FEATURE_SUBDIR);
473
- const RULESYNC_CHECKS_RELATIVE_DIR_PATH = join$417(RULESYNC_RELATIVE_DIR_PATH, CHECKS_FEATURE_SUBDIR);
474
- const RULESYNC_MCP_RELATIVE_FILE_PATH = join$417(RULESYNC_RELATIVE_DIR_PATH, "mcp.jsonc");
475
- const RULESYNC_HOOKS_RELATIVE_FILE_PATH = join$417(RULESYNC_RELATIVE_DIR_PATH, "hooks.jsonc");
476
- const RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH = join$417(RULESYNC_RELATIVE_DIR_PATH, "permissions.jsonc");
477
- join$417(RULESYNC_RELATIVE_DIR_PATH, "mcp.json");
478
- const RULESYNC_HOOKS_LEGACY_RELATIVE_FILE_PATH = join$417(RULESYNC_RELATIVE_DIR_PATH, "hooks.json");
479
- const RULESYNC_PERMISSIONS_LEGACY_RELATIVE_FILE_PATH = join$417(RULESYNC_RELATIVE_DIR_PATH, "permissions.json");
472
+ const CURATED_SKILLS_FEATURE_SUBDIR = join$418(SKILLS_FEATURE_SUBDIR, ".curated");
473
+ const RULESYNC_RULES_RELATIVE_DIR_PATH = join$418(RULESYNC_RELATIVE_DIR_PATH, RULES_FEATURE_SUBDIR);
474
+ const RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH = join$418(RULESYNC_RELATIVE_DIR_PATH, CURATED_RULES_FEATURE_SUBDIR);
475
+ const RULESYNC_COMMANDS_RELATIVE_DIR_PATH = join$418(RULESYNC_RELATIVE_DIR_PATH, COMMANDS_FEATURE_SUBDIR);
476
+ const RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH = join$418(RULESYNC_RELATIVE_DIR_PATH, SUBAGENTS_FEATURE_SUBDIR);
477
+ const RULESYNC_CHECKS_RELATIVE_DIR_PATH = join$418(RULESYNC_RELATIVE_DIR_PATH, CHECKS_FEATURE_SUBDIR);
478
+ const RULESYNC_MCP_RELATIVE_FILE_PATH = join$418(RULESYNC_RELATIVE_DIR_PATH, "mcp.jsonc");
479
+ const RULESYNC_HOOKS_RELATIVE_FILE_PATH = join$418(RULESYNC_RELATIVE_DIR_PATH, "hooks.jsonc");
480
+ const RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH = join$418(RULESYNC_RELATIVE_DIR_PATH, "permissions.jsonc");
481
+ join$418(RULESYNC_RELATIVE_DIR_PATH, "mcp.json");
482
+ const RULESYNC_HOOKS_LEGACY_RELATIVE_FILE_PATH = join$418(RULESYNC_RELATIVE_DIR_PATH, "hooks.json");
483
+ const RULESYNC_PERMISSIONS_LEGACY_RELATIVE_FILE_PATH = join$418(RULESYNC_RELATIVE_DIR_PATH, "permissions.json");
480
484
  const RULESYNC_AIIGNORE_FILE_NAME = ".aiignore";
481
- const RULESYNC_AIIGNORE_RELATIVE_FILE_PATH = join$417(RULESYNC_RELATIVE_DIR_PATH, ".aiignore");
485
+ const RULESYNC_AIIGNORE_RELATIVE_FILE_PATH = join$418(RULESYNC_RELATIVE_DIR_PATH, ".aiignore");
482
486
  const RULESYNC_IGNORE_RELATIVE_FILE_PATH = ".rulesyncignore";
483
487
  const RULESYNC_OVERVIEW_FILE_NAME = "overview.md";
484
- const RULESYNC_SKILLS_RELATIVE_DIR_PATH = join$417(RULESYNC_RELATIVE_DIR_PATH, SKILLS_FEATURE_SUBDIR);
485
- const RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH = join$417(RULESYNC_RELATIVE_DIR_PATH, CURATED_SKILLS_FEATURE_SUBDIR);
488
+ const RULESYNC_SKILLS_RELATIVE_DIR_PATH = join$418(RULESYNC_RELATIVE_DIR_PATH, SKILLS_FEATURE_SUBDIR);
489
+ const RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH = join$418(RULESYNC_RELATIVE_DIR_PATH, CURATED_SKILLS_FEATURE_SUBDIR);
486
490
  const RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH = "rulesync.lock";
487
491
  const RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH = "rulesync-npm.lock.json";
488
492
  const RULESYNC_MCP_FILE_NAME = "mcp.jsonc";
@@ -528,6 +532,7 @@ const rulesProcessorToolTargetTuple = [
528
532
  "claudecode-legacy",
529
533
  "cline",
530
534
  "codebuddy",
535
+ "codebuff",
531
536
  "codewhale",
532
537
  "codexcli",
533
538
  "commandcode",
@@ -1407,6 +1412,13 @@ async function getFileSize(filepath) {
1407
1412
  throw new Error(`Failed to get file size for "${filepath}": ${formatError(error)}`, { cause: error });
1408
1413
  }
1409
1414
  }
1415
+ /**
1416
+ * Whether a file carries any execute bit. Windows reports none, so a file
1417
+ * there is never seen as executable.
1418
+ */
1419
+ async function hasExecutableBit(filepath) {
1420
+ return ((await (0, node_fs_promises.stat)(filepath)).mode & 73) !== 0;
1421
+ }
1410
1422
  async function isSymlink(filepath) {
1411
1423
  try {
1412
1424
  return (await (0, node_fs_promises.lstat)(filepath)).isSymbolicLink();
@@ -3179,6 +3191,9 @@ var Config = class Config {
3179
3191
  const featureValue = value[feature];
3180
3192
  if (featureValue && typeof featureValue === "object" && isFeatureValueEnabled(featureValue)) return featureValue;
3181
3193
  }
3194
+ getRootGitignoreDestination() {
3195
+ return this.gitignoreDestination;
3196
+ }
3182
3197
  getGitignoreDestination(target, feature) {
3183
3198
  const rootLevel = this.gitignoreDestination;
3184
3199
  if (!isRulesyncConfigTargetsObject(this.targets)) return rootLevel;
@@ -3196,7 +3211,8 @@ var Config = class Config {
3196
3211
  return toolLevel ?? rootLevel;
3197
3212
  }
3198
3213
  static parseGitignoreDestination(value) {
3199
- if (value === "gitignore" || value === "gitattributes") return value;
3214
+ const result = GitignoreDestinationSchema.safeParse(value);
3215
+ return result.success ? result.data : void 0;
3200
3216
  }
3201
3217
  /**
3202
3218
  * Check if per-target features configuration is being used.
@@ -5266,12 +5282,20 @@ const JUNIE_HOOK_EVENTS = [
5266
5282
  "sessionEnd"
5267
5283
  ];
5268
5284
  /**
5269
- * Hook events supported by the GitLab Duo CLI. Only `SessionStart` is
5270
- * documented; its optional regex matcher filters on the session source
5271
- * (`startup`, `resume`). Only `type: "command"` hooks are supported.
5285
+ * Hook events supported by the GitLab Duo CLI. `SessionStart` is documented;
5286
+ * `PreToolUse` and `SessionEnd` ship in gitlab-lsp (`HOOK_EVENT_NAMES`, v9.28.0+)
5287
+ * but are not on docs.gitlab.com yet. The optional regex matcher filters on the
5288
+ * session source (`startup`, `resume`) for `SessionStart`, on `tool_name` for
5289
+ * `PreToolUse`, and on the end reason for `SessionEnd`. Only `type: "command"`
5290
+ * hooks are supported.
5272
5291
  * @see https://docs.gitlab.com/user/gitlab_duo_cli/customize/
5292
+ * @see https://gitlab.com/gitlab-org/editor-extensions/gitlab-lsp/-/blob/main/packages/core/hooks/src/schema.ts
5273
5293
  */
5274
- const GITLABDUO_HOOK_EVENTS = ["sessionStart"];
5294
+ const GITLABDUO_HOOK_EVENTS = [
5295
+ "sessionStart",
5296
+ "preToolUse",
5297
+ "sessionEnd"
5298
+ ];
5275
5299
  /**
5276
5300
  * Hook events supported by Qwen Code.
5277
5301
  *
@@ -5612,7 +5636,10 @@ const CORTEXCODE_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CA
5612
5636
  * or `agent` hooks with a `timeout` in seconds. The hooks reference documents ten events; the
5613
5637
  * plugins reference lists the full set, stating that plugin hooks "respond to
5614
5638
  * the same lifecycle events as user-defined hooks", so every event listed
5615
- * there is supported here.
5639
+ * there is supported here. The hooks reference also names `Setup`
5640
+ * (bootstrap/maintenance), and the published package's settings schema
5641
+ * (`@tencent-ai/codebuddy-code` 2.167.0) accepts `UserPromptExpansion`, whose
5642
+ * matcher filters on `command_name` as in Claude Code.
5616
5643
  *
5617
5644
  * @see https://www.codebuddy.ai/docs/cli/hooks
5618
5645
  * @see https://www.codebuddy.ai/docs/cli/plugins-reference
@@ -5643,7 +5670,9 @@ const CODEBUDDY_HOOK_EVENTS = [
5643
5670
  "postCompact",
5644
5671
  "elicitation",
5645
5672
  "elicitationResult",
5646
- "sessionEnd"
5673
+ "sessionEnd",
5674
+ "setup",
5675
+ "userPromptExpansion"
5647
5676
  ];
5648
5677
  /**
5649
5678
  * CodeBuddy Code events that fire on every occurrence and take no `matcher`.
@@ -5692,7 +5721,9 @@ const CANONICAL_TO_CODEBUDDY_EVENT_NAMES = {
5692
5721
  postCompact: "PostCompact",
5693
5722
  elicitation: "Elicitation",
5694
5723
  elicitationResult: "ElicitationResult",
5695
- sessionEnd: "SessionEnd"
5724
+ sessionEnd: "SessionEnd",
5725
+ setup: "Setup",
5726
+ userPromptExpansion: "UserPromptExpansion"
5696
5727
  };
5697
5728
  const CODEBUDDY_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_CODEBUDDY_EVENT_NAMES).map(([k, v]) => [v, k]));
5698
5729
  /**
@@ -6546,7 +6577,11 @@ const KIRO_IDE_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANO
6546
6577
  /**
6547
6578
  * Map canonical camelCase event names to GitLab Duo CLI PascalCase.
6548
6579
  */
6549
- const CANONICAL_TO_GITLABDUO_EVENT_NAMES = { sessionStart: "SessionStart" };
6580
+ const CANONICAL_TO_GITLABDUO_EVENT_NAMES = {
6581
+ sessionStart: "SessionStart",
6582
+ preToolUse: "PreToolUse",
6583
+ sessionEnd: "SessionEnd"
6584
+ };
6550
6585
  /**
6551
6586
  * Map GitLab Duo CLI PascalCase event names to canonical camelCase.
6552
6587
  */
@@ -8287,6 +8322,48 @@ const ReasonixPermissionsOverrideSchema = zod_mini.z.looseObject({
8287
8322
  ]))
8288
8323
  });
8289
8324
  /**
8325
+ * Tool-scoped override block for Qoder. `outputStyle` is written to the
8326
+ * top-level `outputStyle` key of `.qoder/settings.json` (project) /
8327
+ * `~/.qoder/settings.json` (global): the name of the active output style, a
8328
+ * custom style from `.qoder/output-styles/` (see the `qoder.channel:
8329
+ * output-style` rule option) or a `plugin-name:style-name` reference. The
8330
+ * shared `permission` block continues to drive `permissions.allow`/`ask`/`deny`.
8331
+ *
8332
+ * @see https://docs.qoder.com/cli/output-styles
8333
+ * @see https://docs.qoder.com/cli/settings-reference
8334
+ * @example
8335
+ * { "outputStyle": "concise-review" }
8336
+ */
8337
+ const QoderPermissionsOverrideSchema = zod_mini.z.looseObject({
8338
+ permission: zod_mini.z.optional(ToolScopedPermissionSchema),
8339
+ outputStyle: zod_mini.z.optional(zod_mini.z.string().check(zod_mini.z.trim(), zod_mini.z.minLength(1)))
8340
+ });
8341
+ /**
8342
+ * One native Factory Droid permission rule. `match.prefix` positions are either
8343
+ * a single token or a non-empty list of alternative tokens.
8344
+ *
8345
+ * @see https://docs.factory.com/autonomy-and-safety/permission-rules
8346
+ */
8347
+ const FactorydroidPermissionRuleSchema = zod_mini.z.looseObject({
8348
+ id: zod_mini.z.string(),
8349
+ decision: zod_mini.z.enum([
8350
+ "allow",
8351
+ "ask",
8352
+ "block"
8353
+ ]),
8354
+ match: zod_mini.z.looseObject({ prefix: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.array(zod_mini.z.string()).check(zod_mini.z.minLength(1))])).check(zod_mini.z.minLength(1))) }),
8355
+ tests: zod_mini.z.optional(zod_mini.z.looseObject({
8356
+ match: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
8357
+ noMatch: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string()))
8358
+ })),
8359
+ reason: zod_mini.z.optional(zod_mini.z.string()),
8360
+ enabled: zod_mini.z.optional(zod_mini.z.boolean())
8361
+ });
8362
+ const FactorydroidPermissionRulesSchema = zod_mini.z.looseObject({
8363
+ version: zod_mini.z.number(),
8364
+ rules: zod_mini.z.array(FactorydroidPermissionRuleSchema)
8365
+ });
8366
+ /**
8290
8367
  * Tool-scoped override block for Factory Droid. Factory Droid's `settings.json`
8291
8368
  * exposes security controls with no canonical per-command allow/ask/deny slot —
8292
8369
  * `commandBlocklist` (a hard-block tier that can never be approved, distinct from
@@ -8294,7 +8371,8 @@ const ReasonixPermissionsOverrideSchema = zod_mini.z.looseObject({
8294
8371
  * (`enabled`/`mode`/`filesystem`/`network`), `mcpPolicy`,
8295
8372
  * `mcpAutonomyOverrides` (per-MCP-tool autonomy levels), `enableDroidShield`,
8296
8373
  * and autonomy settings (`sessionDefaultSettings`, `maxAutonomyLevel`,
8297
- * `subagentAutonomyLevel`, `interactionMode`). Fields placed here are merged
8374
+ * `subagentAutonomyLevel`, `interactionMode`), and the native versioned
8375
+ * `permissionRules` (allow/ask/block). Fields placed here are merged
8298
8376
  * into `settings.json` and
8299
8377
  * emitted only for Factory Droid, while the shared `permission` block continues
8300
8378
  * to drive `commandAllowlist`/`commandDenylist`. Kept `looseObject` passthrough.
@@ -8305,7 +8383,8 @@ const ReasonixPermissionsOverrideSchema = zod_mini.z.looseObject({
8305
8383
  const FactorydroidPermissionsOverrideSchema = zod_mini.z.looseObject({
8306
8384
  permission: zod_mini.z.optional(ToolScopedPermissionSchema),
8307
8385
  commandBlocklist: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
8308
- disabledSkills: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string()))
8386
+ disabledSkills: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
8387
+ permissionRules: zod_mini.z.optional(FactorydroidPermissionRulesSchema)
8309
8388
  });
8310
8389
  /**
8311
8390
  * Tool-scoped override block for Warp. Warp's `[agents.profiles]` table exposes
@@ -9110,6 +9189,7 @@ const PermissionsConfigSchema = zod_mini.z.looseObject({
9110
9189
  codewhale: zod_mini.z.optional(CodewhalePermissionsOverrideSchema),
9111
9190
  zed: zod_mini.z.optional(ZedPermissionsOverrideSchema),
9112
9191
  devin: zod_mini.z.optional(DevinPermissionsOverrideSchema),
9192
+ qoder: zod_mini.z.optional(QoderPermissionsOverrideSchema),
9113
9193
  "antigravity-ide": zod_mini.z.optional(CanonicalPermissionsOverrideSchema),
9114
9194
  continue: zod_mini.z.optional(CanonicalPermissionsOverrideSchema),
9115
9195
  copilot: zod_mini.z.optional(CopilotPermissionsOverrideSchema),
@@ -9121,7 +9201,6 @@ const PermissionsConfigSchema = zod_mini.z.looseObject({
9121
9201
  goose: zod_mini.z.optional(CanonicalPermissionsOverrideSchema),
9122
9202
  omp: zod_mini.z.optional(OmpPermissionsOverrideSchema),
9123
9203
  pool: zod_mini.z.optional(CanonicalPermissionsOverrideSchema),
9124
- qoder: zod_mini.z.optional(CanonicalPermissionsOverrideSchema),
9125
9204
  grokcli: zod_mini.z.optional(CanonicalPermissionsOverrideSchema),
9126
9205
  "kimi-code": zod_mini.z.optional(KimiCodePermissionsOverrideSchema),
9127
9206
  roo: zod_mini.z.optional(CanonicalPermissionsOverrideSchema),
@@ -9642,7 +9721,9 @@ const RulesyncRuleFrontmatterSchema = zod_mini.z.object({
9642
9721
  qoder: zod_mini.z.optional(zod_mini.z.looseObject({
9643
9722
  trigger: zod_mini.z.optional(zod_mini.z.string()),
9644
9723
  glob: zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.array(zod_mini.z.string())])),
9645
- description: zod_mini.z.optional(zod_mini.z.string())
9724
+ description: zod_mini.z.optional(zod_mini.z.string()),
9725
+ channel: zod_mini.z.optional(zod_mini.z.enum(["output-style"])),
9726
+ name: zod_mini.z.optional(zod_mini.z.string().check(zod_mini.z.trim(), zod_mini.z.minLength(1)))
9646
9727
  })),
9647
9728
  augmentcode: zod_mini.z.optional(zod_mini.z.looseObject({
9648
9729
  type: zod_mini.z.optional(zod_mini.z.string()),
@@ -13011,7 +13092,7 @@ const SHARED_CONFIG_OWNERSHIP = {
13011
13092
  },
13012
13093
  permissions: {
13013
13094
  kind: "replace-owned-keys",
13014
- ownedKeys: ["permissions"]
13095
+ ownedKeys: ["permissions", "outputStyle"]
13015
13096
  }
13016
13097
  }
13017
13098
  },
@@ -22425,6 +22506,7 @@ const QODER_DIR = ".qoder";
22425
22506
  const QODER_RULE_FILE_NAME = "AGENTS.md";
22426
22507
  const QODER_LOCAL_RULE_FILE_NAME = "AGENTS.local.md";
22427
22508
  const QODER_RULES_DIR_PATH = (0, node_path.join)(QODER_DIR, "rules");
22509
+ const QODER_OUTPUT_STYLES_DIR_PATH = (0, node_path.join)(QODER_DIR, "output-styles");
22428
22510
  const QODER_SETTINGS_FILE_NAME = "settings.json";
22429
22511
  const QODER_PROJECT_MCP_FILE_NAME = ".mcp.json";
22430
22512
  const QODER_COMMANDS_DIR_PATH = (0, node_path.join)(QODER_DIR, "commands");
@@ -29799,6 +29881,11 @@ const CORTEXCODE_SPEC = {
29799
29881
  canonical: "enabled",
29800
29882
  tool: "enabled"
29801
29883
  }],
29884
+ stringPassthroughFields: [{
29885
+ canonical: "statusMessage",
29886
+ tool: "statusMessage",
29887
+ commandOnly: true
29888
+ }],
29802
29889
  objectPassthroughFields: [{
29803
29890
  canonical: "source",
29804
29891
  tool: "source",
@@ -29813,7 +29900,7 @@ const CORTEXCODE_SPEC = {
29813
29900
  * (project scope) and of the dedicated `~/.snowflake/cortex/hooks.json` (user
29814
29901
  * scope), both in the Claude-Code shape: `{ "<Event>": [{ "matcher"?:
29815
29902
  * "<regex>", "hooks": [{ "type": "command" | "prompt", ..., "timeout"?:
29816
- * <seconds>, "enabled"?: <boolean>, "source"?: { "source", "ref"? } }] }] }`.
29903
+ * <seconds>, "enabled"?: <boolean>, "statusMessage"?: <string> (command only), "source"?: { "source", "ref"? } }] }] }`.
29817
29904
  * The project file also holds settings rulesync does not own, so generation
29818
29905
  * merges the `hooks` key into
29819
29906
  * either file (see `SHARED_CONFIG_OWNERSHIP`) instead of overwriting it, and
@@ -30752,7 +30839,8 @@ const FACTORYDROID_OVERRIDE_KEYS = [
30752
30839
  "allowManagedHooksOnly",
30753
30840
  "disabledSkills",
30754
30841
  "modelPolicy",
30755
- "missionPolicy"
30842
+ "missionPolicy",
30843
+ "permissionRules"
30756
30844
  ];
30757
30845
  //#endregion
30758
30846
  //#region src/utils/factorydroid-settings.ts
@@ -30967,7 +31055,7 @@ const GITLABDUO_CONVERTER_CONFIG = {
30967
31055
  *
30968
31056
  * Hooks live in a dedicated file, `<project>/.gitlab/duo/hooks.json` (project,
30969
31057
  * opt-in via `--enable-project-hooks` / `GITLAB_ENABLE_PROJECT_HOOKS=true`) or
30970
- * `~/.gitlab/duo/hooks.json` (user), shaped as `{ "hooks": { "SessionStart": [...] } }`.
31058
+ * `~/.gitlab/duo/hooks.json` (user), shaped as `{ "hooks": { "SessionStart": [...], "PreToolUse": [...] } }`.
30971
31059
  * rulesync owns the whole file, so it is regenerated and deleted as a unit.
30972
31060
  */
30973
31061
  var GitlabduoHooks = class GitlabduoHooks extends ToolHooks {
@@ -36621,6 +36709,7 @@ var ClineIgnore = class ClineIgnore extends ToolIgnore {
36621
36709
  const CODEBUFF_IGNORE_FILE_NAME = ".codebuffignore";
36622
36710
  const CODEBUFF_AGENTS_DIR = ".agents";
36623
36711
  const CODEBUFF_MCP_FILE_NAME = "mcp.json";
36712
+ const CODEBUFF_GLOBAL_RULE_FILE_NAME = ".AGENTS.md";
36624
36713
  //#endregion
36625
36714
  //#region src/features/ignore/codebuff-ignore.ts
36626
36715
  /**
@@ -43522,11 +43611,45 @@ const CANONICAL_TO_GITLABDUO_TRANSPORT = {
43522
43611
  sse: "sse"
43523
43612
  };
43524
43613
  /**
43614
+ * Map the canonical `timeout` (milliseconds) onto GitLab Duo's `timeoutMs`, a
43615
+ * per-server tool-call inactivity timeout (gitlab-lsp v9.25.0+) that must be a
43616
+ * positive integer. An explicit `timeoutMs` on the same server wins; a
43617
+ * `timeout` Duo would reject is dropped with a warning.
43618
+ *
43619
+ * @see https://gitlab.com/gitlab-org/editor-extensions/gitlab-lsp/-/commit/e8c4a76a66e8f37d4c97fc03ffe983bfadf43f76
43620
+ */
43621
+ function toGitlabduoTimeout({ serverName, server, logger }) {
43622
+ const { timeout, ...rest } = server;
43623
+ if (timeout === void 0 || rest.timeoutMs !== void 0) return rest;
43624
+ if (typeof timeout !== "number" || !Number.isInteger(timeout) || timeout <= 0) {
43625
+ logger?.warn(`GitLab Duo CLI MCP server "${serverName}": dropping timeout ${JSON.stringify(timeout)}, as timeoutMs must be a positive integer of milliseconds.`);
43626
+ return rest;
43627
+ }
43628
+ return {
43629
+ ...rest,
43630
+ timeoutMs: timeout
43631
+ };
43632
+ }
43633
+ /**
43634
+ * Read GitLab Duo's `timeoutMs` back into the canonical `timeout`
43635
+ * (milliseconds). A server that already carries a `timeout` is left as is, so
43636
+ * both keys round-trip and `timeoutMs` keeps winning on the next generate.
43637
+ */
43638
+ function fromGitlabduoTimeout(server) {
43639
+ if (typeof server !== "object" || server === null || Array.isArray(server)) return server;
43640
+ const { timeoutMs, ...rest } = server;
43641
+ if (timeoutMs === void 0 || rest.timeout !== void 0) return server;
43642
+ return {
43643
+ ...rest,
43644
+ timeout: timeoutMs
43645
+ };
43646
+ }
43647
+ /**
43525
43648
  * Convert one canonical server into GitLab Duo's shape. Every documented
43526
43649
  * example spells the transport out as `type` (`stdio`, `http` or `sse`), so it
43527
43650
  * is always written, inferred from `command` / `url` when the source leaves it
43528
- * implicit. Every other field — `args`, `env`, `approvedTools`, ... — is carried
43529
- * through as authored.
43651
+ * implicit. The canonical `timeout` becomes `timeoutMs`; every other field —
43652
+ * `args`, `env`, `approvedTools`, ... — is carried through as authored.
43530
43653
  */
43531
43654
  function toGitlabduoServer({ serverName, serverConfig, logger }) {
43532
43655
  if (declaresNoTransport(serverConfig)) return warnAndSkipMcpServer({
@@ -43535,7 +43658,7 @@ function toGitlabduoServer({ serverName, serverConfig, logger }) {
43535
43658
  reason: "no command or url",
43536
43659
  logger
43537
43660
  });
43538
- const { type, transport, httpUrl: _httpUrl, url: _url, ...rest } = serverConfig;
43661
+ const { type, transport, httpUrl: _httpUrl, url: _url, ...others } = serverConfig;
43539
43662
  const declared = type ?? transport;
43540
43663
  const mapped = declared ? CANONICAL_TO_GITLABDUO_TRANSPORT[declared] : isRemoteMcpServer(serverConfig) ? "http" : "stdio";
43541
43664
  if (!mapped) return warnAndSkipMcpServer({
@@ -43544,8 +43667,13 @@ function toGitlabduoServer({ serverName, serverConfig, logger }) {
43544
43667
  reason: `the unsupported transport "${declared}" (GitLab Duo CLI accepts stdio, http and sse)`,
43545
43668
  logger
43546
43669
  });
43670
+ const rest = toGitlabduoTimeout({
43671
+ serverName,
43672
+ server: others,
43673
+ logger
43674
+ });
43547
43675
  if (mapped === "stdio") {
43548
- const { command, args } = rest;
43676
+ const { command, args } = serverConfig;
43549
43677
  if (Array.isArray(command)) {
43550
43678
  const [executable, ...leadingArgs] = command;
43551
43679
  return {
@@ -43634,7 +43762,13 @@ var GitlabduoMcp = class GitlabduoMcp extends ToolMcp {
43634
43762
  });
43635
43763
  }
43636
43764
  toRulesyncMcp() {
43637
- return this.toRulesyncMcpDefault();
43765
+ const { mcpServers, ...others } = this.json;
43766
+ if (typeof mcpServers !== "object" || mcpServers === null || Array.isArray(mcpServers)) return this.toRulesyncMcpDefault();
43767
+ const servers = Object.fromEntries(Object.entries(mcpServers).map(([name, server]) => [name, fromGitlabduoTimeout(server)]));
43768
+ return this.toRulesyncMcpDefault({ fileContent: JSON.stringify({
43769
+ ...others,
43770
+ mcpServers: servers
43771
+ }) });
43638
43772
  }
43639
43773
  validate() {
43640
43774
  return {
@@ -46741,6 +46875,15 @@ const CONSUMED_CANONICAL_KEYS = /* @__PURE__ */ new Set([
46741
46875
  ]);
46742
46876
  const MILLISECONDS_PER_SECOND = 1e3;
46743
46877
  /**
46878
+ * The only keys a project override entry for a user-level server carries:
46879
+ * Pi reads a `.pi/mcp.json` entry without `command`, `url` or `type` as an
46880
+ * override of `enabled`, `exposure` and `toolExposure` of the user-level server
46881
+ * with the same name, and keeps the rest of that server.
46882
+ *
46883
+ * @see https://github.com/earendil-works/pi/blob/v1.1.0/packages/coding-agent/docs/mcp.md
46884
+ */
46885
+ const PI_OVERRIDE_PASSTHROUGH_KEYS = ["exposure", "toolExposure"];
46886
+ /**
46744
46887
  * Pi rejects a server whose name has any other character, and treats names
46745
46888
  * that differ only in `-` and `_` as one server (its tools share the
46746
46889
  * `mcp__<name>` namespace with `-` replaced by `_`), rejecting the second.
@@ -46776,17 +46919,12 @@ function hasUnexpandedEnvVarRef(serverConfig) {
46776
46919
  /**
46777
46920
  * The transport fields Pi reads for one server: `type` `stdio` with a string
46778
46921
  * `command` plus `args`, or `type` `http` with a `url`. A server Pi cannot
46779
- * start or reach — no transport, a remote transport without a URL, an SSE or
46780
- * WebSocket server, or a stdio entry without a command — is rejected by Pi, so
46781
- * it is skipped with a warning (`null`).
46922
+ * start or reach — a remote transport without a URL, an SSE or WebSocket
46923
+ * server, or a stdio entry without a command — is rejected by Pi, so it is
46924
+ * skipped with a warning (`null`). A server with no transport at all is a
46925
+ * project override instead (see `convertOverrideToPi`).
46782
46926
  */
46783
46927
  function convertTransportToPi({ serverName, serverConfig, logger }) {
46784
- if (declaresNoTransport(serverConfig)) return warnAndSkipMcpServer({
46785
- toolName: "Pi",
46786
- serverName,
46787
- reason: "no transport",
46788
- logger
46789
- });
46790
46928
  if (isRemoteMcpServer(serverConfig)) {
46791
46929
  const url = resolveRemoteMcpUrl(serverConfig);
46792
46930
  const stated = serverConfig.type ?? serverConfig.transport;
@@ -46827,56 +46965,117 @@ function convertTransportToPi({ serverName, serverConfig, logger }) {
46827
46965
  };
46828
46966
  }
46829
46967
  /**
46830
- * Convert the canonical server map to Pi's `mcpServers` shape. Besides the
46831
- * transport fields, Pi switches a server off with `enabled: false` and reads
46832
- * `timeout` in seconds (canonical: milliseconds). A server name Pi rejects is
46833
- * skipped with a warning, and so is `auth` in the project file, which Pi
46834
- * accepts only in the global one (it drops the whole server otherwise).
46968
+ * A transport-less server in the project file becomes a Pi override entry for
46969
+ * the user-level server of the same name. `disabled` maps to `enabled` both
46970
+ * ways (an explicit `disabled: false` is "Enable in this project"), and any key
46971
+ * Pi ignores on an override is dropped with a warning. The global file has no
46972
+ * layer below it to override, so the entry is skipped there.
46973
+ */
46974
+ function convertOverrideToPi({ serverName, serverConfig, global, logger }) {
46975
+ if (global) return warnAndSkipMcpServer({
46976
+ toolName: "Pi",
46977
+ serverName,
46978
+ reason: "no transport",
46979
+ logger
46980
+ });
46981
+ const record = serverConfig;
46982
+ const override = {};
46983
+ if (typeof serverConfig.disabled === "boolean") override.enabled = !serverConfig.disabled;
46984
+ for (const key of PI_OVERRIDE_PASSTHROUGH_KEYS) if (record[key] !== void 0) override[key] = record[key];
46985
+ if (Object.keys(override).length === 0) return warnAndSkipMcpServer({
46986
+ toolName: "Pi",
46987
+ serverName,
46988
+ reason: "no transport and nothing a Pi project override sets (disabled, exposure, toolExposure)",
46989
+ logger
46990
+ });
46991
+ const droppedKeys = Object.keys(record).filter((key) => key !== "disabled" && !PI_OVERRIDE_PASSTHROUGH_KEYS.includes(key) && !PROTOTYPE_POLLUTION_KEYS.has(key));
46992
+ 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.`);
46993
+ return override;
46994
+ }
46995
+ /**
46996
+ * Whether Pi rejects the server name, either outright or because an earlier
46997
+ * server already took its `mcp__<name>` namespace; warns when it does.
46998
+ */
46999
+ function isRejectedServerName({ serverName, namespaces, logger }) {
47000
+ if (!PI_SERVER_NAME_PATTERN.test(serverName)) {
47001
+ warnAndSkipMcpServer({
47002
+ toolName: "Pi",
47003
+ serverName,
47004
+ reason: "a name Pi rejects (use letters, digits, \"_\" and \"-\")",
47005
+ logger
47006
+ });
47007
+ return true;
47008
+ }
47009
+ const clash = namespaces.get(toPiNamespace(serverName));
47010
+ if (clash !== void 0) {
47011
+ warnAndSkipMcpServer({
47012
+ toolName: "Pi",
47013
+ serverName,
47014
+ reason: `a name Pi treats as the same server as ${quoteValueForWarning(clash)} ("-" and "_" are equivalent)`,
47015
+ logger
47016
+ });
47017
+ return true;
47018
+ }
47019
+ return false;
47020
+ }
47021
+ /**
47022
+ * Convert one canonical server to its Pi entry, or `null` when it is skipped.
47023
+ * Besides the transport fields, Pi switches a server off with `enabled: false`
47024
+ * and reads `timeout` in seconds (canonical: milliseconds). `auth` is dropped
47025
+ * with a warning from the project file, since Pi accepts it only in the global
47026
+ * one (it drops the whole server otherwise). A server without a transport is a
47027
+ * project override entry instead.
47028
+ */
47029
+ function convertServerToPi({ serverName, serverConfig, global, logger }) {
47030
+ if (declaresNoTransport(serverConfig)) return convertOverrideToPi({
47031
+ serverName,
47032
+ serverConfig,
47033
+ global,
47034
+ logger
47035
+ });
47036
+ const converted = convertTransportToPi({
47037
+ serverName,
47038
+ serverConfig,
47039
+ logger
47040
+ });
47041
+ if (!converted) return null;
47042
+ for (const [key, value] of Object.entries(serverConfig)) {
47043
+ if (CONSUMED_CANONICAL_KEYS.has(key) || PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
47044
+ converted[key] = value;
47045
+ }
47046
+ if (serverConfig.timeout !== void 0) {
47047
+ if (Number.isFinite(serverConfig.timeout) && serverConfig.timeout > 0) converted.timeout = serverConfig.timeout / MILLISECONDS_PER_SECOND;
47048
+ else logger?.warn(`Pi MCP: dropping the "timeout" of ${quoteValueForWarning(serverName)}: expected a positive number of milliseconds.`);
47049
+ }
47050
+ if (serverConfig.disabled === true) converted.enabled = false;
47051
+ if (!global && "auth" in converted) {
47052
+ delete converted.auth;
47053
+ logger?.warn(`Pi MCP: dropping the "auth" of ${quoteValueForWarning(serverName)}: Pi accepts it only in the global ~/.pi/agent/mcp.json.`);
47054
+ }
47055
+ 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.`);
47056
+ return converted;
47057
+ }
47058
+ /**
47059
+ * Convert the canonical server map to Pi's `mcpServers` shape. A server name
47060
+ * Pi rejects is skipped with a warning.
46835
47061
  */
46836
47062
  function convertToPiFormat({ mcpServers, global, logger }) {
46837
47063
  const result = {};
46838
47064
  const namespaces = /* @__PURE__ */ new Map();
46839
47065
  for (const [serverName, serverConfig] of Object.entries(mcpServers)) {
46840
47066
  if (PROTOTYPE_POLLUTION_KEYS.has(serverName) || !isRecord$1(serverConfig)) continue;
46841
- if (!PI_SERVER_NAME_PATTERN.test(serverName)) {
46842
- warnAndSkipMcpServer({
46843
- toolName: "Pi",
46844
- serverName,
46845
- reason: "a name Pi rejects (use letters, digits, \"_\" and \"-\")",
46846
- logger
46847
- });
46848
- continue;
46849
- }
46850
- const clash = namespaces.get(toPiNamespace(serverName));
46851
- if (clash !== void 0) {
46852
- warnAndSkipMcpServer({
46853
- toolName: "Pi",
46854
- serverName,
46855
- reason: `a name Pi treats as the same server as ${quoteValueForWarning(clash)} ("-" and "_" are equivalent)`,
46856
- logger
46857
- });
46858
- continue;
46859
- }
46860
- const converted = convertTransportToPi({
47067
+ if (isRejectedServerName({
47068
+ serverName,
47069
+ namespaces,
47070
+ logger
47071
+ })) continue;
47072
+ const converted = convertServerToPi({
46861
47073
  serverName,
46862
47074
  serverConfig,
47075
+ global,
46863
47076
  logger
46864
47077
  });
46865
47078
  if (!converted) continue;
46866
- for (const [key, value] of Object.entries(serverConfig)) {
46867
- if (CONSUMED_CANONICAL_KEYS.has(key) || PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
46868
- converted[key] = value;
46869
- }
46870
- if (serverConfig.timeout !== void 0) {
46871
- if (Number.isFinite(serverConfig.timeout) && serverConfig.timeout > 0) converted.timeout = serverConfig.timeout / MILLISECONDS_PER_SECOND;
46872
- else logger?.warn(`Pi MCP: dropping the "timeout" of ${quoteValueForWarning(serverName)}: expected a positive number of milliseconds.`);
46873
- }
46874
- if (serverConfig.disabled === true) converted.enabled = false;
46875
- if (!global && "auth" in converted) {
46876
- delete converted.auth;
46877
- logger?.warn(`Pi MCP: dropping the "auth" of ${quoteValueForWarning(serverName)}: Pi accepts it only in the global ~/.pi/agent/mcp.json.`);
46878
- }
46879
- 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.`);
46880
47079
  namespaces.set(toPiNamespace(serverName), serverName);
46881
47080
  result[serverName] = converted;
46882
47081
  }
@@ -46885,16 +47084,19 @@ function convertToPiFormat({ mcpServers, global, logger }) {
46885
47084
  /**
46886
47085
  * Convert Pi's `mcpServers` back to the canonical shape: `enabled: false`
46887
47086
  * becomes `disabled: true` and `timeout` (seconds) becomes milliseconds; every
46888
- * other key is already canonical.
47087
+ * other key is already canonical. On a transport-less override entry an
47088
+ * explicit `enabled: true` is kept as `disabled: false`, since it switches on a
47089
+ * user-level server the user turned off.
46889
47090
  */
46890
47091
  function convertFromPiFormat(mcpServers) {
46891
47092
  const result = {};
46892
47093
  for (const [serverName, serverConfig] of Object.entries(mcpServers)) {
46893
47094
  if (PROTOTYPE_POLLUTION_KEYS.has(serverName) || !isRecord$1(serverConfig)) continue;
46894
47095
  const { enabled, timeout, ...rest } = serverConfig;
46895
- const converted = enabled === false ? {
47096
+ const keepsEnabled = enabled === true && declaresNoTransport(rest);
47097
+ const converted = enabled === false || keepsEnabled ? {
46896
47098
  ...rest,
46897
- disabled: true
47099
+ disabled: !enabled
46898
47100
  } : rest;
46899
47101
  if (typeof timeout === "number") converted.timeout = timeout * MILLISECONDS_PER_SECOND;
46900
47102
  else if (timeout !== void 0) converted.timeout = timeout;
@@ -46970,8 +47172,11 @@ var PiMcp = class PiMcp extends ToolMcp {
46970
47172
  });
46971
47173
  }
46972
47174
  toRulesyncMcp() {
46973
- const mcpServers = isMcpServers(this.json.mcpServers) ? this.json.mcpServers : {};
46974
- return this.toRulesyncMcpDefault({ fileContent: JSON.stringify({ mcpServers: convertFromPiFormat(mcpServers) }, null, 2) });
47175
+ const { shared, toolOnly } = splitMcpServersByTransport(convertFromPiFormat(isMcpServers(this.json.mcpServers) ? this.json.mcpServers : {}));
47176
+ return this.toRulesyncMcpDefault({ fileContent: JSON.stringify({
47177
+ mcpServers: shared,
47178
+ ...Object.keys(toolOnly).length > 0 && { pi: { mcpServers: toolOnly } }
47179
+ }, null, 2) });
46975
47180
  }
46976
47181
  validate() {
46977
47182
  return {
@@ -64764,9 +64969,11 @@ const DEFAULT_TOOLS_KEY = "defaultTools";
64764
64969
  * the built-in tools enabled at startup (v0.84.2) — authored through the `pi`
64765
64970
  * permissions override.
64766
64971
  *
64767
- * Scope semantics are the **opposite** of the union-merge most targets use:
64768
- * a project `defaultTools` array *replaces* the global array rather than adding
64769
- * to it, so the two scopes are written independently and never combined.
64972
+ * Scope layering is Pi's own rather than the union-merge most targets use:
64973
+ * a project `defaultTools` array with a plain name *replaces* the global
64974
+ * array, while one made only of `+name` / `-name` entries (Pi v0.99.0+) changes
64975
+ * the global selection instead. Entries are written verbatim, so the two
64976
+ * scopes are written independently and never combined by rulesync.
64770
64977
  * Project scope writes `.pi/settings.json`; global scope writes
64771
64978
  * `~/.pi/agent/settings.json`.
64772
64979
  *
@@ -65426,6 +65633,13 @@ function convertPoolToRulesync({ tools, paths }) {
65426
65633
  //#region src/features/permissions/qoder-permissions.ts
65427
65634
  /** Top-level key of `.qoder/settings.json` that rulesync owns. */
65428
65635
  const QODER_PERMISSIONS_KEY = "permissions";
65636
+ /**
65637
+ * Top-level key the `qoder` override authors: the active output style. Qoder
65638
+ * also reads `general.outputStyle`, but the top-level key wins, so that is the
65639
+ * one written and imported.
65640
+ * @see https://docs.qoder.com/cli/settings-reference
65641
+ */
65642
+ const QODER_OUTPUT_STYLE_KEY = "outputStyle";
65429
65643
  const CATCH_ALL_PATTERN$2 = "*";
65430
65644
  /** Prefix of the fully qualified MCP tool names (`mcp__<server>__<tool>`). */
65431
65645
  const MCP_TOOL_PREFIX = "mcp__";
@@ -65585,9 +65799,11 @@ function buildQoderRuleLists({ config, existingPermissions, logger }) {
65585
65799
  * through the shared-config gateway, the sibling keys of `permissions`
65586
65800
  * (`additionalDirectories`, `trustDirectories`) are kept, and the file is
65587
65801
  * never deleted. `.qoder/settings.local.json` (where `/allow` and `/deny`
65588
- * persist) is left to the user.
65802
+ * persist) is left to the user. The `qoder` override's `outputStyle` is
65803
+ * written to (and imported from) the top-level `outputStyle` key.
65589
65804
  *
65590
65805
  * @see https://docs.qoder.com/en/cli/permissions
65806
+ * @see https://docs.qoder.com/cli/output-styles
65591
65807
  */
65592
65808
  var QoderPermissions = class QoderPermissions extends ToolPermissions {
65593
65809
  constructor(params) {
@@ -65626,11 +65842,14 @@ var QoderPermissions = class QoderPermissions extends ToolPermissions {
65626
65842
  relativePath: (0, node_path.join)(paths.relativeDirPath, paths.relativeFilePath)
65627
65843
  });
65628
65844
  const existingPermissions = isRecord$1(existing[QODER_PERMISSIONS_KEY]) ? existing[QODER_PERMISSIONS_KEY] : {};
65845
+ const config = rulesyncPermissions.getJson();
65629
65846
  const lists = buildQoderRuleLists({
65630
- config: rulesyncPermissions.getJson(),
65847
+ config,
65631
65848
  existingPermissions,
65632
65849
  logger
65633
65850
  });
65851
+ const outputStyle = config.qoder?.outputStyle?.trim();
65852
+ if (outputStyle !== void 0) logger?.warn(`Qoder permissions: qoder.outputStyle = ${JSON.stringify(outputStyle)} was written to ${(0, node_path.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.`);
65634
65853
  const permissions = { ...existingPermissions };
65635
65854
  for (const action of ACTIONS) if (lists[action].length > 0) permissions[action] = lists[action];
65636
65855
  else delete permissions[action];
@@ -65642,7 +65861,10 @@ var QoderPermissions = class QoderPermissions extends ToolPermissions {
65642
65861
  fileKey: sharedConfigFileKey(paths),
65643
65862
  feature: "permissions",
65644
65863
  existingContent,
65645
- patch: { [QODER_PERMISSIONS_KEY]: Object.keys(permissions).length > 0 ? permissions : void 0 },
65864
+ patch: {
65865
+ [QODER_PERMISSIONS_KEY]: Object.keys(permissions).length > 0 ? permissions : void 0,
65866
+ ...outputStyle !== void 0 && { [QODER_OUTPUT_STYLE_KEY]: outputStyle }
65867
+ },
65646
65868
  filePath
65647
65869
  }),
65648
65870
  validate: true,
@@ -65663,7 +65885,12 @@ var QoderPermissions = class QoderPermissions extends ToolPermissions {
65663
65885
  const existing = rules[parsed.pattern];
65664
65886
  if (existing === void 0 || ACTION_RANK[action] > ACTION_RANK[existing]) rules[parsed.pattern] = action;
65665
65887
  }
65666
- return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify({ permission }, null, 2) });
65888
+ const outputStyle = settings[QODER_OUTPUT_STYLE_KEY];
65889
+ const hasOutputStyle = typeof outputStyle === "string" && outputStyle.trim() !== "";
65890
+ return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify({
65891
+ permission,
65892
+ ...hasOutputStyle && { qoder: { outputStyle } }
65893
+ }, null, 2) });
65667
65894
  }
65668
65895
  validate() {
65669
65896
  return {
@@ -84711,8 +84938,8 @@ var DevinSubagent = class extends ToolSubagent {
84711
84938
  * Derive the subagent name from this instance's relative file path.
84712
84939
  *
84713
84940
  * The tool-side layout is `<name>/AGENT.md`, so the name is the parent
84714
- * directory of the file. If the path is unexpectedly flat (e.g. a legacy
84715
- * `<name>.md`), fall back to the basename without extension.
84941
+ * directory of the file. Devin also reads a flat `<name>.md`, which import
84942
+ * picks up too; its name is the basename without extension.
84716
84943
  */
84717
84944
  getSubagentName() {
84718
84945
  const relativeFilePath = this.getRelativeFilePath();
@@ -84805,8 +85032,9 @@ var DevinSubagent = class extends ToolSubagent {
84805
85032
  /**
84806
85033
  * Subagent inside a Devin plugin bundle (`<plugin>/agents/<name>/AGENT.md`).
84807
85034
  * Plugin subagents use the same profile format as project ones and are
84808
- * exposed as `<plugin>:<name>`. Devin also reads a flat `agents/<name>.md`;
84809
- * the directory form is written to match the `devin` target.
85035
+ * exposed as `<plugin>:<name>`. Devin also reads a flat `agents/<name>.md`,
85036
+ * which import picks up too; the directory form is written to match the
85037
+ * `devin` target.
84810
85038
  *
84811
85039
  * @see https://docs.devin.ai/cli/extensibility/plugins/overview
84812
85040
  */
@@ -88368,6 +88596,17 @@ var ZoocodeSubagent = class extends RooSubagent {
88368
88596
  };
88369
88597
  //#endregion
88370
88598
  //#region src/features/subagents/subagents-processor.ts
88599
+ /**
88600
+ * The globs import scans for a tool: its import-only patterns when it reads
88601
+ * more layouts than Rulesync writes, otherwise the generation pattern.
88602
+ */
88603
+ function importFilePatternsOf(meta) {
88604
+ return meta.importFilePatterns ?? meta.filePattern;
88605
+ }
88606
+ /** Whether unparseable files are left out of this import or orphan sweep. */
88607
+ function skipsInvalidFiles(meta, forDeletion) {
88608
+ return meta.skipInvalidFiles === true || !forDeletion && meta.skipInvalidImportFiles === true;
88609
+ }
88371
88610
  const SubagentsProcessorToolTargetSchema = zod_mini.z.enum(subagentsProcessorToolTargetTuple);
88372
88611
  /**
88373
88612
  * Factory Map mapping tool targets to their subagent factories.
@@ -88564,7 +88803,9 @@ const toolSubagentFactories = /* @__PURE__ */ new Map([
88564
88803
  supportsProject: true,
88565
88804
  supportsSimulated: false,
88566
88805
  supportsGlobal: true,
88567
- filePattern: "*/AGENT.md"
88806
+ filePattern: "*/AGENT.md",
88807
+ importFilePatterns: ["*.md", "*/AGENT.md"],
88808
+ skipInvalidImportFiles: true
88568
88809
  }
88569
88810
  }],
88570
88811
  ["devin-plugin", {
@@ -88573,7 +88814,9 @@ const toolSubagentFactories = /* @__PURE__ */ new Map([
88573
88814
  supportsProject: true,
88574
88815
  supportsSimulated: false,
88575
88816
  supportsGlobal: false,
88576
- filePattern: "*/AGENT.md"
88817
+ filePattern: "*/AGENT.md",
88818
+ importFilePatterns: ["*.md", "*/AGENT.md"],
88819
+ skipInvalidImportFiles: true
88577
88820
  }
88578
88821
  }],
88579
88822
  ["factorydroid", {
@@ -89052,6 +89295,7 @@ var SubagentsProcessor = class extends FeatureProcessor {
89052
89295
  const factory = this.getFactory(this.toolTarget);
89053
89296
  const paths = factory.class.getSettablePaths({ global: this.global });
89054
89297
  const roots = forDeletion ? [paths.relativeDirPath] : [paths.relativeDirPath, ...paths.importDirPaths ?? []];
89298
+ const filePatterns = forDeletion ? factory.meta.filePattern : importFilePatternsOf(factory.meta);
89055
89299
  const toolSubagents = [];
89056
89300
  const claimedRelativeFilePaths = /* @__PURE__ */ new Map();
89057
89301
  const claimedImportIdentities = new ClaimedIdentities();
@@ -89068,7 +89312,7 @@ var SubagentsProcessor = class extends FeatureProcessor {
89068
89312
  this.logger.warn(`Skipping the orphan sweep for ${quoteForLog(baseDir)}; nothing under it is deleted: ${stripControlCharacters(formatError(error))}`);
89069
89313
  continue;
89070
89314
  }
89071
- const subagentFilePaths = (await findFilesByGlobs(factory.meta.filePattern, {
89315
+ const subagentFilePaths = (await findFilesByGlobs(filePatterns, {
89072
89316
  cwd: baseDir,
89073
89317
  followSymbolicLinks: !forDeletion
89074
89318
  })).toSorted((left, right) => compareRelativePathsShallowestFirst((0, node_path.relative)(baseDir, left), (0, node_path.relative)(baseDir, right)));
@@ -89082,7 +89326,7 @@ var SubagentsProcessor = class extends FeatureProcessor {
89082
89326
  })));
89083
89327
  ownedFilePaths = subagentFilePaths.filter((_, index) => ownership[index]);
89084
89328
  }
89085
- const parsedFiles = factory.meta.skipInvalidFiles ? (await Promise.all(ownedFilePaths.map(async (path) => {
89329
+ const parsedFiles = skipsInvalidFiles(factory.meta, forDeletion) ? (await Promise.all(ownedFilePaths.map(async (path) => {
89086
89330
  try {
89087
89331
  return {
89088
89332
  path,
@@ -91958,6 +92202,85 @@ var CodebuddyRule = class CodebuddyRule extends ToolRule {
91958
92202
  }
91959
92203
  };
91960
92204
  //#endregion
92205
+ //#region src/features/rules/codebuff-rule.ts
92206
+ /**
92207
+ * Rule generator for Codebuff (Freebuff), global scope only.
92208
+ *
92209
+ * Codebuff reads one user-level instructions file from the home directory:
92210
+ * the first hidden file matching `AGENTS.md` or `CLAUDE.md` (case-insensitive,
92211
+ * in that priority order), so the root rule is written to `~/.AGENTS.md`. There
92212
+ * is no user-level rules directory, so non-root rules fold into that file.
92213
+ *
92214
+ * Project scope is not supported here: Codebuff reads the project-root
92215
+ * `AGENTS.md`, which the `agentsmd` target already writes. The RulesProcessor
92216
+ * keeps this target out of project mode (`supportsProject: false`), so the
92217
+ * settable paths are the home-directory ones regardless of `global`.
92218
+ *
92219
+ * @see https://github.com/CodebuffAI/freebuff/blob/main/sdk/src/run-state.ts
92220
+ * @see https://github.com/CodebuffAI/freebuff/blob/main/common/src/constants/knowledge.ts
92221
+ */
92222
+ var CodebuffRule = class CodebuffRule extends ToolRule {
92223
+ static getSettablePaths(_options = {}) {
92224
+ return { root: {
92225
+ relativeDirPath: ".",
92226
+ relativeFilePath: CODEBUFF_GLOBAL_RULE_FILE_NAME
92227
+ } };
92228
+ }
92229
+ static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true }) {
92230
+ const { root } = this.getSettablePaths({ global: true });
92231
+ if (relativeFilePath !== root.relativeFilePath) throw new Error(`CodebuffRule only supports the root rule: ${relativeFilePath}`);
92232
+ const fileContent = await readFileContent((0, node_path.join)(outputRoot, root.relativeDirPath, root.relativeFilePath));
92233
+ return new CodebuffRule({
92234
+ outputRoot,
92235
+ relativeDirPath: root.relativeDirPath,
92236
+ relativeFilePath: root.relativeFilePath,
92237
+ fileContent,
92238
+ validate,
92239
+ root: true
92240
+ });
92241
+ }
92242
+ static fromRulesyncRule({ outputRoot = process.cwd(), rulesyncRule, validate = true }) {
92243
+ const { root } = this.getSettablePaths({ global: true });
92244
+ const frontmatter = rulesyncRule.getFrontmatter();
92245
+ return new CodebuffRule({
92246
+ outputRoot,
92247
+ relativeDirPath: root.relativeDirPath,
92248
+ relativeFilePath: root.relativeFilePath,
92249
+ fileContent: rulesyncRule.getBody(),
92250
+ validate,
92251
+ root: frontmatter.root ?? false,
92252
+ description: frontmatter.description,
92253
+ globs: frontmatter.globs
92254
+ });
92255
+ }
92256
+ toRulesyncRule() {
92257
+ return this.toRulesyncRuleDefault();
92258
+ }
92259
+ validate() {
92260
+ return {
92261
+ success: true,
92262
+ error: null
92263
+ };
92264
+ }
92265
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
92266
+ const { root } = this.getSettablePaths({ global: true });
92267
+ return new CodebuffRule({
92268
+ outputRoot,
92269
+ relativeDirPath,
92270
+ relativeFilePath,
92271
+ fileContent: "",
92272
+ validate: false,
92273
+ root: relativeDirPath === root.relativeDirPath && relativeFilePath === root.relativeFilePath
92274
+ });
92275
+ }
92276
+ static isTargetedByRulesyncRule(rulesyncRule) {
92277
+ return this.isTargetedByRulesyncRuleDefault({
92278
+ rulesyncRule,
92279
+ toolTarget: "codebuff"
92280
+ });
92281
+ }
92282
+ };
92283
+ //#endregion
91961
92284
  //#region src/features/rules/codewhale-rule.ts
91962
92285
  /**
91963
92286
  * Rule generator for Codewhale.
@@ -93595,7 +93918,7 @@ var DshRule = class extends NestedAgentsmdRule {
93595
93918
  * `getExtraFixedFiles`): Droid loads "direct `.md` children of each
93596
93919
  * `output-styles` directory".
93597
93920
  */
93598
- const OUTPUT_STYLE_FILE_GLOB = "*.md";
93921
+ const OUTPUT_STYLE_FILE_GLOB$1 = "*.md";
93599
93922
  /**
93600
93923
  * Rule generator for Factory Droid.
93601
93924
  *
@@ -93717,7 +94040,7 @@ var FactorydroidRule = class FactorydroidRule extends ToolRule {
93717
94040
  static getExtraFixedFiles({ global = false } = {}) {
93718
94041
  return [...this.getChannelPaths({ global }).map(({ path }) => path), {
93719
94042
  relativeDirPath: this.getOutputStylesDirPath(),
93720
- relativeFilePath: OUTPUT_STYLE_FILE_GLOB,
94043
+ relativeFilePath: OUTPUT_STYLE_FILE_GLOB$1,
93721
94044
  importOnly: true
93722
94045
  }];
93723
94046
  }
@@ -95572,6 +95895,12 @@ const UNIVERSAL_GLOBS$1 = /* @__PURE__ */ new Set([
95572
95895
  * Any other mode is kept in the `qoder` section so it survives a round trip.
95573
95896
  */
95574
95897
  const INFERABLE_TRIGGERS = /* @__PURE__ */ new Set(["always_on", "glob"]);
95898
+ /**
95899
+ * The file-name glob `getExtraFixedFiles` returns for the output-styles
95900
+ * directory, so import enumerates every style in it (never deletion: the
95901
+ * directory also holds hand-written styles).
95902
+ */
95903
+ const OUTPUT_STYLE_FILE_GLOB = "*.md";
95575
95904
  function normalizeGlobList(value) {
95576
95905
  if (value === void 0) return [];
95577
95906
  return (typeof value === "string" ? splitBraceAwareList(value) : value).map((glob) => glob.trim()).filter((glob) => glob !== "");
@@ -95605,22 +95934,59 @@ function resolveTrigger(frontmatter) {
95605
95934
  * A `qoder.trigger` (for example `model_decision` or `manual`) always takes
95606
95935
  * precedence and round-trips through import.
95607
95936
  *
95937
+ * A non-root rule with `qoder.channel: output-style` becomes a custom output
95938
+ * style instead, `.qoder/output-styles/<rule file name>.md` (both scopes),
95939
+ * with the rule's `description` and optional `qoder.name` as the style's
95940
+ * `description` / `name` frontmatter.
95941
+ *
95608
95942
  * @see https://docs.qoder.com/en/cli/04-extending-qoder-cli/memory
95943
+ * @see https://docs.qoder.com/cli/output-styles
95609
95944
  */
95610
95945
  var QoderRule = class QoderRule extends ToolRule {
95611
95946
  frontmatter;
95612
95947
  body;
95613
- constructor({ frontmatter, body, ...rest }) {
95948
+ outputStyle;
95949
+ constructor({ frontmatter, body, outputStyle = false, ...rest }) {
95614
95950
  if (rest.validate) {
95615
95951
  const result = QoderRuleFrontmatterSchema.safeParse(frontmatter);
95616
95952
  if (!result.success) throw new Error(`Invalid frontmatter in ${(0, node_path.join)(rest.relativeDirPath, rest.relativeFilePath)}: ${formatError(result.error)}`);
95617
95953
  }
95618
95954
  super({
95619
95955
  ...rest,
95620
- fileContent: rest.root ? body : stringifyFrontmatter(body, frontmatter)
95956
+ fileContent: rest.root || outputStyle && Object.keys(frontmatter).length === 0 ? body : stringifyFrontmatter(body, frontmatter)
95621
95957
  });
95622
95958
  this.frontmatter = frontmatter;
95623
95959
  this.body = body;
95960
+ this.outputStyle = outputStyle;
95961
+ }
95962
+ /**
95963
+ * Whether the given path is a custom output style: a direct `.md` child of
95964
+ * `.qoder/output-styles/`, at either scope.
95965
+ */
95966
+ static isOutputStylePath({ relativeDirPath, relativeFilePath }) {
95967
+ return relativeDirPath === QODER_OUTPUT_STYLES_DIR_PATH && (0, node_path.extname)(relativeFilePath) === ".md" && (0, node_path.basename)(relativeFilePath) === relativeFilePath;
95968
+ }
95969
+ /** Whether a rule opts into the `output-style` channel (non-root only). */
95970
+ static isOutputStyleRule(rulesyncRule) {
95971
+ const frontmatter = rulesyncRule.getFrontmatter();
95972
+ return !frontmatter.root && frontmatter.qoder?.channel === "output-style";
95973
+ }
95974
+ /**
95975
+ * The output-styles directory, enumerated for import only: it also holds
95976
+ * styles users write by hand (including personal ones in
95977
+ * `~/.qoder/output-styles/`) that rulesync cannot tell from its own, so
95978
+ * `--delete` leaves it alone.
95979
+ */
95980
+ static getExtraFixedFiles(_params = {}) {
95981
+ return [{
95982
+ relativeDirPath: QODER_OUTPUT_STYLES_DIR_PATH,
95983
+ relativeFilePath: OUTPUT_STYLE_FILE_GLOB,
95984
+ importOnly: true
95985
+ }];
95986
+ }
95987
+ /** Qoder loads output styles itself; they are not rules to reference. */
95988
+ isExcludedFromRootReferences() {
95989
+ return this.outputStyle;
95624
95990
  }
95625
95991
  static getSettablePaths({ global = false, excludeToolDir } = {}) {
95626
95992
  return {
@@ -95633,6 +95999,23 @@ var QoderRule = class QoderRule extends ToolRule {
95633
95999
  }
95634
96000
  static async fromFile({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, validate = true, global = false }) {
95635
96001
  const paths = this.getSettablePaths({ global });
96002
+ if (this.isOutputStylePath({
96003
+ relativeDirPath,
96004
+ relativeFilePath
96005
+ })) {
96006
+ const filePath = (0, node_path.join)(outputRoot, QODER_OUTPUT_STYLES_DIR_PATH, relativeFilePath);
96007
+ const { frontmatter, body } = parseFrontmatter(await readFileContent(filePath), filePath);
96008
+ return new QoderRule({
96009
+ outputRoot,
96010
+ relativeDirPath: QODER_OUTPUT_STYLES_DIR_PATH,
96011
+ relativeFilePath,
96012
+ frontmatter,
96013
+ body: body.trim(),
96014
+ validate,
96015
+ root: false,
96016
+ outputStyle: true
96017
+ });
96018
+ }
95636
96019
  if (relativeDirPath !== void 0 ? relativeDirPath !== QODER_RULES_DIR_PATH : relativeFilePath === paths.root.relativeFilePath) {
95637
96020
  const rootDirPath = relativeDirPath ?? paths.root.relativeDirPath;
95638
96021
  const fileContent = await readFileContent((0, node_path.join)(outputRoot, rootDirPath, paths.root.relativeFilePath));
@@ -95665,6 +96048,11 @@ var QoderRule = class QoderRule extends ToolRule {
95665
96048
  const root = rulesyncFrontmatter.root ?? false;
95666
96049
  const paths = this.getSettablePaths({ global });
95667
96050
  const body = rulesyncRule.getBody();
96051
+ if (this.isOutputStyleRule(rulesyncRule)) return this.buildOutputStyle({
96052
+ outputRoot,
96053
+ rulesyncRule,
96054
+ validate
96055
+ });
95668
96056
  if (root) return new QoderRule({
95669
96057
  outputRoot,
95670
96058
  relativeDirPath: paths.root.relativeDirPath,
@@ -95684,8 +96072,32 @@ var QoderRule = class QoderRule extends ToolRule {
95684
96072
  root: false
95685
96073
  });
95686
96074
  }
96075
+ /**
96076
+ * Write an `output-style` rule as `.qoder/output-styles/<name>.md`. The file
96077
+ * name is the rule's own, so a rule nested under `.rulesync/rules/` is
96078
+ * refused: Qoder reads only the files directly in `output-styles/`.
96079
+ */
96080
+ static buildOutputStyle({ outputRoot, rulesyncRule, validate }) {
96081
+ const { description, qoder } = rulesyncRule.getFrontmatter();
96082
+ const sourcePath = rulesyncRule.getRelativeFilePath();
96083
+ if ((0, node_path.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}.`);
96084
+ const name = qoder?.name;
96085
+ return new QoderRule({
96086
+ outputRoot,
96087
+ relativeDirPath: QODER_OUTPUT_STYLES_DIR_PATH,
96088
+ relativeFilePath: `${(0, node_path.basename)(sourcePath, (0, node_path.extname)(sourcePath))}.md`,
96089
+ frontmatter: {
96090
+ ...name !== void 0 && { name },
96091
+ ...description !== void 0 && { description }
96092
+ },
96093
+ body: rulesyncRule.getBody(),
96094
+ validate,
96095
+ root: false,
96096
+ outputStyle: true
96097
+ });
96098
+ }
95687
96099
  static buildFrontmatter(rulesyncFrontmatter, relativeFilePath) {
95688
- const { trigger: storedTrigger, glob: storedGlob, ...section } = rulesyncFrontmatter.qoder ?? {};
96100
+ const { trigger: storedTrigger, glob: storedGlob, channel: _channel, name: _name, ...section } = rulesyncFrontmatter.qoder ?? {};
95689
96101
  const specificGlobs = normalizeGlobList(storedGlob ?? rulesyncFrontmatter.globs).filter((glob) => !UNIVERSAL_GLOBS$1.has(glob));
95690
96102
  let trigger = storedTrigger ?? (specificGlobs.length > 0 ? "glob" : "always_on");
95691
96103
  const description = section.description ?? rulesyncFrontmatter.description;
@@ -95703,6 +96115,25 @@ var QoderRule = class QoderRule extends ToolRule {
95703
96115
  }
95704
96116
  toRulesyncRule() {
95705
96117
  if (this.isRoot()) return this.toRulesyncRuleDefault();
96118
+ if (this.outputStyle) {
96119
+ const { name, description } = this.frontmatter;
96120
+ return new RulesyncRule({
96121
+ outputRoot: this.getOutputRoot(),
96122
+ relativeDirPath: RULESYNC_RULES_RELATIVE_DIR_PATH,
96123
+ relativeFilePath: this.getRelativeFilePath(),
96124
+ frontmatter: {
96125
+ root: false,
96126
+ targets: ["qoder"],
96127
+ ...typeof description === "string" && { description },
96128
+ qoder: {
96129
+ channel: "output-style",
96130
+ ...typeof name === "string" && name.trim() !== "" && { name }
96131
+ }
96132
+ },
96133
+ body: this.body,
96134
+ validate: true
96135
+ });
96136
+ }
95706
96137
  const trigger = resolveTrigger(this.frontmatter);
95707
96138
  const { trigger: _trigger, alwaysApply: _alwaysApply, description, glob, paths, ...unmapped } = this.frontmatter;
95708
96139
  const fileGlobs = normalizeGlobList(glob ?? paths);
@@ -95751,6 +96182,10 @@ var QoderRule = class QoderRule extends ToolRule {
95751
96182
  return this.body;
95752
96183
  }
95753
96184
  static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
96185
+ const outputStyle = this.isOutputStylePath({
96186
+ relativeDirPath,
96187
+ relativeFilePath
96188
+ });
95754
96189
  return new QoderRule({
95755
96190
  outputRoot,
95756
96191
  relativeDirPath,
@@ -95758,7 +96193,8 @@ var QoderRule = class QoderRule extends ToolRule {
95758
96193
  frontmatter: {},
95759
96194
  body: "",
95760
96195
  validate: false,
95761
- root: relativeDirPath !== QODER_RULES_DIR_PATH && relativeFilePath === "AGENTS.md"
96196
+ root: !outputStyle && relativeDirPath !== QODER_RULES_DIR_PATH && relativeFilePath === "AGENTS.md",
96197
+ outputStyle
95762
96198
  });
95763
96199
  }
95764
96200
  static isTargetedByRulesyncRule(rulesyncRule) {
@@ -97445,6 +97881,16 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
97445
97881
  localRootFileName: CODEBUDDY_LOCAL_RULE_FILE_NAME
97446
97882
  }
97447
97883
  }],
97884
+ ["codebuff", {
97885
+ class: CodebuffRule,
97886
+ meta: {
97887
+ extension: "md",
97888
+ supportsProject: false,
97889
+ supportsGlobal: true,
97890
+ ruleDiscoveryMode: "auto",
97891
+ collisionPolicy: "fold"
97892
+ }
97893
+ }],
97448
97894
  ["codewhale", {
97449
97895
  class: CodewhaleRule,
97450
97896
  meta: {
@@ -97875,7 +98321,9 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
97875
98321
  }]
97876
98322
  ]);
97877
98323
  const allToolTargetKeys = [...toolRuleFactories.keys()];
97878
- const rulesProcessorToolTargets = allToolTargetKeys;
98324
+ const rulesProcessorToolTargets = allToolTargetKeys.filter((target) => {
98325
+ return toolRuleFactories.get(target)?.meta.supportsProject !== false;
98326
+ });
97879
98327
  const rulesProcessorToolTargetsGlobal = allToolTargetKeys.filter((target) => {
97880
98328
  return toolRuleFactories.get(target)?.meta.supportsGlobal ?? false;
97881
98329
  });
@@ -97954,7 +98402,7 @@ var RulesProcessor = class extends FeatureProcessor {
97954
98402
  const factory = this.getFactory(this.toolTarget);
97955
98403
  const { meta } = factory;
97956
98404
  this.warnForIncludeRootOption(meta);
97957
- this.warnForFactorydroidRuleOptions({
98405
+ this.warnForOutputStyleRuleOptions({
97958
98406
  rules: alignedRules,
97959
98407
  factory
97960
98408
  });
@@ -98188,22 +98636,25 @@ var RulesProcessor = class extends FeatureProcessor {
98188
98636
  }) !== "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.");
98189
98637
  }
98190
98638
  /**
98191
- * Warn about `factorydroid` frontmatter that does not do what it looks like
98192
- * it does: an `output-style` rule that also targets other tools reaches them
98193
- * as an ordinary rule (so presentation instructions end up in their project
98194
- * rules), and `factorydroid.name` is read only with `channel: output-style`.
98195
- * Checked while generating for factorydroid only, so each warning prints once.
98639
+ * Warn about `factorydroid` / `qoder` frontmatter that does not do what it
98640
+ * looks like it does: an `output-style` rule that also targets other tools
98641
+ * reaches them as an ordinary rule (so presentation instructions end up in
98642
+ * their project rules), and `<tool>.name` is read only with
98643
+ * `channel: output-style`. Checked while generating for that tool only, so
98644
+ * each warning prints once.
98196
98645
  */
98197
- warnForFactorydroidRuleOptions({ rules, factory }) {
98198
- if (this.toolTarget !== "factorydroid") return;
98646
+ warnForOutputStyleRuleOptions({ rules, factory }) {
98647
+ const toolTarget = this.toolTarget;
98648
+ if (toolTarget !== "factorydroid" && toolTarget !== "qoder") return;
98199
98649
  for (const rule of rules) {
98200
98650
  const frontmatter = rule.getFrontmatter();
98201
- if (frontmatter.root || !frontmatter.factorydroid || !factory.class.isTargetedByRulesyncRule(rule)) continue;
98202
- const { channel, name } = frontmatter.factorydroid;
98651
+ const section = frontmatter[toolTarget];
98652
+ if (frontmatter.root || !section || !factory.class.isTargetedByRulesyncRule(rule)) continue;
98653
+ const { channel, name } = section;
98203
98654
  const fileName = stripControlCharacters(rule.getRelativeFilePath());
98204
98655
  const targets = frontmatter.targets ?? ["*"];
98205
- 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.`);
98206
- 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.`);
98656
+ 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"}.`);
98657
+ 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.`);
98207
98658
  }
98208
98659
  }
98209
98660
  /**
@@ -102650,6 +103101,12 @@ Object.defineProperty(exports, "hasEnclosingMarkOutsideKeycap", {
102650
103101
  return hasEnclosingMarkOutsideKeycap;
102651
103102
  }
102652
103103
  });
103104
+ Object.defineProperty(exports, "hasExecutableBit", {
103105
+ enumerable: true,
103106
+ get: function() {
103107
+ return hasExecutableBit;
103108
+ }
103109
+ });
102653
103110
  Object.defineProperty(exports, "importFromTool", {
102654
103111
  enumerable: true,
102655
103112
  get: function() {
@@ -102806,6 +103263,12 @@ Object.defineProperty(exports, "resolveRulesyncSourceWritePath", {
102806
103263
  return resolveRulesyncSourceWritePath;
102807
103264
  }
102808
103265
  });
103266
+ Object.defineProperty(exports, "resolvedPathEscapesRoot", {
103267
+ enumerable: true,
103268
+ get: function() {
103269
+ return resolvedPathEscapesRoot;
103270
+ }
103271
+ });
102809
103272
  Object.defineProperty(exports, "runWithDirectoryRollback", {
102810
103273
  enumerable: true,
102811
103274
  get: function() {