rulesync 7.11.0 → 7.12.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.
@@ -433,10 +433,9 @@ function parseFrontmatter(content, filePath) {
433
433
  body = result.content;
434
434
  } catch (error) {
435
435
  if (filePath) {
436
- throw new Error(
437
- `Failed to parse frontmatter in ${filePath}: ${error instanceof Error ? error.message : String(error)}`,
438
- { cause: error }
439
- );
436
+ throw new Error(`Failed to parse frontmatter in ${filePath}: ${formatError(error)}`, {
437
+ cause: error
438
+ });
440
439
  }
441
440
  throw error;
442
441
  }
@@ -609,7 +608,7 @@ var ToolCommand = class extends AiFile {
609
608
 
610
609
  // src/features/commands/simulated-command.ts
611
610
  var SimulatedCommandFrontmatterSchema = import_mini2.z.object({
612
- description: import_mini2.z.string()
611
+ description: import_mini2.z.optional(import_mini2.z.string())
613
612
  });
614
613
  var SimulatedCommand = class _SimulatedCommand extends ToolCommand {
615
614
  frontmatter;
@@ -829,7 +828,7 @@ var RulesyncTargetsSchema = import_mini3.z.array(import_mini3.z.enum(ALL_TOOL_TA
829
828
  // src/features/commands/rulesync-command.ts
830
829
  var RulesyncCommandFrontmatterSchema = import_mini4.z.looseObject({
831
830
  targets: import_mini4.z._default(RulesyncTargetsSchema, ["*"]),
832
- description: import_mini4.z.string()
831
+ description: import_mini4.z.optional(import_mini4.z.string())
833
832
  });
834
833
  var RulesyncCommand = class _RulesyncCommand extends RulesyncFile {
835
834
  frontmatter;
@@ -917,7 +916,7 @@ var AntigravityWorkflowFrontmatterSchema = import_mini5.z.looseObject({
917
916
  turbo: import_mini5.z.optional(import_mini5.z.boolean())
918
917
  });
919
918
  var AntigravityCommandFrontmatterSchema = import_mini5.z.looseObject({
920
- description: import_mini5.z.string(),
919
+ description: import_mini5.z.optional(import_mini5.z.string()),
921
920
  // Support for workflow-specific configuration
922
921
  ...AntigravityWorkflowFrontmatterSchema.shape
923
922
  });
@@ -1090,7 +1089,7 @@ ${body}${turboDirective}`;
1090
1089
  var import_node_path8 = require("path");
1091
1090
  var import_mini6 = require("zod/mini");
1092
1091
  var ClaudecodeCommandFrontmatterSchema = import_mini6.z.looseObject({
1093
- description: import_mini6.z.string(),
1092
+ description: import_mini6.z.optional(import_mini6.z.string()),
1094
1093
  "allowed-tools": import_mini6.z.optional(import_mini6.z.union([import_mini6.z.string(), import_mini6.z.array(import_mini6.z.string())])),
1095
1094
  "argument-hint": import_mini6.z.optional(import_mini6.z.string()),
1096
1095
  model: import_mini6.z.optional(import_mini6.z.string()),
@@ -1245,8 +1244,7 @@ var ClineCommand = class _ClineCommand extends ToolCommand {
1245
1244
  }
1246
1245
  toRulesyncCommand() {
1247
1246
  const rulesyncFrontmatter = {
1248
- targets: ["*"],
1249
- description: ""
1247
+ targets: ["*"]
1250
1248
  };
1251
1249
  return new RulesyncCommand({
1252
1250
  baseDir: process.cwd(),
@@ -1331,8 +1329,7 @@ var CodexcliCommand = class _CodexcliCommand extends ToolCommand {
1331
1329
  }
1332
1330
  toRulesyncCommand() {
1333
1331
  const rulesyncFrontmatter = {
1334
- targets: ["*"],
1335
- description: ""
1332
+ targets: ["*"]
1336
1333
  };
1337
1334
  return new RulesyncCommand({
1338
1335
  baseDir: ".",
@@ -1410,7 +1407,7 @@ var import_node_path11 = require("path");
1410
1407
  var import_mini7 = require("zod/mini");
1411
1408
  var CopilotCommandFrontmatterSchema = import_mini7.z.looseObject({
1412
1409
  mode: import_mini7.z.optional(import_mini7.z.string()),
1413
- description: import_mini7.z.string()
1410
+ description: import_mini7.z.optional(import_mini7.z.string())
1414
1411
  });
1415
1412
  var CopilotCommand = class _CopilotCommand extends ToolCommand {
1416
1413
  frontmatter;
@@ -1593,7 +1590,7 @@ var CursorCommand = class _CursorCommand extends ToolCommand {
1593
1590
  return this.frontmatter;
1594
1591
  }
1595
1592
  toRulesyncCommand() {
1596
- const { description = "", ...restFields } = this.frontmatter;
1593
+ const { description, ...restFields } = this.frontmatter;
1597
1594
  const rulesyncFrontmatter = {
1598
1595
  targets: ["*"],
1599
1596
  description,
@@ -1787,7 +1784,7 @@ var GeminiCliCommand = class _GeminiCliCommand extends ToolCommand {
1787
1784
  }
1788
1785
  return {
1789
1786
  ...result.data,
1790
- description: result.data.description || ""
1787
+ description: result.data.description
1791
1788
  };
1792
1789
  } catch (error) {
1793
1790
  throw new Error(
@@ -1809,7 +1806,7 @@ var GeminiCliCommand = class _GeminiCliCommand extends ToolCommand {
1809
1806
  const { description, prompt: _prompt, ...restFields } = this.frontmatter;
1810
1807
  const rulesyncFrontmatter = {
1811
1808
  targets: ["geminicli"],
1812
- description: description ?? "",
1809
+ description,
1813
1810
  // Preserve extra fields in geminicli section (excluding prompt which is the body)
1814
1811
  ...Object.keys(restFields).length > 0 && { geminicli: restFields }
1815
1812
  };
@@ -1838,8 +1835,9 @@ var GeminiCliCommand = class _GeminiCliCommand extends ToolCommand {
1838
1835
  prompt: rulesyncCommand.getBody(),
1839
1836
  ...geminicliFields
1840
1837
  };
1841
- const tomlContent = `description = "${geminiFrontmatter.description}"
1842
- prompt = """
1838
+ const descriptionLine = geminiFrontmatter.description !== void 0 ? `description = "${geminiFrontmatter.description}"
1839
+ ` : "";
1840
+ const tomlContent = `${descriptionLine}prompt = """
1843
1841
  ${geminiFrontmatter.prompt}
1844
1842
  """`;
1845
1843
  const paths = this.getSettablePaths({ global });
@@ -1909,8 +1907,7 @@ var KiloCommand = class _KiloCommand extends ToolCommand {
1909
1907
  }
1910
1908
  toRulesyncCommand() {
1911
1909
  const rulesyncFrontmatter = {
1912
- targets: ["*"],
1913
- description: ""
1910
+ targets: ["*"]
1914
1911
  };
1915
1912
  return new RulesyncCommand({
1916
1913
  baseDir: process.cwd(),
@@ -1990,8 +1987,7 @@ var KiroCommand = class _KiroCommand extends ToolCommand {
1990
1987
  }
1991
1988
  toRulesyncCommand() {
1992
1989
  const rulesyncFrontmatter = {
1993
- targets: ["*"],
1994
- description: ""
1990
+ targets: ["*"]
1995
1991
  };
1996
1992
  return new RulesyncCommand({
1997
1993
  baseDir: process.cwd(),
@@ -2065,7 +2061,7 @@ var KiroCommand = class _KiroCommand extends ToolCommand {
2065
2061
  var import_node_path17 = require("path");
2066
2062
  var import_mini10 = require("zod/mini");
2067
2063
  var OpenCodeCommandFrontmatterSchema = import_mini10.z.looseObject({
2068
- description: import_mini10.z.string(),
2064
+ description: import_mini10.z.optional(import_mini10.z.string()),
2069
2065
  agent: (0, import_mini10.optional)(import_mini10.z.string()),
2070
2066
  subtask: (0, import_mini10.optional)(import_mini10.z.boolean()),
2071
2067
  model: (0, import_mini10.optional)(import_mini10.z.string())
@@ -2205,7 +2201,7 @@ var OpenCodeCommand = class _OpenCodeCommand extends ToolCommand {
2205
2201
  var import_node_path18 = require("path");
2206
2202
  var import_mini11 = require("zod/mini");
2207
2203
  var RooCommandFrontmatterSchema = import_mini11.z.looseObject({
2208
- description: import_mini11.z.string(),
2204
+ description: import_mini11.z.optional(import_mini11.z.string()),
2209
2205
  "argument-hint": (0, import_mini11.optional)(import_mini11.z.string())
2210
2206
  });
2211
2207
  var RooCommand = class _RooCommand extends ToolCommand {
@@ -2744,7 +2740,7 @@ var HookDefinitionSchema = import_mini13.z.looseObject({
2744
2740
  type: import_mini13.z.optional(import_mini13.z.enum(["command", "prompt"])),
2745
2741
  timeout: import_mini13.z.optional(import_mini13.z.number()),
2746
2742
  matcher: import_mini13.z.optional(safeString),
2747
- prompt: import_mini13.z.optional(import_mini13.z.string()),
2743
+ prompt: import_mini13.z.optional(safeString),
2748
2744
  loop_limit: import_mini13.z.optional(import_mini13.z.nullable(import_mini13.z.number())),
2749
2745
  name: import_mini13.z.optional(safeString),
2750
2746
  description: import_mini13.z.optional(safeString)
@@ -2796,7 +2792,7 @@ var OPENCODE_HOOK_EVENTS = [
2796
2792
  var COPILOT_HOOK_EVENTS = [
2797
2793
  "sessionStart",
2798
2794
  "sessionEnd",
2799
- "afterSubmitPrompt",
2795
+ "beforeSubmitPrompt",
2800
2796
  "preToolUse",
2801
2797
  "postToolUse",
2802
2798
  "afterError"
@@ -2907,7 +2903,7 @@ var CANONICAL_TO_OPENCODE_EVENT_NAMES = {
2907
2903
  var CANONICAL_TO_COPILOT_EVENT_NAMES = {
2908
2904
  sessionStart: "sessionStart",
2909
2905
  sessionEnd: "sessionEnd",
2910
- afterSubmitPrompt: "userPromptSubmitted",
2906
+ beforeSubmitPrompt: "userPromptSubmitted",
2911
2907
  preToolUse: "preToolUse",
2912
2908
  postToolUse: "postToolUse",
2913
2909
  afterError: "errorOccurred"
@@ -2935,6 +2931,104 @@ var GEMINICLI_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(
2935
2931
  // src/features/hooks/claudecode-hooks.ts
2936
2932
  var import_node_path21 = require("path");
2937
2933
 
2934
+ // src/features/hooks/tool-hooks-converter.ts
2935
+ function isToolMatcherEntry(x) {
2936
+ if (x === null || typeof x !== "object") {
2937
+ return false;
2938
+ }
2939
+ if ("matcher" in x && typeof x.matcher !== "string") {
2940
+ return false;
2941
+ }
2942
+ if ("hooks" in x && !Array.isArray(x.hooks)) {
2943
+ return false;
2944
+ }
2945
+ return true;
2946
+ }
2947
+ function canonicalToToolHooks({
2948
+ config,
2949
+ toolOverrideHooks,
2950
+ converterConfig
2951
+ }) {
2952
+ const supported = new Set(converterConfig.supportedEvents);
2953
+ const sharedHooks = {};
2954
+ for (const [event, defs] of Object.entries(config.hooks)) {
2955
+ if (supported.has(event)) {
2956
+ sharedHooks[event] = defs;
2957
+ }
2958
+ }
2959
+ const effectiveHooks = {
2960
+ ...sharedHooks,
2961
+ ...toolOverrideHooks
2962
+ };
2963
+ const result = {};
2964
+ for (const [eventName, definitions] of Object.entries(effectiveHooks)) {
2965
+ const toolEventName = converterConfig.canonicalToToolEventNames[eventName] ?? eventName;
2966
+ const byMatcher = /* @__PURE__ */ new Map();
2967
+ for (const def of definitions) {
2968
+ const key = def.matcher ?? "";
2969
+ const list = byMatcher.get(key);
2970
+ if (list) list.push(def);
2971
+ else byMatcher.set(key, [def]);
2972
+ }
2973
+ const entries = [];
2974
+ for (const [matcherKey, defs] of byMatcher) {
2975
+ const hooks = defs.map((def) => {
2976
+ const command = def.command !== void 0 && def.command !== null && !def.command.startsWith("$") ? `${converterConfig.projectDirVar}/${def.command.replace(/^\.\//, "")}` : def.command;
2977
+ return {
2978
+ type: def.type ?? "command",
2979
+ ...command !== void 0 && command !== null && { command },
2980
+ ...def.timeout !== void 0 && def.timeout !== null && { timeout: def.timeout },
2981
+ ...def.prompt !== void 0 && def.prompt !== null && { prompt: def.prompt }
2982
+ };
2983
+ });
2984
+ entries.push(matcherKey ? { matcher: matcherKey, hooks } : { hooks });
2985
+ }
2986
+ result[toolEventName] = entries;
2987
+ }
2988
+ return result;
2989
+ }
2990
+ function toolHooksToCanonical({
2991
+ hooks,
2992
+ converterConfig
2993
+ }) {
2994
+ if (hooks === null || hooks === void 0 || typeof hooks !== "object") {
2995
+ return {};
2996
+ }
2997
+ const canonical = {};
2998
+ for (const [toolEventName, matcherEntries] of Object.entries(hooks)) {
2999
+ const eventName = converterConfig.toolToCanonicalEventNames[toolEventName] ?? toolEventName;
3000
+ if (!Array.isArray(matcherEntries)) continue;
3001
+ const defs = [];
3002
+ for (const rawEntry of matcherEntries) {
3003
+ if (!isToolMatcherEntry(rawEntry)) continue;
3004
+ const hookDefs = rawEntry.hooks ?? [];
3005
+ for (const h of hookDefs) {
3006
+ const cmd = typeof h.command === "string" ? h.command : void 0;
3007
+ const command = typeof cmd === "string" && cmd.includes(`${converterConfig.projectDirVar}/`) ? cmd.replace(
3008
+ new RegExp(
3009
+ `^${converterConfig.projectDirVar.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\/?`
3010
+ ),
3011
+ "./"
3012
+ ) : cmd;
3013
+ const hookType = h.type === "command" || h.type === "prompt" ? h.type : "command";
3014
+ const timeout = typeof h.timeout === "number" ? h.timeout : void 0;
3015
+ const prompt = typeof h.prompt === "string" ? h.prompt : void 0;
3016
+ defs.push({
3017
+ type: hookType,
3018
+ ...command !== void 0 && command !== null && { command },
3019
+ ...timeout !== void 0 && timeout !== null && { timeout },
3020
+ ...prompt !== void 0 && prompt !== null && { prompt },
3021
+ ...rawEntry.matcher !== void 0 && rawEntry.matcher !== null && rawEntry.matcher !== "" && { matcher: rawEntry.matcher }
3022
+ });
3023
+ }
3024
+ }
3025
+ if (defs.length > 0) {
3026
+ canonical[eventName] = defs;
3027
+ }
3028
+ }
3029
+ return canonical;
3030
+ }
3031
+
2938
3032
  // src/types/tool-file.ts
2939
3033
  var ToolFile = class extends AiFile {
2940
3034
  };
@@ -3025,91 +3119,12 @@ var ToolHooks = class extends ToolFile {
3025
3119
  };
3026
3120
 
3027
3121
  // src/features/hooks/claudecode-hooks.ts
3028
- function canonicalToClaudeHooks(config) {
3029
- const claudeSupported = new Set(CLAUDE_HOOK_EVENTS);
3030
- const sharedHooks = {};
3031
- for (const [event, defs] of Object.entries(config.hooks)) {
3032
- if (claudeSupported.has(event)) {
3033
- sharedHooks[event] = defs;
3034
- }
3035
- }
3036
- const effectiveHooks = {
3037
- ...sharedHooks,
3038
- ...config.claudecode?.hooks
3039
- };
3040
- const claude = {};
3041
- for (const [eventName, definitions] of Object.entries(effectiveHooks)) {
3042
- const claudeEventName = CANONICAL_TO_CLAUDE_EVENT_NAMES[eventName] ?? eventName;
3043
- const byMatcher = /* @__PURE__ */ new Map();
3044
- for (const def of definitions) {
3045
- const key = def.matcher ?? "";
3046
- const list = byMatcher.get(key);
3047
- if (list) list.push(def);
3048
- else byMatcher.set(key, [def]);
3049
- }
3050
- const entries = [];
3051
- for (const [matcherKey, defs] of byMatcher) {
3052
- const hooks = defs.map((def) => {
3053
- const command = def.command !== void 0 && def.command !== null && !def.command.startsWith("$") ? `$CLAUDE_PROJECT_DIR/${def.command.replace(/^\.\//, "")}` : def.command;
3054
- return {
3055
- type: def.type ?? "command",
3056
- ...command !== void 0 && command !== null && { command },
3057
- ...def.timeout !== void 0 && def.timeout !== null && { timeout: def.timeout },
3058
- ...def.prompt !== void 0 && def.prompt !== null && { prompt: def.prompt }
3059
- };
3060
- });
3061
- entries.push(matcherKey ? { matcher: matcherKey, hooks } : { hooks });
3062
- }
3063
- claude[claudeEventName] = entries;
3064
- }
3065
- return claude;
3066
- }
3067
- function isClaudeMatcherEntry(x) {
3068
- if (x === null || typeof x !== "object") {
3069
- return false;
3070
- }
3071
- if ("matcher" in x && typeof x.matcher !== "string") {
3072
- return false;
3073
- }
3074
- if ("hooks" in x && !Array.isArray(x.hooks)) {
3075
- return false;
3076
- }
3077
- return true;
3078
- }
3079
- function claudeHooksToCanonical(claudeHooks) {
3080
- if (claudeHooks === null || claudeHooks === void 0 || typeof claudeHooks !== "object") {
3081
- return {};
3082
- }
3083
- const canonical = {};
3084
- for (const [claudeEventName, matcherEntries] of Object.entries(claudeHooks)) {
3085
- const eventName = CLAUDE_TO_CANONICAL_EVENT_NAMES[claudeEventName] ?? claudeEventName;
3086
- if (!Array.isArray(matcherEntries)) continue;
3087
- const defs = [];
3088
- for (const rawEntry of matcherEntries) {
3089
- if (!isClaudeMatcherEntry(rawEntry)) continue;
3090
- const entry = rawEntry;
3091
- const hooks = entry.hooks ?? [];
3092
- for (const h of hooks) {
3093
- const cmd = typeof h.command === "string" ? h.command : void 0;
3094
- const command = typeof cmd === "string" && cmd.includes("$CLAUDE_PROJECT_DIR/") ? cmd.replace(/^\$CLAUDE_PROJECT_DIR\/?/, "./") : cmd;
3095
- const hookType = h.type === "command" || h.type === "prompt" ? h.type : "command";
3096
- const timeout = typeof h.timeout === "number" ? h.timeout : void 0;
3097
- const prompt = typeof h.prompt === "string" ? h.prompt : void 0;
3098
- defs.push({
3099
- type: hookType,
3100
- ...command !== void 0 && command !== null && { command },
3101
- ...timeout !== void 0 && timeout !== null && { timeout },
3102
- ...prompt !== void 0 && prompt !== null && { prompt },
3103
- ...entry.matcher !== void 0 && entry.matcher !== null && entry.matcher !== "" && { matcher: entry.matcher }
3104
- });
3105
- }
3106
- }
3107
- if (defs.length > 0) {
3108
- canonical[eventName] = defs;
3109
- }
3110
- }
3111
- return canonical;
3112
- }
3122
+ var CLAUDE_CONVERTER_CONFIG = {
3123
+ supportedEvents: CLAUDE_HOOK_EVENTS,
3124
+ canonicalToToolEventNames: CANONICAL_TO_CLAUDE_EVENT_NAMES,
3125
+ toolToCanonicalEventNames: CLAUDE_TO_CANONICAL_EVENT_NAMES,
3126
+ projectDirVar: "$CLAUDE_PROJECT_DIR"
3127
+ };
3113
3128
  var ClaudecodeHooks = class _ClaudecodeHooks extends ToolHooks {
3114
3129
  constructor(params) {
3115
3130
  super({
@@ -3161,7 +3176,11 @@ var ClaudecodeHooks = class _ClaudecodeHooks extends ToolHooks {
3161
3176
  );
3162
3177
  }
3163
3178
  const config = rulesyncHooks.getJson();
3164
- const claudeHooks = canonicalToClaudeHooks(config);
3179
+ const claudeHooks = canonicalToToolHooks({
3180
+ config,
3181
+ toolOverrideHooks: config.claudecode?.hooks,
3182
+ converterConfig: CLAUDE_CONVERTER_CONFIG
3183
+ });
3165
3184
  const merged = { ...settings, hooks: claudeHooks };
3166
3185
  const fileContent = JSON.stringify(merged, null, 2);
3167
3186
  return new _ClaudecodeHooks({
@@ -3184,7 +3203,10 @@ var ClaudecodeHooks = class _ClaudecodeHooks extends ToolHooks {
3184
3203
  }
3185
3204
  );
3186
3205
  }
3187
- const hooks = claudeHooksToCanonical(settings.hooks);
3206
+ const hooks = toolHooksToCanonical({
3207
+ hooks: settings.hooks,
3208
+ converterConfig: CLAUDE_CONVERTER_CONFIG
3209
+ });
3188
3210
  return this.toRulesyncHooksDefault({
3189
3211
  fileContent: JSON.stringify({ version: 1, hooks }, null, 2)
3190
3212
  });
@@ -3493,91 +3515,12 @@ var CursorHooks = class _CursorHooks extends ToolHooks {
3493
3515
 
3494
3516
  // src/features/hooks/factorydroid-hooks.ts
3495
3517
  var import_node_path24 = require("path");
3496
- function canonicalToFactorydroidHooks(config) {
3497
- const supported = new Set(FACTORYDROID_HOOK_EVENTS);
3498
- const sharedHooks = {};
3499
- for (const [event, defs] of Object.entries(config.hooks)) {
3500
- if (supported.has(event)) {
3501
- sharedHooks[event] = defs;
3502
- }
3503
- }
3504
- const effectiveHooks = {
3505
- ...sharedHooks,
3506
- ...config.factorydroid?.hooks
3507
- };
3508
- const result = {};
3509
- for (const [eventName, definitions] of Object.entries(effectiveHooks)) {
3510
- const pascalEventName = CANONICAL_TO_FACTORYDROID_EVENT_NAMES[eventName] ?? eventName;
3511
- const byMatcher = /* @__PURE__ */ new Map();
3512
- for (const def of definitions) {
3513
- const key = def.matcher ?? "";
3514
- const list = byMatcher.get(key);
3515
- if (list) list.push(def);
3516
- else byMatcher.set(key, [def]);
3517
- }
3518
- const entries = [];
3519
- for (const [matcherKey, defs] of byMatcher) {
3520
- const hooks = defs.map((def) => {
3521
- const command = def.command !== void 0 && def.command !== null && !def.command.startsWith("$") ? `$FACTORY_PROJECT_DIR/${def.command.replace(/^\.\//, "")}` : def.command;
3522
- return {
3523
- type: def.type ?? "command",
3524
- ...command !== void 0 && command !== null && { command },
3525
- ...def.timeout !== void 0 && def.timeout !== null && { timeout: def.timeout },
3526
- ...def.prompt !== void 0 && def.prompt !== null && { prompt: def.prompt }
3527
- };
3528
- });
3529
- entries.push(matcherKey ? { matcher: matcherKey, hooks } : { hooks });
3530
- }
3531
- result[pascalEventName] = entries;
3532
- }
3533
- return result;
3534
- }
3535
- function isFactorydroidMatcherEntry(x) {
3536
- if (x === null || typeof x !== "object") {
3537
- return false;
3538
- }
3539
- if ("matcher" in x && typeof x.matcher !== "string") {
3540
- return false;
3541
- }
3542
- if ("hooks" in x && !Array.isArray(x.hooks)) {
3543
- return false;
3544
- }
3545
- return true;
3546
- }
3547
- function factorydroidHooksToCanonical(hooks) {
3548
- if (hooks === null || hooks === void 0 || typeof hooks !== "object") {
3549
- return {};
3550
- }
3551
- const canonical = {};
3552
- for (const [pascalEventName, matcherEntries] of Object.entries(hooks)) {
3553
- const eventName = FACTORYDROID_TO_CANONICAL_EVENT_NAMES[pascalEventName] ?? pascalEventName;
3554
- if (!Array.isArray(matcherEntries)) continue;
3555
- const defs = [];
3556
- for (const rawEntry of matcherEntries) {
3557
- if (!isFactorydroidMatcherEntry(rawEntry)) continue;
3558
- const entry = rawEntry;
3559
- const hookDefs = entry.hooks ?? [];
3560
- for (const h of hookDefs) {
3561
- const cmd = typeof h.command === "string" ? h.command : void 0;
3562
- const command = typeof cmd === "string" && cmd.includes("$FACTORY_PROJECT_DIR/") ? cmd.replace(/^\$FACTORY_PROJECT_DIR\/?/, "./") : cmd;
3563
- const hookType = h.type === "command" || h.type === "prompt" ? h.type : "command";
3564
- const timeout = typeof h.timeout === "number" ? h.timeout : void 0;
3565
- const prompt = typeof h.prompt === "string" ? h.prompt : void 0;
3566
- defs.push({
3567
- type: hookType,
3568
- ...command !== void 0 && command !== null && { command },
3569
- ...timeout !== void 0 && timeout !== null && { timeout },
3570
- ...prompt !== void 0 && prompt !== null && { prompt },
3571
- ...entry.matcher !== void 0 && entry.matcher !== null && entry.matcher !== "" && { matcher: entry.matcher }
3572
- });
3573
- }
3574
- }
3575
- if (defs.length > 0) {
3576
- canonical[eventName] = defs;
3577
- }
3578
- }
3579
- return canonical;
3580
- }
3518
+ var FACTORYDROID_CONVERTER_CONFIG = {
3519
+ supportedEvents: FACTORYDROID_HOOK_EVENTS,
3520
+ canonicalToToolEventNames: CANONICAL_TO_FACTORYDROID_EVENT_NAMES,
3521
+ toolToCanonicalEventNames: FACTORYDROID_TO_CANONICAL_EVENT_NAMES,
3522
+ projectDirVar: "$FACTORY_PROJECT_DIR"
3523
+ };
3581
3524
  var FactorydroidHooks = class _FactorydroidHooks extends ToolHooks {
3582
3525
  constructor(params) {
3583
3526
  super({
@@ -3629,7 +3572,11 @@ var FactorydroidHooks = class _FactorydroidHooks extends ToolHooks {
3629
3572
  );
3630
3573
  }
3631
3574
  const config = rulesyncHooks.getJson();
3632
- const factorydroidHooks = canonicalToFactorydroidHooks(config);
3575
+ const factorydroidHooks = canonicalToToolHooks({
3576
+ config,
3577
+ toolOverrideHooks: config.factorydroid?.hooks,
3578
+ converterConfig: FACTORYDROID_CONVERTER_CONFIG
3579
+ });
3633
3580
  const merged = { ...settings, hooks: factorydroidHooks };
3634
3581
  const fileContent = JSON.stringify(merged, null, 2);
3635
3582
  return new _FactorydroidHooks({
@@ -3652,7 +3599,10 @@ var FactorydroidHooks = class _FactorydroidHooks extends ToolHooks {
3652
3599
  }
3653
3600
  );
3654
3601
  }
3655
- const hooks = factorydroidHooksToCanonical(settings.hooks);
3602
+ const hooks = toolHooksToCanonical({
3603
+ hooks: settings.hooks,
3604
+ converterConfig: FACTORYDROID_CONVERTER_CONFIG
3605
+ });
3656
3606
  return this.toRulesyncHooksDefault({
3657
3607
  fileContent: JSON.stringify({ version: 1, hooks }, null, 2)
3658
3608
  });
@@ -7780,7 +7730,8 @@ var RulesyncSkillFrontmatterSchemaInternal = import_mini23.z.looseObject({
7780
7730
  targets: import_mini23.z._default(RulesyncTargetsSchema, ["*"]),
7781
7731
  claudecode: import_mini23.z.optional(
7782
7732
  import_mini23.z.looseObject({
7783
- "allowed-tools": import_mini23.z.optional(import_mini23.z.array(import_mini23.z.string()))
7733
+ "allowed-tools": import_mini23.z.optional(import_mini23.z.array(import_mini23.z.string())),
7734
+ model: import_mini23.z.optional(import_mini23.z.string())
7784
7735
  })
7785
7736
  ),
7786
7737
  codexcli: import_mini23.z.optional(
@@ -8216,7 +8167,8 @@ var import_mini26 = require("zod/mini");
8216
8167
  var ClaudecodeSkillFrontmatterSchema = import_mini26.z.looseObject({
8217
8168
  name: import_mini26.z.string(),
8218
8169
  description: import_mini26.z.string(),
8219
- "allowed-tools": import_mini26.z.optional(import_mini26.z.array(import_mini26.z.string()))
8170
+ "allowed-tools": import_mini26.z.optional(import_mini26.z.array(import_mini26.z.string())),
8171
+ model: import_mini26.z.optional(import_mini26.z.string())
8220
8172
  });
8221
8173
  var ClaudecodeSkill = class _ClaudecodeSkill extends ToolSkill {
8222
8174
  constructor({
@@ -8282,15 +8234,15 @@ var ClaudecodeSkill = class _ClaudecodeSkill extends ToolSkill {
8282
8234
  }
8283
8235
  toRulesyncSkill() {
8284
8236
  const frontmatter = this.getFrontmatter();
8237
+ const claudecodeSection = {
8238
+ ...frontmatter["allowed-tools"] && { "allowed-tools": frontmatter["allowed-tools"] },
8239
+ ...frontmatter.model && { model: frontmatter.model }
8240
+ };
8285
8241
  const rulesyncFrontmatter = {
8286
8242
  name: frontmatter.name,
8287
8243
  description: frontmatter.description,
8288
8244
  targets: ["*"],
8289
- ...frontmatter["allowed-tools"] && {
8290
- claudecode: {
8291
- "allowed-tools": frontmatter["allowed-tools"]
8292
- }
8293
- }
8245
+ ...Object.keys(claudecodeSection).length > 0 && { claudecode: claudecodeSection }
8294
8246
  };
8295
8247
  return new RulesyncSkill({
8296
8248
  baseDir: this.baseDir,
@@ -8312,7 +8264,12 @@ var ClaudecodeSkill = class _ClaudecodeSkill extends ToolSkill {
8312
8264
  const claudecodeFrontmatter = {
8313
8265
  name: rulesyncFrontmatter.name,
8314
8266
  description: rulesyncFrontmatter.description,
8315
- "allowed-tools": rulesyncFrontmatter.claudecode?.["allowed-tools"]
8267
+ ...rulesyncFrontmatter.claudecode?.["allowed-tools"] && {
8268
+ "allowed-tools": rulesyncFrontmatter.claudecode["allowed-tools"]
8269
+ },
8270
+ ...rulesyncFrontmatter.claudecode?.model && {
8271
+ model: rulesyncFrontmatter.claudecode.model
8272
+ }
8316
8273
  };
8317
8274
  const settablePaths = _ClaudecodeSkill.getSettablePaths({ global });
8318
8275
  return new _ClaudecodeSkill({
@@ -10442,7 +10399,7 @@ var ToolSubagent = class extends ToolFile {
10442
10399
  // src/features/subagents/simulated-subagent.ts
10443
10400
  var SimulatedSubagentFrontmatterSchema = import_mini38.z.object({
10444
10401
  name: import_mini38.z.string(),
10445
- description: import_mini38.z.string()
10402
+ description: import_mini38.z.optional(import_mini38.z.string())
10446
10403
  });
10447
10404
  var SimulatedSubagent = class extends ToolSubagent {
10448
10405
  frontmatter;
@@ -10666,7 +10623,7 @@ var import_mini39 = require("zod/mini");
10666
10623
  var RulesyncSubagentFrontmatterSchema = import_mini39.z.looseObject({
10667
10624
  targets: import_mini39.z._default(RulesyncTargetsSchema, ["*"]),
10668
10625
  name: import_mini39.z.string(),
10669
- description: import_mini39.z.string()
10626
+ description: import_mini39.z.optional(import_mini39.z.string())
10670
10627
  });
10671
10628
  var RulesyncSubagent = class _RulesyncSubagent extends RulesyncFile {
10672
10629
  frontmatter;
@@ -10737,7 +10694,7 @@ var RulesyncSubagent = class _RulesyncSubagent extends RulesyncFile {
10737
10694
  // src/features/subagents/claudecode-subagent.ts
10738
10695
  var ClaudecodeSubagentFrontmatterSchema = import_mini40.z.looseObject({
10739
10696
  name: import_mini40.z.string(),
10740
- description: import_mini40.z.string(),
10697
+ description: import_mini40.z.optional(import_mini40.z.string()),
10741
10698
  model: import_mini40.z.optional(import_mini40.z.string()),
10742
10699
  tools: import_mini40.z.optional(import_mini40.z.union([import_mini40.z.string(), import_mini40.z.array(import_mini40.z.string())])),
10743
10700
  permissionMode: import_mini40.z.optional(import_mini40.z.string()),
@@ -10947,7 +10904,7 @@ var CodexCliSubagent = class _CodexCliSubagent extends ToolSubagent {
10947
10904
  const rulesyncFrontmatter = {
10948
10905
  targets: ["codexcli"],
10949
10906
  name,
10950
- description: description ?? "",
10907
+ description,
10951
10908
  // Only include codexcli section if there are fields
10952
10909
  ...Object.keys(codexcliSection).length > 0 && { codexcli: codexcliSection }
10953
10910
  };
@@ -11060,7 +11017,7 @@ var import_mini42 = require("zod/mini");
11060
11017
  var REQUIRED_TOOL = "agent/runSubagent";
11061
11018
  var CopilotSubagentFrontmatterSchema = import_mini42.z.looseObject({
11062
11019
  name: import_mini42.z.string(),
11063
- description: import_mini42.z.string(),
11020
+ description: import_mini42.z.optional(import_mini42.z.string()),
11064
11021
  tools: import_mini42.z.optional(import_mini42.z.union([import_mini42.z.string(), import_mini42.z.array(import_mini42.z.string())]))
11065
11022
  });
11066
11023
  var normalizeTools = (tools) => {
@@ -11225,7 +11182,7 @@ var import_node_path85 = require("path");
11225
11182
  var import_mini43 = require("zod/mini");
11226
11183
  var CursorSubagentFrontmatterSchema = import_mini43.z.looseObject({
11227
11184
  name: import_mini43.z.string(),
11228
- description: import_mini43.z.string()
11185
+ description: import_mini43.z.optional(import_mini43.z.string())
11229
11186
  });
11230
11187
  var CursorSubagent = class _CursorSubagent extends ToolSubagent {
11231
11188
  frontmatter;
@@ -11430,7 +11387,7 @@ var KiroSubagent = class _KiroSubagent extends ToolSubagent {
11430
11387
  const rulesyncFrontmatter = {
11431
11388
  targets: ["kiro"],
11432
11389
  name,
11433
- description: description ?? "",
11390
+ description: description ?? void 0,
11434
11391
  // Only include kiro section if there are fields
11435
11392
  ...Object.keys(kiroSection).length > 0 && { kiro: kiroSection }
11436
11393
  };
@@ -11541,7 +11498,7 @@ var KiroSubagent = class _KiroSubagent extends ToolSubagent {
11541
11498
  var import_node_path87 = require("path");
11542
11499
  var import_mini45 = require("zod/mini");
11543
11500
  var OpenCodeSubagentFrontmatterSchema = import_mini45.z.looseObject({
11544
- description: import_mini45.z.string(),
11501
+ description: import_mini45.z.optional(import_mini45.z.string()),
11545
11502
  mode: import_mini45.z._default(import_mini45.z.string(), "subagent"),
11546
11503
  name: import_mini45.z.optional(import_mini45.z.string())
11547
11504
  });
@@ -12371,7 +12328,7 @@ var globStrategy = {
12371
12328
  },
12372
12329
  exportRulesyncData: ({ description, ...frontmatter }) => ({
12373
12330
  globs: parseGlobsString(frontmatter.globs),
12374
- description: description || "",
12331
+ description,
12375
12332
  antigravity: frontmatter
12376
12333
  })
12377
12334
  };
@@ -12383,7 +12340,7 @@ var manualStrategy = {
12383
12340
  }),
12384
12341
  exportRulesyncData: ({ description, ...frontmatter }) => ({
12385
12342
  globs: [],
12386
- description: description || "",
12343
+ description,
12387
12344
  antigravity: frontmatter
12388
12345
  })
12389
12346
  };
@@ -12395,7 +12352,7 @@ var alwaysOnStrategy = {
12395
12352
  }),
12396
12353
  exportRulesyncData: ({ description, ...frontmatter }) => ({
12397
12354
  globs: ["**/*"],
12398
- description: description || "",
12355
+ description,
12399
12356
  antigravity: frontmatter
12400
12357
  })
12401
12358
  };
@@ -12408,7 +12365,7 @@ var modelDecisionStrategy = {
12408
12365
  }),
12409
12366
  exportRulesyncData: ({ description, ...frontmatter }) => ({
12410
12367
  globs: [],
12411
- description: description || "",
12368
+ description,
12412
12369
  antigravity: frontmatter
12413
12370
  })
12414
12371
  };
@@ -12423,7 +12380,7 @@ var unknownStrategy = {
12423
12380
  },
12424
12381
  exportRulesyncData: ({ description, ...frontmatter }) => ({
12425
12382
  globs: frontmatter.globs ? parseGlobsString(frontmatter.globs) : ["**/*"],
12426
- description: description || "",
12383
+ description,
12427
12384
  antigravity: frontmatter
12428
12385
  })
12429
12386
  };
@@ -12445,7 +12402,7 @@ var inferenceStrategy = {
12445
12402
  },
12446
12403
  exportRulesyncData: ({ description, ...frontmatter }) => ({
12447
12404
  globs: frontmatter.globs ? parseGlobsString(frontmatter.globs) : ["**/*"],
12448
- description: description || "",
12405
+ description,
12449
12406
  antigravity: frontmatter
12450
12407
  })
12451
12408
  };
@@ -12575,7 +12532,6 @@ var AntigravityRule = class _AntigravityRule extends ToolRule {
12575
12532
  const strategy = STRATEGIES.find((s) => s.canHandle(this.frontmatter.trigger));
12576
12533
  let rulesyncData = {
12577
12534
  globs: [],
12578
- description: "",
12579
12535
  antigravity: this.frontmatter
12580
12536
  };
12581
12537
  if (strategy) {
@@ -12645,7 +12601,6 @@ var AugmentcodeLegacyRule = class _AugmentcodeLegacyRule extends ToolRule {
12645
12601
  const rulesyncFrontmatter = {
12646
12602
  root: this.isRoot(),
12647
12603
  targets: ["*"],
12648
- description: "",
12649
12604
  globs: this.isRoot() ? ["**/*"] : []
12650
12605
  };
12651
12606
  return new RulesyncRule({
@@ -12820,6 +12775,12 @@ var ClaudecodeLegacyRule = class _ClaudecodeLegacyRule extends ToolRule {
12820
12775
  relativeDirPath: ".",
12821
12776
  relativeFilePath: "CLAUDE.md"
12822
12777
  },
12778
+ alternativeRoots: [
12779
+ {
12780
+ relativeDirPath: ".claude",
12781
+ relativeFilePath: "CLAUDE.md"
12782
+ }
12783
+ ],
12823
12784
  nonRoot: {
12824
12785
  relativeDirPath: buildToolPath(".claude", "memories", excludeToolDir)
12825
12786
  }
@@ -12829,18 +12790,19 @@ var ClaudecodeLegacyRule = class _ClaudecodeLegacyRule extends ToolRule {
12829
12790
  baseDir = process.cwd(),
12830
12791
  relativeFilePath,
12831
12792
  validate = true,
12832
- global = false
12793
+ global = false,
12794
+ relativeDirPath: overrideDirPath
12833
12795
  }) {
12834
12796
  const paths = this.getSettablePaths({ global });
12835
12797
  const isRoot = relativeFilePath === paths.root.relativeFilePath;
12836
12798
  if (isRoot) {
12837
- const relativePath2 = paths.root.relativeFilePath;
12799
+ const rootDirPath = overrideDirPath ?? paths.root.relativeDirPath;
12838
12800
  const fileContent2 = await readFileContent(
12839
- (0, import_node_path95.join)(baseDir, paths.root.relativeDirPath, relativePath2)
12801
+ (0, import_node_path95.join)(baseDir, rootDirPath, paths.root.relativeFilePath)
12840
12802
  );
12841
12803
  return new _ClaudecodeLegacyRule({
12842
12804
  baseDir,
12843
- relativeDirPath: paths.root.relativeDirPath,
12805
+ relativeDirPath: rootDirPath,
12844
12806
  relativeFilePath: paths.root.relativeFilePath,
12845
12807
  fileContent: fileContent2,
12846
12808
  validate,
@@ -12935,6 +12897,12 @@ var ClaudecodeRule = class _ClaudecodeRule extends ToolRule {
12935
12897
  relativeDirPath: ".",
12936
12898
  relativeFilePath: "CLAUDE.md"
12937
12899
  },
12900
+ alternativeRoots: [
12901
+ {
12902
+ relativeDirPath: ".claude",
12903
+ relativeFilePath: "CLAUDE.md"
12904
+ }
12905
+ ],
12938
12906
  nonRoot: {
12939
12907
  relativeDirPath: buildToolPath(".claude", "rules", excludeToolDir)
12940
12908
  }
@@ -12967,17 +12935,19 @@ var ClaudecodeRule = class _ClaudecodeRule extends ToolRule {
12967
12935
  baseDir = process.cwd(),
12968
12936
  relativeFilePath,
12969
12937
  validate = true,
12970
- global = false
12938
+ global = false,
12939
+ relativeDirPath: overrideDirPath
12971
12940
  }) {
12972
12941
  const paths = this.getSettablePaths({ global });
12973
12942
  const isRoot = relativeFilePath === paths.root.relativeFilePath;
12974
12943
  if (isRoot) {
12944
+ const rootDirPath = overrideDirPath ?? paths.root.relativeDirPath;
12975
12945
  const fileContent2 = await readFileContent(
12976
- (0, import_node_path96.join)(baseDir, paths.root.relativeDirPath, paths.root.relativeFilePath)
12946
+ (0, import_node_path96.join)(baseDir, rootDirPath, paths.root.relativeFilePath)
12977
12947
  );
12978
12948
  return new _ClaudecodeRule({
12979
12949
  baseDir,
12980
- relativeDirPath: paths.root.relativeDirPath,
12950
+ relativeDirPath: rootDirPath,
12981
12951
  relativeFilePath: paths.root.relativeFilePath,
12982
12952
  frontmatter: {},
12983
12953
  body: fileContent2.trim(),
@@ -12989,16 +12959,12 @@ var ClaudecodeRule = class _ClaudecodeRule extends ToolRule {
12989
12959
  throw new Error(`nonRoot path is not set for ${relativeFilePath}`);
12990
12960
  }
12991
12961
  const relativePath = (0, import_node_path96.join)(paths.nonRoot.relativeDirPath, relativeFilePath);
12992
- const fileContent = await readFileContent((0, import_node_path96.join)(baseDir, relativePath));
12993
- const { frontmatter, body: content } = parseFrontmatter(
12994
- fileContent,
12995
- (0, import_node_path96.join)(baseDir, relativePath)
12996
- );
12962
+ const filePath = (0, import_node_path96.join)(baseDir, relativePath);
12963
+ const fileContent = await readFileContent(filePath);
12964
+ const { frontmatter, body: content } = parseFrontmatter(fileContent, filePath);
12997
12965
  const result = ClaudecodeRuleFrontmatterSchema.safeParse(frontmatter);
12998
12966
  if (!result.success) {
12999
- throw new Error(
13000
- `Invalid frontmatter in ${(0, import_node_path96.join)(baseDir, relativePath)}: ${formatError(result.error)}`
13001
- );
12967
+ throw new Error(`Invalid frontmatter in ${filePath}: ${formatError(result.error)}`);
13002
12968
  }
13003
12969
  return new _ClaudecodeRule({
13004
12970
  baseDir,
@@ -13430,7 +13396,8 @@ var CopilotRule = class _CopilotRule extends ToolRule {
13430
13396
  const isRoot = relativeFilePath === paths.root.relativeFilePath;
13431
13397
  if (isRoot) {
13432
13398
  const relativePath2 = (0, import_node_path99.join)(paths.root.relativeDirPath, paths.root.relativeFilePath);
13433
- const fileContent2 = await readFileContent((0, import_node_path99.join)(baseDir, relativePath2));
13399
+ const filePath2 = (0, import_node_path99.join)(baseDir, relativePath2);
13400
+ const fileContent2 = await readFileContent(filePath2);
13434
13401
  return new _CopilotRule({
13435
13402
  baseDir,
13436
13403
  relativeDirPath: paths.root.relativeDirPath,
@@ -13445,16 +13412,12 @@ var CopilotRule = class _CopilotRule extends ToolRule {
13445
13412
  throw new Error(`nonRoot path is not set for ${relativeFilePath}`);
13446
13413
  }
13447
13414
  const relativePath = (0, import_node_path99.join)(paths.nonRoot.relativeDirPath, relativeFilePath);
13448
- const fileContent = await readFileContent((0, import_node_path99.join)(baseDir, relativePath));
13449
- const { frontmatter, body: content } = parseFrontmatter(
13450
- fileContent,
13451
- (0, import_node_path99.join)(baseDir, relativePath)
13452
- );
13415
+ const filePath = (0, import_node_path99.join)(baseDir, relativePath);
13416
+ const fileContent = await readFileContent(filePath);
13417
+ const { frontmatter, body: content } = parseFrontmatter(fileContent, filePath);
13453
13418
  const result = CopilotRuleFrontmatterSchema.safeParse(frontmatter);
13454
13419
  if (!result.success) {
13455
- throw new Error(
13456
- `Invalid frontmatter in ${(0, import_node_path99.join)(baseDir, relativeFilePath)}: ${formatError(result.error)}`
13457
- );
13420
+ throw new Error(`Invalid frontmatter in ${filePath}: ${formatError(result.error)}`);
13458
13421
  }
13459
13422
  return new _CopilotRule({
13460
13423
  baseDir,
@@ -15341,35 +15304,62 @@ var RulesProcessor = class extends FeatureProcessor {
15341
15304
  const settablePaths = factory.class.getSettablePaths({
15342
15305
  global: this.global
15343
15306
  });
15307
+ const resolveRelativeDirPath = (filePath) => {
15308
+ const dirName = (0, import_node_path113.dirname)((0, import_node_path113.relative)(this.baseDir, filePath));
15309
+ return dirName === "" ? "." : dirName;
15310
+ };
15311
+ const findFilesWithFallback = async (primaryGlob, alternativeRoots, buildAltGlob) => {
15312
+ const primaryFilePaths = await findFilesByGlobs(primaryGlob);
15313
+ if (primaryFilePaths.length > 0) {
15314
+ return primaryFilePaths;
15315
+ }
15316
+ if (alternativeRoots) {
15317
+ return findFilesByGlobs(alternativeRoots.map(buildAltGlob));
15318
+ }
15319
+ return [];
15320
+ };
15344
15321
  const rootToolRules = await (async () => {
15345
15322
  if (!settablePaths.root) {
15346
15323
  return [];
15347
15324
  }
15348
- const rootFilePaths = await findFilesByGlobs(
15325
+ const uniqueRootFilePaths = await findFilesWithFallback(
15349
15326
  (0, import_node_path113.join)(
15350
15327
  this.baseDir,
15351
15328
  settablePaths.root.relativeDirPath ?? ".",
15352
15329
  settablePaths.root.relativeFilePath
15353
- )
15330
+ ),
15331
+ settablePaths.alternativeRoots,
15332
+ (alt) => (0, import_node_path113.join)(this.baseDir, alt.relativeDirPath, alt.relativeFilePath)
15354
15333
  );
15355
15334
  if (forDeletion) {
15356
- return rootFilePaths.map(
15357
- (filePath) => factory.class.forDeletion({
15335
+ return uniqueRootFilePaths.map((filePath) => {
15336
+ const relativeDirPath = resolveRelativeDirPath(filePath);
15337
+ checkPathTraversal({
15338
+ relativePath: relativeDirPath,
15339
+ intendedRootDir: this.baseDir
15340
+ });
15341
+ return factory.class.forDeletion({
15358
15342
  baseDir: this.baseDir,
15359
- relativeDirPath: settablePaths.root?.relativeDirPath ?? ".",
15343
+ relativeDirPath,
15360
15344
  relativeFilePath: (0, import_node_path113.basename)(filePath),
15361
15345
  global: this.global
15362
- })
15363
- ).filter((rule) => rule.isDeletable());
15346
+ });
15347
+ }).filter((rule) => rule.isDeletable());
15364
15348
  }
15365
15349
  return await Promise.all(
15366
- rootFilePaths.map(
15367
- (filePath) => factory.class.fromFile({
15350
+ uniqueRootFilePaths.map((filePath) => {
15351
+ const relativeDirPath = resolveRelativeDirPath(filePath);
15352
+ checkPathTraversal({
15353
+ relativePath: relativeDirPath,
15354
+ intendedRootDir: this.baseDir
15355
+ });
15356
+ return factory.class.fromFile({
15368
15357
  baseDir: this.baseDir,
15369
15358
  relativeFilePath: (0, import_node_path113.basename)(filePath),
15359
+ relativeDirPath,
15370
15360
  global: this.global
15371
- })
15372
- )
15361
+ });
15362
+ })
15373
15363
  );
15374
15364
  })();
15375
15365
  logger.debug(`Found ${rootToolRules.length} root tool rule files`);
@@ -15383,17 +15373,24 @@ var RulesProcessor = class extends FeatureProcessor {
15383
15373
  if (!settablePaths.root) {
15384
15374
  return [];
15385
15375
  }
15386
- const localRootFilePaths = await findFilesByGlobs(
15387
- (0, import_node_path113.join)(this.baseDir, settablePaths.root.relativeDirPath ?? ".", "CLAUDE.local.md")
15376
+ const uniqueLocalRootFilePaths = await findFilesWithFallback(
15377
+ (0, import_node_path113.join)(this.baseDir, settablePaths.root.relativeDirPath ?? ".", "CLAUDE.local.md"),
15378
+ settablePaths.alternativeRoots,
15379
+ (alt) => (0, import_node_path113.join)(this.baseDir, alt.relativeDirPath, "CLAUDE.local.md")
15388
15380
  );
15389
- return localRootFilePaths.map(
15390
- (filePath) => factory.class.forDeletion({
15381
+ return uniqueLocalRootFilePaths.map((filePath) => {
15382
+ const relativeDirPath = resolveRelativeDirPath(filePath);
15383
+ checkPathTraversal({
15384
+ relativePath: relativeDirPath,
15385
+ intendedRootDir: this.baseDir
15386
+ });
15387
+ return factory.class.forDeletion({
15391
15388
  baseDir: this.baseDir,
15392
- relativeDirPath: settablePaths.root?.relativeDirPath ?? ".",
15389
+ relativeDirPath,
15393
15390
  relativeFilePath: (0, import_node_path113.basename)(filePath),
15394
15391
  global: this.global
15395
- })
15396
- ).filter((rule) => rule.isDeletable());
15392
+ });
15393
+ }).filter((rule) => rule.isDeletable());
15397
15394
  })();
15398
15395
  logger.debug(`Found ${localRootToolRules.length} local root tool rule files for deletion`);
15399
15396
  const nonRootToolRules = await (async () => {
@@ -16839,6 +16836,14 @@ async function processEmptyFeatureGeneration(params) {
16839
16836
  }
16840
16837
  return { count: totalCount, paths: [], hasDiff };
16841
16838
  }
16839
+ function warnUnsupportedTargets(params) {
16840
+ const { config, supportedTargets, featureName } = params;
16841
+ for (const target of config.getTargets()) {
16842
+ if (!supportedTargets.includes(target) && config.getFeatures(target).includes(featureName)) {
16843
+ logger.warn(`Target '${target}' does not support the feature '${featureName}'. Skipping.`);
16844
+ }
16845
+ }
16846
+ }
16842
16847
  async function checkRulesyncDirExists(params) {
16843
16848
  return fileExists((0, import_node_path116.join)(params.baseDir, RULESYNC_RELATIVE_DIR_PATH));
16844
16849
  }
@@ -16876,10 +16881,9 @@ async function generateRulesCore(params) {
16876
16881
  let totalCount = 0;
16877
16882
  const allPaths = [];
16878
16883
  let hasDiff = false;
16879
- const toolTargets = (0, import_es_toolkit4.intersection)(
16880
- config.getTargets(),
16881
- RulesProcessor.getToolTargets({ global: config.getGlobal() })
16882
- );
16884
+ const supportedTargets = RulesProcessor.getToolTargets({ global: config.getGlobal() });
16885
+ const toolTargets = (0, import_es_toolkit4.intersection)(config.getTargets(), supportedTargets);
16886
+ warnUnsupportedTargets({ config, supportedTargets, featureName: "rules" });
16883
16887
  for (const baseDir of config.getBaseDirs()) {
16884
16888
  for (const toolTarget of toolTargets) {
16885
16889
  if (!config.getFeatures(toolTarget).includes("rules")) {
@@ -16911,13 +16915,19 @@ async function generateRulesCore(params) {
16911
16915
  }
16912
16916
  async function generateIgnoreCore(params) {
16913
16917
  const { config } = params;
16918
+ const supportedIgnoreTargets = IgnoreProcessor.getToolTargets();
16919
+ warnUnsupportedTargets({
16920
+ config,
16921
+ supportedTargets: supportedIgnoreTargets,
16922
+ featureName: "ignore"
16923
+ });
16914
16924
  if (config.getGlobal()) {
16915
16925
  return { count: 0, paths: [], hasDiff: false };
16916
16926
  }
16917
16927
  let totalCount = 0;
16918
16928
  const allPaths = [];
16919
16929
  let hasDiff = false;
16920
- for (const toolTarget of (0, import_es_toolkit4.intersection)(config.getTargets(), IgnoreProcessor.getToolTargets())) {
16930
+ for (const toolTarget of (0, import_es_toolkit4.intersection)(config.getTargets(), supportedIgnoreTargets)) {
16921
16931
  if (!config.getFeatures(toolTarget).includes("ignore")) {
16922
16932
  continue;
16923
16933
  }
@@ -16961,10 +16971,9 @@ async function generateMcpCore(params) {
16961
16971
  let totalCount = 0;
16962
16972
  const allPaths = [];
16963
16973
  let hasDiff = false;
16964
- const toolTargets = (0, import_es_toolkit4.intersection)(
16965
- config.getTargets(),
16966
- McpProcessor.getToolTargets({ global: config.getGlobal() })
16967
- );
16974
+ const supportedMcpTargets = McpProcessor.getToolTargets({ global: config.getGlobal() });
16975
+ const toolTargets = (0, import_es_toolkit4.intersection)(config.getTargets(), supportedMcpTargets);
16976
+ warnUnsupportedTargets({ config, supportedTargets: supportedMcpTargets, featureName: "mcp" });
16968
16977
  for (const baseDir of config.getBaseDirs()) {
16969
16978
  for (const toolTarget of toolTargets) {
16970
16979
  if (!config.getFeatures(toolTarget).includes("mcp")) {
@@ -16995,13 +17004,16 @@ async function generateCommandsCore(params) {
16995
17004
  let totalCount = 0;
16996
17005
  const allPaths = [];
16997
17006
  let hasDiff = false;
16998
- const toolTargets = (0, import_es_toolkit4.intersection)(
16999
- config.getTargets(),
17000
- CommandsProcessor.getToolTargets({
17001
- global: config.getGlobal(),
17002
- includeSimulated: config.getSimulateCommands()
17003
- })
17004
- );
17007
+ const supportedCommandsTargets = CommandsProcessor.getToolTargets({
17008
+ global: config.getGlobal(),
17009
+ includeSimulated: config.getSimulateCommands()
17010
+ });
17011
+ const toolTargets = (0, import_es_toolkit4.intersection)(config.getTargets(), supportedCommandsTargets);
17012
+ warnUnsupportedTargets({
17013
+ config,
17014
+ supportedTargets: supportedCommandsTargets,
17015
+ featureName: "commands"
17016
+ });
17005
17017
  for (const baseDir of config.getBaseDirs()) {
17006
17018
  for (const toolTarget of toolTargets) {
17007
17019
  if (!config.getFeatures(toolTarget).includes("commands")) {
@@ -17032,13 +17044,16 @@ async function generateSubagentsCore(params) {
17032
17044
  let totalCount = 0;
17033
17045
  const allPaths = [];
17034
17046
  let hasDiff = false;
17035
- const toolTargets = (0, import_es_toolkit4.intersection)(
17036
- config.getTargets(),
17037
- SubagentsProcessor.getToolTargets({
17038
- global: config.getGlobal(),
17039
- includeSimulated: config.getSimulateSubagents()
17040
- })
17041
- );
17047
+ const supportedSubagentsTargets = SubagentsProcessor.getToolTargets({
17048
+ global: config.getGlobal(),
17049
+ includeSimulated: config.getSimulateSubagents()
17050
+ });
17051
+ const toolTargets = (0, import_es_toolkit4.intersection)(config.getTargets(), supportedSubagentsTargets);
17052
+ warnUnsupportedTargets({
17053
+ config,
17054
+ supportedTargets: supportedSubagentsTargets,
17055
+ featureName: "subagents"
17056
+ });
17042
17057
  for (const baseDir of config.getBaseDirs()) {
17043
17058
  for (const toolTarget of toolTargets) {
17044
17059
  if (!config.getFeatures(toolTarget).includes("subagents")) {
@@ -17070,13 +17085,16 @@ async function generateSkillsCore(params) {
17070
17085
  const allPaths = [];
17071
17086
  let hasDiff = false;
17072
17087
  const allSkills = [];
17073
- const toolTargets = (0, import_es_toolkit4.intersection)(
17074
- config.getTargets(),
17075
- SkillsProcessor.getToolTargets({
17076
- global: config.getGlobal(),
17077
- includeSimulated: config.getSimulateSkills()
17078
- })
17079
- );
17088
+ const supportedSkillsTargets = SkillsProcessor.getToolTargets({
17089
+ global: config.getGlobal(),
17090
+ includeSimulated: config.getSimulateSkills()
17091
+ });
17092
+ const toolTargets = (0, import_es_toolkit4.intersection)(config.getTargets(), supportedSkillsTargets);
17093
+ warnUnsupportedTargets({
17094
+ config,
17095
+ supportedTargets: supportedSkillsTargets,
17096
+ featureName: "skills"
17097
+ });
17080
17098
  for (const baseDir of config.getBaseDirs()) {
17081
17099
  for (const toolTarget of toolTargets) {
17082
17100
  if (!config.getFeatures(toolTarget).includes("skills")) {
@@ -17112,10 +17130,9 @@ async function generateHooksCore(params) {
17112
17130
  let totalCount = 0;
17113
17131
  const allPaths = [];
17114
17132
  let hasDiff = false;
17115
- const toolTargets = (0, import_es_toolkit4.intersection)(
17116
- config.getTargets(),
17117
- HooksProcessor.getToolTargets({ global: config.getGlobal() })
17118
- );
17133
+ const supportedHooksTargets = HooksProcessor.getToolTargets({ global: config.getGlobal() });
17134
+ const toolTargets = (0, import_es_toolkit4.intersection)(config.getTargets(), supportedHooksTargets);
17135
+ warnUnsupportedTargets({ config, supportedTargets: supportedHooksTargets, featureName: "hooks" });
17119
17136
  for (const baseDir of config.getBaseDirs()) {
17120
17137
  for (const toolTarget of toolTargets) {
17121
17138
  if (!config.getFeatures(toolTarget).includes("hooks")) {
@@ -17291,7 +17308,7 @@ var RULESYNC_HEADER = "# Generated by Rulesync";
17291
17308
  var LEGACY_RULESYNC_HEADER = "# Generated by rulesync - AI tool configuration files";
17292
17309
  var RULESYNC_IGNORE_ENTRIES = [
17293
17310
  // Rulesync curated (fetched) skills
17294
- ".rulesync/skills/.curated/",
17311
+ `${RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH}/`,
17295
17312
  // AGENTS.md
17296
17313
  "**/AGENTS.md",
17297
17314
  "**/.agents/",
@@ -17511,6 +17528,7 @@ async function importRulesCore(params) {
17511
17528
  });
17512
17529
  const toolFiles = await rulesProcessor.loadToolFiles();
17513
17530
  if (toolFiles.length === 0) {
17531
+ logger.warn(`No rule files found for ${tool}. Skipping import.`);
17514
17532
  return 0;
17515
17533
  }
17516
17534
  const rulesyncFiles = await rulesProcessor.convertToolFilesToRulesyncFiles(toolFiles);
@@ -17538,6 +17556,7 @@ async function importIgnoreCore(params) {
17538
17556
  });
17539
17557
  const toolFiles = await ignoreProcessor.loadToolFiles();
17540
17558
  if (toolFiles.length === 0) {
17559
+ logger.warn(`No ignore files found for ${tool}. Skipping import.`);
17541
17560
  return 0;
17542
17561
  }
17543
17562
  const rulesyncFiles = await ignoreProcessor.convertToolFilesToRulesyncFiles(toolFiles);
@@ -17567,6 +17586,7 @@ async function importMcpCore(params) {
17567
17586
  });
17568
17587
  const toolFiles = await mcpProcessor.loadToolFiles();
17569
17588
  if (toolFiles.length === 0) {
17589
+ logger.warn(`No MCP files found for ${tool}. Skipping import.`);
17570
17590
  return 0;
17571
17591
  }
17572
17592
  const rulesyncFiles = await mcpProcessor.convertToolFilesToRulesyncFiles(toolFiles);
@@ -17593,6 +17613,7 @@ async function importCommandsCore(params) {
17593
17613
  });
17594
17614
  const toolFiles = await commandsProcessor.loadToolFiles();
17595
17615
  if (toolFiles.length === 0) {
17616
+ logger.warn(`No command files found for ${tool}. Skipping import.`);
17596
17617
  return 0;
17597
17618
  }
17598
17619
  const rulesyncFiles = await commandsProcessor.convertToolFilesToRulesyncFiles(toolFiles);
@@ -17619,6 +17640,7 @@ async function importSubagentsCore(params) {
17619
17640
  });
17620
17641
  const toolFiles = await subagentsProcessor.loadToolFiles();
17621
17642
  if (toolFiles.length === 0) {
17643
+ logger.warn(`No subagent files found for ${tool}. Skipping import.`);
17622
17644
  return 0;
17623
17645
  }
17624
17646
  const rulesyncFiles = await subagentsProcessor.convertToolFilesToRulesyncFiles(toolFiles);
@@ -17645,6 +17667,7 @@ async function importSkillsCore(params) {
17645
17667
  });
17646
17668
  const toolDirs = await skillsProcessor.loadToolDirs();
17647
17669
  if (toolDirs.length === 0) {
17670
+ logger.warn(`No skill directories found for ${tool}. Skipping import.`);
17648
17671
  return 0;
17649
17672
  }
17650
17673
  const rulesyncDirs = await skillsProcessor.convertToolDirsToRulesyncDirs(toolDirs);
@@ -17676,6 +17699,7 @@ async function importHooksCore(params) {
17676
17699
  });
17677
17700
  const toolFiles = await hooksProcessor.loadToolFiles();
17678
17701
  if (toolFiles.length === 0) {
17702
+ logger.warn(`No hooks files found for ${tool}. Skipping import.`);
17679
17703
  return 0;
17680
17704
  }
17681
17705
  const rulesyncFiles = await hooksProcessor.convertToolFilesToRulesyncFiles(toolFiles);
@@ -18069,15 +18093,22 @@ function normalizeSourceKey(source) {
18069
18093
  "https://www.github.com/",
18070
18094
  "https://github.com/",
18071
18095
  "http://www.github.com/",
18072
- "http://github.com/"
18096
+ "http://github.com/",
18097
+ "https://www.gitlab.com/",
18098
+ "https://gitlab.com/",
18099
+ "http://www.gitlab.com/",
18100
+ "http://gitlab.com/"
18073
18101
  ]) {
18074
18102
  if (key.toLowerCase().startsWith(prefix)) {
18075
18103
  key = key.substring(prefix.length);
18076
18104
  break;
18077
18105
  }
18078
18106
  }
18079
- if (key.startsWith("github:")) {
18080
- key = key.substring("github:".length);
18107
+ for (const provider of ["github:", "gitlab:"]) {
18108
+ if (key.startsWith(provider)) {
18109
+ key = key.substring(provider.length);
18110
+ break;
18111
+ }
18081
18112
  }
18082
18113
  key = key.replace(/\/+$/, "");
18083
18114
  key = key.replace(/\.git$/, "");
@@ -18161,10 +18192,9 @@ async function resolveAndFetchSources(params) {
18161
18192
  allFetchedSkillNames.add(name);
18162
18193
  }
18163
18194
  } catch (error) {
18195
+ logger.error(`Failed to fetch source "${sourceEntry.source}": ${formatError(error)}`);
18164
18196
  if (error instanceof GitHubClientError) {
18165
18197
  logGitHubAuthHints(error);
18166
- } else {
18167
- logger.error(`Failed to fetch source "${sourceEntry.source}": ${formatError(error)}`);
18168
18198
  }
18169
18199
  }
18170
18200
  }
@@ -18199,7 +18229,8 @@ async function fetchSource(params) {
18199
18229
  let { lock } = params;
18200
18230
  const parsed = parseSource(sourceEntry.source);
18201
18231
  if (parsed.provider === "gitlab") {
18202
- throw new Error("GitLab sources are not yet supported.");
18232
+ logger.warn(`GitLab sources are not yet supported. Skipping "${sourceEntry.source}".`);
18233
+ return { skillCount: 0, fetchedSkillNames: [], updatedLock: lock };
18203
18234
  }
18204
18235
  const sourceKey = sourceEntry.source;
18205
18236
  const locked = getLockedSource(lock, sourceKey);
@@ -20161,7 +20192,7 @@ async function updateCommand(currentVersion, options) {
20161
20192
  }
20162
20193
 
20163
20194
  // src/cli/index.ts
20164
- var getVersion = () => "7.11.0";
20195
+ var getVersion = () => "7.12.1";
20165
20196
  var main = async () => {
20166
20197
  const program = new import_commander.Command();
20167
20198
  const version = getVersion();