rulesync 7.12.0 → 7.12.2

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.
@@ -608,7 +608,7 @@ var ToolCommand = class extends AiFile {
608
608
 
609
609
  // src/features/commands/simulated-command.ts
610
610
  var SimulatedCommandFrontmatterSchema = import_mini2.z.object({
611
- description: import_mini2.z.string()
611
+ description: import_mini2.z.optional(import_mini2.z.string())
612
612
  });
613
613
  var SimulatedCommand = class _SimulatedCommand extends ToolCommand {
614
614
  frontmatter;
@@ -828,7 +828,7 @@ var RulesyncTargetsSchema = import_mini3.z.array(import_mini3.z.enum(ALL_TOOL_TA
828
828
  // src/features/commands/rulesync-command.ts
829
829
  var RulesyncCommandFrontmatterSchema = import_mini4.z.looseObject({
830
830
  targets: import_mini4.z._default(RulesyncTargetsSchema, ["*"]),
831
- description: import_mini4.z.string()
831
+ description: import_mini4.z.optional(import_mini4.z.string())
832
832
  });
833
833
  var RulesyncCommand = class _RulesyncCommand extends RulesyncFile {
834
834
  frontmatter;
@@ -916,7 +916,7 @@ var AntigravityWorkflowFrontmatterSchema = import_mini5.z.looseObject({
916
916
  turbo: import_mini5.z.optional(import_mini5.z.boolean())
917
917
  });
918
918
  var AntigravityCommandFrontmatterSchema = import_mini5.z.looseObject({
919
- description: import_mini5.z.string(),
919
+ description: import_mini5.z.optional(import_mini5.z.string()),
920
920
  // Support for workflow-specific configuration
921
921
  ...AntigravityWorkflowFrontmatterSchema.shape
922
922
  });
@@ -1089,7 +1089,7 @@ ${body}${turboDirective}`;
1089
1089
  var import_node_path8 = require("path");
1090
1090
  var import_mini6 = require("zod/mini");
1091
1091
  var ClaudecodeCommandFrontmatterSchema = import_mini6.z.looseObject({
1092
- description: import_mini6.z.string(),
1092
+ description: import_mini6.z.optional(import_mini6.z.string()),
1093
1093
  "allowed-tools": import_mini6.z.optional(import_mini6.z.union([import_mini6.z.string(), import_mini6.z.array(import_mini6.z.string())])),
1094
1094
  "argument-hint": import_mini6.z.optional(import_mini6.z.string()),
1095
1095
  model: import_mini6.z.optional(import_mini6.z.string()),
@@ -1244,8 +1244,7 @@ var ClineCommand = class _ClineCommand extends ToolCommand {
1244
1244
  }
1245
1245
  toRulesyncCommand() {
1246
1246
  const rulesyncFrontmatter = {
1247
- targets: ["*"],
1248
- description: ""
1247
+ targets: ["*"]
1249
1248
  };
1250
1249
  return new RulesyncCommand({
1251
1250
  baseDir: process.cwd(),
@@ -1330,8 +1329,7 @@ var CodexcliCommand = class _CodexcliCommand extends ToolCommand {
1330
1329
  }
1331
1330
  toRulesyncCommand() {
1332
1331
  const rulesyncFrontmatter = {
1333
- targets: ["*"],
1334
- description: ""
1332
+ targets: ["*"]
1335
1333
  };
1336
1334
  return new RulesyncCommand({
1337
1335
  baseDir: ".",
@@ -1409,7 +1407,7 @@ var import_node_path11 = require("path");
1409
1407
  var import_mini7 = require("zod/mini");
1410
1408
  var CopilotCommandFrontmatterSchema = import_mini7.z.looseObject({
1411
1409
  mode: import_mini7.z.optional(import_mini7.z.string()),
1412
- description: import_mini7.z.string()
1410
+ description: import_mini7.z.optional(import_mini7.z.string())
1413
1411
  });
1414
1412
  var CopilotCommand = class _CopilotCommand extends ToolCommand {
1415
1413
  frontmatter;
@@ -1592,7 +1590,7 @@ var CursorCommand = class _CursorCommand extends ToolCommand {
1592
1590
  return this.frontmatter;
1593
1591
  }
1594
1592
  toRulesyncCommand() {
1595
- const { description = "", ...restFields } = this.frontmatter;
1593
+ const { description, ...restFields } = this.frontmatter;
1596
1594
  const rulesyncFrontmatter = {
1597
1595
  targets: ["*"],
1598
1596
  description,
@@ -1786,7 +1784,7 @@ var GeminiCliCommand = class _GeminiCliCommand extends ToolCommand {
1786
1784
  }
1787
1785
  return {
1788
1786
  ...result.data,
1789
- description: result.data.description || ""
1787
+ description: result.data.description
1790
1788
  };
1791
1789
  } catch (error) {
1792
1790
  throw new Error(
@@ -1808,7 +1806,7 @@ var GeminiCliCommand = class _GeminiCliCommand extends ToolCommand {
1808
1806
  const { description, prompt: _prompt, ...restFields } = this.frontmatter;
1809
1807
  const rulesyncFrontmatter = {
1810
1808
  targets: ["geminicli"],
1811
- description: description ?? "",
1809
+ description,
1812
1810
  // Preserve extra fields in geminicli section (excluding prompt which is the body)
1813
1811
  ...Object.keys(restFields).length > 0 && { geminicli: restFields }
1814
1812
  };
@@ -1837,8 +1835,9 @@ var GeminiCliCommand = class _GeminiCliCommand extends ToolCommand {
1837
1835
  prompt: rulesyncCommand.getBody(),
1838
1836
  ...geminicliFields
1839
1837
  };
1840
- const tomlContent = `description = "${geminiFrontmatter.description}"
1841
- prompt = """
1838
+ const descriptionLine = geminiFrontmatter.description !== void 0 ? `description = "${geminiFrontmatter.description}"
1839
+ ` : "";
1840
+ const tomlContent = `${descriptionLine}prompt = """
1842
1841
  ${geminiFrontmatter.prompt}
1843
1842
  """`;
1844
1843
  const paths = this.getSettablePaths({ global });
@@ -1908,8 +1907,7 @@ var KiloCommand = class _KiloCommand extends ToolCommand {
1908
1907
  }
1909
1908
  toRulesyncCommand() {
1910
1909
  const rulesyncFrontmatter = {
1911
- targets: ["*"],
1912
- description: ""
1910
+ targets: ["*"]
1913
1911
  };
1914
1912
  return new RulesyncCommand({
1915
1913
  baseDir: process.cwd(),
@@ -1989,8 +1987,7 @@ var KiroCommand = class _KiroCommand extends ToolCommand {
1989
1987
  }
1990
1988
  toRulesyncCommand() {
1991
1989
  const rulesyncFrontmatter = {
1992
- targets: ["*"],
1993
- description: ""
1990
+ targets: ["*"]
1994
1991
  };
1995
1992
  return new RulesyncCommand({
1996
1993
  baseDir: process.cwd(),
@@ -2064,7 +2061,7 @@ var KiroCommand = class _KiroCommand extends ToolCommand {
2064
2061
  var import_node_path17 = require("path");
2065
2062
  var import_mini10 = require("zod/mini");
2066
2063
  var OpenCodeCommandFrontmatterSchema = import_mini10.z.looseObject({
2067
- description: import_mini10.z.string(),
2064
+ description: import_mini10.z.optional(import_mini10.z.string()),
2068
2065
  agent: (0, import_mini10.optional)(import_mini10.z.string()),
2069
2066
  subtask: (0, import_mini10.optional)(import_mini10.z.boolean()),
2070
2067
  model: (0, import_mini10.optional)(import_mini10.z.string())
@@ -2204,7 +2201,7 @@ var OpenCodeCommand = class _OpenCodeCommand extends ToolCommand {
2204
2201
  var import_node_path18 = require("path");
2205
2202
  var import_mini11 = require("zod/mini");
2206
2203
  var RooCommandFrontmatterSchema = import_mini11.z.looseObject({
2207
- description: import_mini11.z.string(),
2204
+ description: import_mini11.z.optional(import_mini11.z.string()),
2208
2205
  "argument-hint": (0, import_mini11.optional)(import_mini11.z.string())
2209
2206
  });
2210
2207
  var RooCommand = class _RooCommand extends ToolCommand {
@@ -2743,7 +2740,7 @@ var HookDefinitionSchema = import_mini13.z.looseObject({
2743
2740
  type: import_mini13.z.optional(import_mini13.z.enum(["command", "prompt"])),
2744
2741
  timeout: import_mini13.z.optional(import_mini13.z.number()),
2745
2742
  matcher: import_mini13.z.optional(safeString),
2746
- prompt: import_mini13.z.optional(import_mini13.z.string()),
2743
+ prompt: import_mini13.z.optional(safeString),
2747
2744
  loop_limit: import_mini13.z.optional(import_mini13.z.nullable(import_mini13.z.number())),
2748
2745
  name: import_mini13.z.optional(safeString),
2749
2746
  description: import_mini13.z.optional(safeString)
@@ -2795,7 +2792,7 @@ var OPENCODE_HOOK_EVENTS = [
2795
2792
  var COPILOT_HOOK_EVENTS = [
2796
2793
  "sessionStart",
2797
2794
  "sessionEnd",
2798
- "afterSubmitPrompt",
2795
+ "beforeSubmitPrompt",
2799
2796
  "preToolUse",
2800
2797
  "postToolUse",
2801
2798
  "afterError"
@@ -2906,7 +2903,7 @@ var CANONICAL_TO_OPENCODE_EVENT_NAMES = {
2906
2903
  var CANONICAL_TO_COPILOT_EVENT_NAMES = {
2907
2904
  sessionStart: "sessionStart",
2908
2905
  sessionEnd: "sessionEnd",
2909
- afterSubmitPrompt: "userPromptSubmitted",
2906
+ beforeSubmitPrompt: "userPromptSubmitted",
2910
2907
  preToolUse: "preToolUse",
2911
2908
  postToolUse: "postToolUse",
2912
2909
  afterError: "errorOccurred"
@@ -2934,6 +2931,107 @@ var GEMINICLI_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(
2934
2931
  // src/features/hooks/claudecode-hooks.ts
2935
2932
  var import_node_path21 = require("path");
2936
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 commandText = def.command;
2977
+ const trimmedCommand = typeof commandText === "string" ? commandText.trimStart() : void 0;
2978
+ const shouldPrefix = typeof trimmedCommand === "string" && !trimmedCommand.startsWith("$") && (!converterConfig.prefixDotRelativeCommandsOnly || trimmedCommand.startsWith("."));
2979
+ const command = shouldPrefix && typeof trimmedCommand === "string" ? `${converterConfig.projectDirVar}/${trimmedCommand.replace(/^\.\//, "")}` : def.command;
2980
+ return {
2981
+ type: def.type ?? "command",
2982
+ ...command !== void 0 && command !== null && { command },
2983
+ ...def.timeout !== void 0 && def.timeout !== null && { timeout: def.timeout },
2984
+ ...def.prompt !== void 0 && def.prompt !== null && { prompt: def.prompt }
2985
+ };
2986
+ });
2987
+ entries.push(matcherKey ? { matcher: matcherKey, hooks } : { hooks });
2988
+ }
2989
+ result[toolEventName] = entries;
2990
+ }
2991
+ return result;
2992
+ }
2993
+ function toolHooksToCanonical({
2994
+ hooks,
2995
+ converterConfig
2996
+ }) {
2997
+ if (hooks === null || hooks === void 0 || typeof hooks !== "object") {
2998
+ return {};
2999
+ }
3000
+ const canonical = {};
3001
+ for (const [toolEventName, matcherEntries] of Object.entries(hooks)) {
3002
+ const eventName = converterConfig.toolToCanonicalEventNames[toolEventName] ?? toolEventName;
3003
+ if (!Array.isArray(matcherEntries)) continue;
3004
+ const defs = [];
3005
+ for (const rawEntry of matcherEntries) {
3006
+ if (!isToolMatcherEntry(rawEntry)) continue;
3007
+ const hookDefs = rawEntry.hooks ?? [];
3008
+ for (const h of hookDefs) {
3009
+ const cmd = typeof h.command === "string" ? h.command : void 0;
3010
+ const command = typeof cmd === "string" && cmd.includes(`${converterConfig.projectDirVar}/`) ? cmd.replace(
3011
+ new RegExp(
3012
+ `^${converterConfig.projectDirVar.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\/?`
3013
+ ),
3014
+ "./"
3015
+ ) : cmd;
3016
+ const hookType = h.type === "command" || h.type === "prompt" ? h.type : "command";
3017
+ const timeout = typeof h.timeout === "number" ? h.timeout : void 0;
3018
+ const prompt = typeof h.prompt === "string" ? h.prompt : void 0;
3019
+ defs.push({
3020
+ type: hookType,
3021
+ ...command !== void 0 && command !== null && { command },
3022
+ ...timeout !== void 0 && timeout !== null && { timeout },
3023
+ ...prompt !== void 0 && prompt !== null && { prompt },
3024
+ ...rawEntry.matcher !== void 0 && rawEntry.matcher !== null && rawEntry.matcher !== "" && { matcher: rawEntry.matcher }
3025
+ });
3026
+ }
3027
+ }
3028
+ if (defs.length > 0) {
3029
+ canonical[eventName] = defs;
3030
+ }
3031
+ }
3032
+ return canonical;
3033
+ }
3034
+
2937
3035
  // src/types/tool-file.ts
2938
3036
  var ToolFile = class extends AiFile {
2939
3037
  };
@@ -3024,91 +3122,13 @@ var ToolHooks = class extends ToolFile {
3024
3122
  };
3025
3123
 
3026
3124
  // src/features/hooks/claudecode-hooks.ts
3027
- function canonicalToClaudeHooks(config) {
3028
- const claudeSupported = new Set(CLAUDE_HOOK_EVENTS);
3029
- const sharedHooks = {};
3030
- for (const [event, defs] of Object.entries(config.hooks)) {
3031
- if (claudeSupported.has(event)) {
3032
- sharedHooks[event] = defs;
3033
- }
3034
- }
3035
- const effectiveHooks = {
3036
- ...sharedHooks,
3037
- ...config.claudecode?.hooks
3038
- };
3039
- const claude = {};
3040
- for (const [eventName, definitions] of Object.entries(effectiveHooks)) {
3041
- const claudeEventName = CANONICAL_TO_CLAUDE_EVENT_NAMES[eventName] ?? eventName;
3042
- const byMatcher = /* @__PURE__ */ new Map();
3043
- for (const def of definitions) {
3044
- const key = def.matcher ?? "";
3045
- const list = byMatcher.get(key);
3046
- if (list) list.push(def);
3047
- else byMatcher.set(key, [def]);
3048
- }
3049
- const entries = [];
3050
- for (const [matcherKey, defs] of byMatcher) {
3051
- const hooks = defs.map((def) => {
3052
- const command = def.command !== void 0 && def.command !== null && !def.command.startsWith("$") ? `$CLAUDE_PROJECT_DIR/${def.command.replace(/^\.\//, "")}` : def.command;
3053
- return {
3054
- type: def.type ?? "command",
3055
- ...command !== void 0 && command !== null && { command },
3056
- ...def.timeout !== void 0 && def.timeout !== null && { timeout: def.timeout },
3057
- ...def.prompt !== void 0 && def.prompt !== null && { prompt: def.prompt }
3058
- };
3059
- });
3060
- entries.push(matcherKey ? { matcher: matcherKey, hooks } : { hooks });
3061
- }
3062
- claude[claudeEventName] = entries;
3063
- }
3064
- return claude;
3065
- }
3066
- function isClaudeMatcherEntry(x) {
3067
- if (x === null || typeof x !== "object") {
3068
- return false;
3069
- }
3070
- if ("matcher" in x && typeof x.matcher !== "string") {
3071
- return false;
3072
- }
3073
- if ("hooks" in x && !Array.isArray(x.hooks)) {
3074
- return false;
3075
- }
3076
- return true;
3077
- }
3078
- function claudeHooksToCanonical(claudeHooks) {
3079
- if (claudeHooks === null || claudeHooks === void 0 || typeof claudeHooks !== "object") {
3080
- return {};
3081
- }
3082
- const canonical = {};
3083
- for (const [claudeEventName, matcherEntries] of Object.entries(claudeHooks)) {
3084
- const eventName = CLAUDE_TO_CANONICAL_EVENT_NAMES[claudeEventName] ?? claudeEventName;
3085
- if (!Array.isArray(matcherEntries)) continue;
3086
- const defs = [];
3087
- for (const rawEntry of matcherEntries) {
3088
- if (!isClaudeMatcherEntry(rawEntry)) continue;
3089
- const entry = rawEntry;
3090
- const hooks = entry.hooks ?? [];
3091
- for (const h of hooks) {
3092
- const cmd = typeof h.command === "string" ? h.command : void 0;
3093
- const command = typeof cmd === "string" && cmd.includes("$CLAUDE_PROJECT_DIR/") ? cmd.replace(/^\$CLAUDE_PROJECT_DIR\/?/, "./") : cmd;
3094
- const hookType = h.type === "command" || h.type === "prompt" ? h.type : "command";
3095
- const timeout = typeof h.timeout === "number" ? h.timeout : void 0;
3096
- const prompt = typeof h.prompt === "string" ? h.prompt : void 0;
3097
- defs.push({
3098
- type: hookType,
3099
- ...command !== void 0 && command !== null && { command },
3100
- ...timeout !== void 0 && timeout !== null && { timeout },
3101
- ...prompt !== void 0 && prompt !== null && { prompt },
3102
- ...entry.matcher !== void 0 && entry.matcher !== null && entry.matcher !== "" && { matcher: entry.matcher }
3103
- });
3104
- }
3105
- }
3106
- if (defs.length > 0) {
3107
- canonical[eventName] = defs;
3108
- }
3109
- }
3110
- return canonical;
3111
- }
3125
+ var CLAUDE_CONVERTER_CONFIG = {
3126
+ supportedEvents: CLAUDE_HOOK_EVENTS,
3127
+ canonicalToToolEventNames: CANONICAL_TO_CLAUDE_EVENT_NAMES,
3128
+ toolToCanonicalEventNames: CLAUDE_TO_CANONICAL_EVENT_NAMES,
3129
+ projectDirVar: "$CLAUDE_PROJECT_DIR",
3130
+ prefixDotRelativeCommandsOnly: true
3131
+ };
3112
3132
  var ClaudecodeHooks = class _ClaudecodeHooks extends ToolHooks {
3113
3133
  constructor(params) {
3114
3134
  super({
@@ -3160,7 +3180,11 @@ var ClaudecodeHooks = class _ClaudecodeHooks extends ToolHooks {
3160
3180
  );
3161
3181
  }
3162
3182
  const config = rulesyncHooks.getJson();
3163
- const claudeHooks = canonicalToClaudeHooks(config);
3183
+ const claudeHooks = canonicalToToolHooks({
3184
+ config,
3185
+ toolOverrideHooks: config.claudecode?.hooks,
3186
+ converterConfig: CLAUDE_CONVERTER_CONFIG
3187
+ });
3164
3188
  const merged = { ...settings, hooks: claudeHooks };
3165
3189
  const fileContent = JSON.stringify(merged, null, 2);
3166
3190
  return new _ClaudecodeHooks({
@@ -3183,7 +3207,10 @@ var ClaudecodeHooks = class _ClaudecodeHooks extends ToolHooks {
3183
3207
  }
3184
3208
  );
3185
3209
  }
3186
- const hooks = claudeHooksToCanonical(settings.hooks);
3210
+ const hooks = toolHooksToCanonical({
3211
+ hooks: settings.hooks,
3212
+ converterConfig: CLAUDE_CONVERTER_CONFIG
3213
+ });
3187
3214
  return this.toRulesyncHooksDefault({
3188
3215
  fileContent: JSON.stringify({ version: 1, hooks }, null, 2)
3189
3216
  });
@@ -3492,91 +3519,12 @@ var CursorHooks = class _CursorHooks extends ToolHooks {
3492
3519
 
3493
3520
  // src/features/hooks/factorydroid-hooks.ts
3494
3521
  var import_node_path24 = require("path");
3495
- function canonicalToFactorydroidHooks(config) {
3496
- const supported = new Set(FACTORYDROID_HOOK_EVENTS);
3497
- const sharedHooks = {};
3498
- for (const [event, defs] of Object.entries(config.hooks)) {
3499
- if (supported.has(event)) {
3500
- sharedHooks[event] = defs;
3501
- }
3502
- }
3503
- const effectiveHooks = {
3504
- ...sharedHooks,
3505
- ...config.factorydroid?.hooks
3506
- };
3507
- const result = {};
3508
- for (const [eventName, definitions] of Object.entries(effectiveHooks)) {
3509
- const pascalEventName = CANONICAL_TO_FACTORYDROID_EVENT_NAMES[eventName] ?? eventName;
3510
- const byMatcher = /* @__PURE__ */ new Map();
3511
- for (const def of definitions) {
3512
- const key = def.matcher ?? "";
3513
- const list = byMatcher.get(key);
3514
- if (list) list.push(def);
3515
- else byMatcher.set(key, [def]);
3516
- }
3517
- const entries = [];
3518
- for (const [matcherKey, defs] of byMatcher) {
3519
- const hooks = defs.map((def) => {
3520
- const command = def.command !== void 0 && def.command !== null && !def.command.startsWith("$") ? `$FACTORY_PROJECT_DIR/${def.command.replace(/^\.\//, "")}` : def.command;
3521
- return {
3522
- type: def.type ?? "command",
3523
- ...command !== void 0 && command !== null && { command },
3524
- ...def.timeout !== void 0 && def.timeout !== null && { timeout: def.timeout },
3525
- ...def.prompt !== void 0 && def.prompt !== null && { prompt: def.prompt }
3526
- };
3527
- });
3528
- entries.push(matcherKey ? { matcher: matcherKey, hooks } : { hooks });
3529
- }
3530
- result[pascalEventName] = entries;
3531
- }
3532
- return result;
3533
- }
3534
- function isFactorydroidMatcherEntry(x) {
3535
- if (x === null || typeof x !== "object") {
3536
- return false;
3537
- }
3538
- if ("matcher" in x && typeof x.matcher !== "string") {
3539
- return false;
3540
- }
3541
- if ("hooks" in x && !Array.isArray(x.hooks)) {
3542
- return false;
3543
- }
3544
- return true;
3545
- }
3546
- function factorydroidHooksToCanonical(hooks) {
3547
- if (hooks === null || hooks === void 0 || typeof hooks !== "object") {
3548
- return {};
3549
- }
3550
- const canonical = {};
3551
- for (const [pascalEventName, matcherEntries] of Object.entries(hooks)) {
3552
- const eventName = FACTORYDROID_TO_CANONICAL_EVENT_NAMES[pascalEventName] ?? pascalEventName;
3553
- if (!Array.isArray(matcherEntries)) continue;
3554
- const defs = [];
3555
- for (const rawEntry of matcherEntries) {
3556
- if (!isFactorydroidMatcherEntry(rawEntry)) continue;
3557
- const entry = rawEntry;
3558
- const hookDefs = entry.hooks ?? [];
3559
- for (const h of hookDefs) {
3560
- const cmd = typeof h.command === "string" ? h.command : void 0;
3561
- const command = typeof cmd === "string" && cmd.includes("$FACTORY_PROJECT_DIR/") ? cmd.replace(/^\$FACTORY_PROJECT_DIR\/?/, "./") : cmd;
3562
- const hookType = h.type === "command" || h.type === "prompt" ? h.type : "command";
3563
- const timeout = typeof h.timeout === "number" ? h.timeout : void 0;
3564
- const prompt = typeof h.prompt === "string" ? h.prompt : void 0;
3565
- defs.push({
3566
- type: hookType,
3567
- ...command !== void 0 && command !== null && { command },
3568
- ...timeout !== void 0 && timeout !== null && { timeout },
3569
- ...prompt !== void 0 && prompt !== null && { prompt },
3570
- ...entry.matcher !== void 0 && entry.matcher !== null && entry.matcher !== "" && { matcher: entry.matcher }
3571
- });
3572
- }
3573
- }
3574
- if (defs.length > 0) {
3575
- canonical[eventName] = defs;
3576
- }
3577
- }
3578
- return canonical;
3579
- }
3522
+ var FACTORYDROID_CONVERTER_CONFIG = {
3523
+ supportedEvents: FACTORYDROID_HOOK_EVENTS,
3524
+ canonicalToToolEventNames: CANONICAL_TO_FACTORYDROID_EVENT_NAMES,
3525
+ toolToCanonicalEventNames: FACTORYDROID_TO_CANONICAL_EVENT_NAMES,
3526
+ projectDirVar: "$FACTORY_PROJECT_DIR"
3527
+ };
3580
3528
  var FactorydroidHooks = class _FactorydroidHooks extends ToolHooks {
3581
3529
  constructor(params) {
3582
3530
  super({
@@ -3628,7 +3576,11 @@ var FactorydroidHooks = class _FactorydroidHooks extends ToolHooks {
3628
3576
  );
3629
3577
  }
3630
3578
  const config = rulesyncHooks.getJson();
3631
- const factorydroidHooks = canonicalToFactorydroidHooks(config);
3579
+ const factorydroidHooks = canonicalToToolHooks({
3580
+ config,
3581
+ toolOverrideHooks: config.factorydroid?.hooks,
3582
+ converterConfig: FACTORYDROID_CONVERTER_CONFIG
3583
+ });
3632
3584
  const merged = { ...settings, hooks: factorydroidHooks };
3633
3585
  const fileContent = JSON.stringify(merged, null, 2);
3634
3586
  return new _FactorydroidHooks({
@@ -3651,7 +3603,10 @@ var FactorydroidHooks = class _FactorydroidHooks extends ToolHooks {
3651
3603
  }
3652
3604
  );
3653
3605
  }
3654
- const hooks = factorydroidHooksToCanonical(settings.hooks);
3606
+ const hooks = toolHooksToCanonical({
3607
+ hooks: settings.hooks,
3608
+ converterConfig: FACTORYDROID_CONVERTER_CONFIG
3609
+ });
3655
3610
  return this.toRulesyncHooksDefault({
3656
3611
  fileContent: JSON.stringify({ version: 1, hooks }, null, 2)
3657
3612
  });
@@ -3702,7 +3657,10 @@ function canonicalToGeminicliHooks(config) {
3702
3657
  const entries = [];
3703
3658
  for (const [matcherKey, defs] of byMatcher) {
3704
3659
  const hooks = defs.map((def) => {
3705
- const command = def.command !== void 0 && def.command !== null && !def.command.startsWith("$") ? `$GEMINI_PROJECT_DIR/${def.command.replace(/^\.\//, "")}` : def.command;
3660
+ const commandText = def.command;
3661
+ const trimmedCommand = typeof commandText === "string" ? commandText.trimStart() : void 0;
3662
+ const shouldPrefix = typeof trimmedCommand === "string" && !trimmedCommand.startsWith("$") && trimmedCommand.startsWith(".");
3663
+ const command = shouldPrefix && typeof trimmedCommand === "string" ? `$GEMINI_PROJECT_DIR/${trimmedCommand.replace(/^\.\//, "")}` : def.command;
3706
3664
  return {
3707
3665
  type: def.type ?? "command",
3708
3666
  ...command !== void 0 && command !== null && { command },
@@ -10448,7 +10406,7 @@ var ToolSubagent = class extends ToolFile {
10448
10406
  // src/features/subagents/simulated-subagent.ts
10449
10407
  var SimulatedSubagentFrontmatterSchema = import_mini38.z.object({
10450
10408
  name: import_mini38.z.string(),
10451
- description: import_mini38.z.string()
10409
+ description: import_mini38.z.optional(import_mini38.z.string())
10452
10410
  });
10453
10411
  var SimulatedSubagent = class extends ToolSubagent {
10454
10412
  frontmatter;
@@ -10672,7 +10630,7 @@ var import_mini39 = require("zod/mini");
10672
10630
  var RulesyncSubagentFrontmatterSchema = import_mini39.z.looseObject({
10673
10631
  targets: import_mini39.z._default(RulesyncTargetsSchema, ["*"]),
10674
10632
  name: import_mini39.z.string(),
10675
- description: import_mini39.z.string()
10633
+ description: import_mini39.z.optional(import_mini39.z.string())
10676
10634
  });
10677
10635
  var RulesyncSubagent = class _RulesyncSubagent extends RulesyncFile {
10678
10636
  frontmatter;
@@ -10743,7 +10701,7 @@ var RulesyncSubagent = class _RulesyncSubagent extends RulesyncFile {
10743
10701
  // src/features/subagents/claudecode-subagent.ts
10744
10702
  var ClaudecodeSubagentFrontmatterSchema = import_mini40.z.looseObject({
10745
10703
  name: import_mini40.z.string(),
10746
- description: import_mini40.z.string(),
10704
+ description: import_mini40.z.optional(import_mini40.z.string()),
10747
10705
  model: import_mini40.z.optional(import_mini40.z.string()),
10748
10706
  tools: import_mini40.z.optional(import_mini40.z.union([import_mini40.z.string(), import_mini40.z.array(import_mini40.z.string())])),
10749
10707
  permissionMode: import_mini40.z.optional(import_mini40.z.string()),
@@ -10953,7 +10911,7 @@ var CodexCliSubagent = class _CodexCliSubagent extends ToolSubagent {
10953
10911
  const rulesyncFrontmatter = {
10954
10912
  targets: ["codexcli"],
10955
10913
  name,
10956
- description: description ?? "",
10914
+ description,
10957
10915
  // Only include codexcli section if there are fields
10958
10916
  ...Object.keys(codexcliSection).length > 0 && { codexcli: codexcliSection }
10959
10917
  };
@@ -11066,7 +11024,7 @@ var import_mini42 = require("zod/mini");
11066
11024
  var REQUIRED_TOOL = "agent/runSubagent";
11067
11025
  var CopilotSubagentFrontmatterSchema = import_mini42.z.looseObject({
11068
11026
  name: import_mini42.z.string(),
11069
- description: import_mini42.z.string(),
11027
+ description: import_mini42.z.optional(import_mini42.z.string()),
11070
11028
  tools: import_mini42.z.optional(import_mini42.z.union([import_mini42.z.string(), import_mini42.z.array(import_mini42.z.string())]))
11071
11029
  });
11072
11030
  var normalizeTools = (tools) => {
@@ -11231,7 +11189,7 @@ var import_node_path85 = require("path");
11231
11189
  var import_mini43 = require("zod/mini");
11232
11190
  var CursorSubagentFrontmatterSchema = import_mini43.z.looseObject({
11233
11191
  name: import_mini43.z.string(),
11234
- description: import_mini43.z.string()
11192
+ description: import_mini43.z.optional(import_mini43.z.string())
11235
11193
  });
11236
11194
  var CursorSubagent = class _CursorSubagent extends ToolSubagent {
11237
11195
  frontmatter;
@@ -11436,7 +11394,7 @@ var KiroSubagent = class _KiroSubagent extends ToolSubagent {
11436
11394
  const rulesyncFrontmatter = {
11437
11395
  targets: ["kiro"],
11438
11396
  name,
11439
- description: description ?? "",
11397
+ description: description ?? void 0,
11440
11398
  // Only include kiro section if there are fields
11441
11399
  ...Object.keys(kiroSection).length > 0 && { kiro: kiroSection }
11442
11400
  };
@@ -11547,7 +11505,7 @@ var KiroSubagent = class _KiroSubagent extends ToolSubagent {
11547
11505
  var import_node_path87 = require("path");
11548
11506
  var import_mini45 = require("zod/mini");
11549
11507
  var OpenCodeSubagentFrontmatterSchema = import_mini45.z.looseObject({
11550
- description: import_mini45.z.string(),
11508
+ description: import_mini45.z.optional(import_mini45.z.string()),
11551
11509
  mode: import_mini45.z._default(import_mini45.z.string(), "subagent"),
11552
11510
  name: import_mini45.z.optional(import_mini45.z.string())
11553
11511
  });
@@ -12377,7 +12335,7 @@ var globStrategy = {
12377
12335
  },
12378
12336
  exportRulesyncData: ({ description, ...frontmatter }) => ({
12379
12337
  globs: parseGlobsString(frontmatter.globs),
12380
- description: description || "",
12338
+ description,
12381
12339
  antigravity: frontmatter
12382
12340
  })
12383
12341
  };
@@ -12389,7 +12347,7 @@ var manualStrategy = {
12389
12347
  }),
12390
12348
  exportRulesyncData: ({ description, ...frontmatter }) => ({
12391
12349
  globs: [],
12392
- description: description || "",
12350
+ description,
12393
12351
  antigravity: frontmatter
12394
12352
  })
12395
12353
  };
@@ -12401,7 +12359,7 @@ var alwaysOnStrategy = {
12401
12359
  }),
12402
12360
  exportRulesyncData: ({ description, ...frontmatter }) => ({
12403
12361
  globs: ["**/*"],
12404
- description: description || "",
12362
+ description,
12405
12363
  antigravity: frontmatter
12406
12364
  })
12407
12365
  };
@@ -12414,7 +12372,7 @@ var modelDecisionStrategy = {
12414
12372
  }),
12415
12373
  exportRulesyncData: ({ description, ...frontmatter }) => ({
12416
12374
  globs: [],
12417
- description: description || "",
12375
+ description,
12418
12376
  antigravity: frontmatter
12419
12377
  })
12420
12378
  };
@@ -12429,7 +12387,7 @@ var unknownStrategy = {
12429
12387
  },
12430
12388
  exportRulesyncData: ({ description, ...frontmatter }) => ({
12431
12389
  globs: frontmatter.globs ? parseGlobsString(frontmatter.globs) : ["**/*"],
12432
- description: description || "",
12390
+ description,
12433
12391
  antigravity: frontmatter
12434
12392
  })
12435
12393
  };
@@ -12451,7 +12409,7 @@ var inferenceStrategy = {
12451
12409
  },
12452
12410
  exportRulesyncData: ({ description, ...frontmatter }) => ({
12453
12411
  globs: frontmatter.globs ? parseGlobsString(frontmatter.globs) : ["**/*"],
12454
- description: description || "",
12412
+ description,
12455
12413
  antigravity: frontmatter
12456
12414
  })
12457
12415
  };
@@ -12581,7 +12539,6 @@ var AntigravityRule = class _AntigravityRule extends ToolRule {
12581
12539
  const strategy = STRATEGIES.find((s) => s.canHandle(this.frontmatter.trigger));
12582
12540
  let rulesyncData = {
12583
12541
  globs: [],
12584
- description: "",
12585
12542
  antigravity: this.frontmatter
12586
12543
  };
12587
12544
  if (strategy) {
@@ -12651,7 +12608,6 @@ var AugmentcodeLegacyRule = class _AugmentcodeLegacyRule extends ToolRule {
12651
12608
  const rulesyncFrontmatter = {
12652
12609
  root: this.isRoot(),
12653
12610
  targets: ["*"],
12654
- description: "",
12655
12611
  globs: this.isRoot() ? ["**/*"] : []
12656
12612
  };
12657
12613
  return new RulesyncRule({
@@ -12826,6 +12782,12 @@ var ClaudecodeLegacyRule = class _ClaudecodeLegacyRule extends ToolRule {
12826
12782
  relativeDirPath: ".",
12827
12783
  relativeFilePath: "CLAUDE.md"
12828
12784
  },
12785
+ alternativeRoots: [
12786
+ {
12787
+ relativeDirPath: ".claude",
12788
+ relativeFilePath: "CLAUDE.md"
12789
+ }
12790
+ ],
12829
12791
  nonRoot: {
12830
12792
  relativeDirPath: buildToolPath(".claude", "memories", excludeToolDir)
12831
12793
  }
@@ -12835,18 +12797,19 @@ var ClaudecodeLegacyRule = class _ClaudecodeLegacyRule extends ToolRule {
12835
12797
  baseDir = process.cwd(),
12836
12798
  relativeFilePath,
12837
12799
  validate = true,
12838
- global = false
12800
+ global = false,
12801
+ relativeDirPath: overrideDirPath
12839
12802
  }) {
12840
12803
  const paths = this.getSettablePaths({ global });
12841
12804
  const isRoot = relativeFilePath === paths.root.relativeFilePath;
12842
12805
  if (isRoot) {
12843
- const relativePath2 = paths.root.relativeFilePath;
12806
+ const rootDirPath = overrideDirPath ?? paths.root.relativeDirPath;
12844
12807
  const fileContent2 = await readFileContent(
12845
- (0, import_node_path95.join)(baseDir, paths.root.relativeDirPath, relativePath2)
12808
+ (0, import_node_path95.join)(baseDir, rootDirPath, paths.root.relativeFilePath)
12846
12809
  );
12847
12810
  return new _ClaudecodeLegacyRule({
12848
12811
  baseDir,
12849
- relativeDirPath: paths.root.relativeDirPath,
12812
+ relativeDirPath: rootDirPath,
12850
12813
  relativeFilePath: paths.root.relativeFilePath,
12851
12814
  fileContent: fileContent2,
12852
12815
  validate,
@@ -12941,6 +12904,12 @@ var ClaudecodeRule = class _ClaudecodeRule extends ToolRule {
12941
12904
  relativeDirPath: ".",
12942
12905
  relativeFilePath: "CLAUDE.md"
12943
12906
  },
12907
+ alternativeRoots: [
12908
+ {
12909
+ relativeDirPath: ".claude",
12910
+ relativeFilePath: "CLAUDE.md"
12911
+ }
12912
+ ],
12944
12913
  nonRoot: {
12945
12914
  relativeDirPath: buildToolPath(".claude", "rules", excludeToolDir)
12946
12915
  }
@@ -12973,17 +12942,19 @@ var ClaudecodeRule = class _ClaudecodeRule extends ToolRule {
12973
12942
  baseDir = process.cwd(),
12974
12943
  relativeFilePath,
12975
12944
  validate = true,
12976
- global = false
12945
+ global = false,
12946
+ relativeDirPath: overrideDirPath
12977
12947
  }) {
12978
12948
  const paths = this.getSettablePaths({ global });
12979
12949
  const isRoot = relativeFilePath === paths.root.relativeFilePath;
12980
12950
  if (isRoot) {
12951
+ const rootDirPath = overrideDirPath ?? paths.root.relativeDirPath;
12981
12952
  const fileContent2 = await readFileContent(
12982
- (0, import_node_path96.join)(baseDir, paths.root.relativeDirPath, paths.root.relativeFilePath)
12953
+ (0, import_node_path96.join)(baseDir, rootDirPath, paths.root.relativeFilePath)
12983
12954
  );
12984
12955
  return new _ClaudecodeRule({
12985
12956
  baseDir,
12986
- relativeDirPath: paths.root.relativeDirPath,
12957
+ relativeDirPath: rootDirPath,
12987
12958
  relativeFilePath: paths.root.relativeFilePath,
12988
12959
  frontmatter: {},
12989
12960
  body: fileContent2.trim(),
@@ -15340,35 +15311,62 @@ var RulesProcessor = class extends FeatureProcessor {
15340
15311
  const settablePaths = factory.class.getSettablePaths({
15341
15312
  global: this.global
15342
15313
  });
15314
+ const resolveRelativeDirPath = (filePath) => {
15315
+ const dirName = (0, import_node_path113.dirname)((0, import_node_path113.relative)(this.baseDir, filePath));
15316
+ return dirName === "" ? "." : dirName;
15317
+ };
15318
+ const findFilesWithFallback = async (primaryGlob, alternativeRoots, buildAltGlob) => {
15319
+ const primaryFilePaths = await findFilesByGlobs(primaryGlob);
15320
+ if (primaryFilePaths.length > 0) {
15321
+ return primaryFilePaths;
15322
+ }
15323
+ if (alternativeRoots) {
15324
+ return findFilesByGlobs(alternativeRoots.map(buildAltGlob));
15325
+ }
15326
+ return [];
15327
+ };
15343
15328
  const rootToolRules = await (async () => {
15344
15329
  if (!settablePaths.root) {
15345
15330
  return [];
15346
15331
  }
15347
- const rootFilePaths = await findFilesByGlobs(
15332
+ const uniqueRootFilePaths = await findFilesWithFallback(
15348
15333
  (0, import_node_path113.join)(
15349
15334
  this.baseDir,
15350
15335
  settablePaths.root.relativeDirPath ?? ".",
15351
15336
  settablePaths.root.relativeFilePath
15352
- )
15337
+ ),
15338
+ settablePaths.alternativeRoots,
15339
+ (alt) => (0, import_node_path113.join)(this.baseDir, alt.relativeDirPath, alt.relativeFilePath)
15353
15340
  );
15354
15341
  if (forDeletion) {
15355
- return rootFilePaths.map(
15356
- (filePath) => factory.class.forDeletion({
15342
+ return uniqueRootFilePaths.map((filePath) => {
15343
+ const relativeDirPath = resolveRelativeDirPath(filePath);
15344
+ checkPathTraversal({
15345
+ relativePath: relativeDirPath,
15346
+ intendedRootDir: this.baseDir
15347
+ });
15348
+ return factory.class.forDeletion({
15357
15349
  baseDir: this.baseDir,
15358
- relativeDirPath: settablePaths.root?.relativeDirPath ?? ".",
15350
+ relativeDirPath,
15359
15351
  relativeFilePath: (0, import_node_path113.basename)(filePath),
15360
15352
  global: this.global
15361
- })
15362
- ).filter((rule) => rule.isDeletable());
15353
+ });
15354
+ }).filter((rule) => rule.isDeletable());
15363
15355
  }
15364
15356
  return await Promise.all(
15365
- rootFilePaths.map(
15366
- (filePath) => factory.class.fromFile({
15357
+ uniqueRootFilePaths.map((filePath) => {
15358
+ const relativeDirPath = resolveRelativeDirPath(filePath);
15359
+ checkPathTraversal({
15360
+ relativePath: relativeDirPath,
15361
+ intendedRootDir: this.baseDir
15362
+ });
15363
+ return factory.class.fromFile({
15367
15364
  baseDir: this.baseDir,
15368
15365
  relativeFilePath: (0, import_node_path113.basename)(filePath),
15366
+ relativeDirPath,
15369
15367
  global: this.global
15370
- })
15371
- )
15368
+ });
15369
+ })
15372
15370
  );
15373
15371
  })();
15374
15372
  logger.debug(`Found ${rootToolRules.length} root tool rule files`);
@@ -15382,17 +15380,24 @@ var RulesProcessor = class extends FeatureProcessor {
15382
15380
  if (!settablePaths.root) {
15383
15381
  return [];
15384
15382
  }
15385
- const localRootFilePaths = await findFilesByGlobs(
15386
- (0, import_node_path113.join)(this.baseDir, settablePaths.root.relativeDirPath ?? ".", "CLAUDE.local.md")
15383
+ const uniqueLocalRootFilePaths = await findFilesWithFallback(
15384
+ (0, import_node_path113.join)(this.baseDir, settablePaths.root.relativeDirPath ?? ".", "CLAUDE.local.md"),
15385
+ settablePaths.alternativeRoots,
15386
+ (alt) => (0, import_node_path113.join)(this.baseDir, alt.relativeDirPath, "CLAUDE.local.md")
15387
15387
  );
15388
- return localRootFilePaths.map(
15389
- (filePath) => factory.class.forDeletion({
15388
+ return uniqueLocalRootFilePaths.map((filePath) => {
15389
+ const relativeDirPath = resolveRelativeDirPath(filePath);
15390
+ checkPathTraversal({
15391
+ relativePath: relativeDirPath,
15392
+ intendedRootDir: this.baseDir
15393
+ });
15394
+ return factory.class.forDeletion({
15390
15395
  baseDir: this.baseDir,
15391
- relativeDirPath: settablePaths.root?.relativeDirPath ?? ".",
15396
+ relativeDirPath,
15392
15397
  relativeFilePath: (0, import_node_path113.basename)(filePath),
15393
15398
  global: this.global
15394
- })
15395
- ).filter((rule) => rule.isDeletable());
15399
+ });
15400
+ }).filter((rule) => rule.isDeletable());
15396
15401
  })();
15397
15402
  logger.debug(`Found ${localRootToolRules.length} local root tool rule files for deletion`);
15398
15403
  const nonRootToolRules = await (async () => {
@@ -20194,7 +20199,7 @@ async function updateCommand(currentVersion, options) {
20194
20199
  }
20195
20200
 
20196
20201
  // src/cli/index.ts
20197
- var getVersion = () => "7.12.0";
20202
+ var getVersion = () => "7.12.2";
20198
20203
  var main = async () => {
20199
20204
  const program = new import_commander.Command();
20200
20205
  const version = getVersion();