rulesync 7.12.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.
@@ -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,104 @@ 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 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
+
2937
3032
  // src/types/tool-file.ts
2938
3033
  var ToolFile = class extends AiFile {
2939
3034
  };
@@ -3024,91 +3119,12 @@ var ToolHooks = class extends ToolFile {
3024
3119
  };
3025
3120
 
3026
3121
  // 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
- }
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
+ };
3112
3128
  var ClaudecodeHooks = class _ClaudecodeHooks extends ToolHooks {
3113
3129
  constructor(params) {
3114
3130
  super({
@@ -3160,7 +3176,11 @@ var ClaudecodeHooks = class _ClaudecodeHooks extends ToolHooks {
3160
3176
  );
3161
3177
  }
3162
3178
  const config = rulesyncHooks.getJson();
3163
- const claudeHooks = canonicalToClaudeHooks(config);
3179
+ const claudeHooks = canonicalToToolHooks({
3180
+ config,
3181
+ toolOverrideHooks: config.claudecode?.hooks,
3182
+ converterConfig: CLAUDE_CONVERTER_CONFIG
3183
+ });
3164
3184
  const merged = { ...settings, hooks: claudeHooks };
3165
3185
  const fileContent = JSON.stringify(merged, null, 2);
3166
3186
  return new _ClaudecodeHooks({
@@ -3183,7 +3203,10 @@ var ClaudecodeHooks = class _ClaudecodeHooks extends ToolHooks {
3183
3203
  }
3184
3204
  );
3185
3205
  }
3186
- const hooks = claudeHooksToCanonical(settings.hooks);
3206
+ const hooks = toolHooksToCanonical({
3207
+ hooks: settings.hooks,
3208
+ converterConfig: CLAUDE_CONVERTER_CONFIG
3209
+ });
3187
3210
  return this.toRulesyncHooksDefault({
3188
3211
  fileContent: JSON.stringify({ version: 1, hooks }, null, 2)
3189
3212
  });
@@ -3492,91 +3515,12 @@ var CursorHooks = class _CursorHooks extends ToolHooks {
3492
3515
 
3493
3516
  // src/features/hooks/factorydroid-hooks.ts
3494
3517
  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
- }
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
+ };
3580
3524
  var FactorydroidHooks = class _FactorydroidHooks extends ToolHooks {
3581
3525
  constructor(params) {
3582
3526
  super({
@@ -3628,7 +3572,11 @@ var FactorydroidHooks = class _FactorydroidHooks extends ToolHooks {
3628
3572
  );
3629
3573
  }
3630
3574
  const config = rulesyncHooks.getJson();
3631
- const factorydroidHooks = canonicalToFactorydroidHooks(config);
3575
+ const factorydroidHooks = canonicalToToolHooks({
3576
+ config,
3577
+ toolOverrideHooks: config.factorydroid?.hooks,
3578
+ converterConfig: FACTORYDROID_CONVERTER_CONFIG
3579
+ });
3632
3580
  const merged = { ...settings, hooks: factorydroidHooks };
3633
3581
  const fileContent = JSON.stringify(merged, null, 2);
3634
3582
  return new _FactorydroidHooks({
@@ -3651,7 +3599,10 @@ var FactorydroidHooks = class _FactorydroidHooks extends ToolHooks {
3651
3599
  }
3652
3600
  );
3653
3601
  }
3654
- const hooks = factorydroidHooksToCanonical(settings.hooks);
3602
+ const hooks = toolHooksToCanonical({
3603
+ hooks: settings.hooks,
3604
+ converterConfig: FACTORYDROID_CONVERTER_CONFIG
3605
+ });
3655
3606
  return this.toRulesyncHooksDefault({
3656
3607
  fileContent: JSON.stringify({ version: 1, hooks }, null, 2)
3657
3608
  });
@@ -10448,7 +10399,7 @@ var ToolSubagent = class extends ToolFile {
10448
10399
  // src/features/subagents/simulated-subagent.ts
10449
10400
  var SimulatedSubagentFrontmatterSchema = import_mini38.z.object({
10450
10401
  name: import_mini38.z.string(),
10451
- description: import_mini38.z.string()
10402
+ description: import_mini38.z.optional(import_mini38.z.string())
10452
10403
  });
10453
10404
  var SimulatedSubagent = class extends ToolSubagent {
10454
10405
  frontmatter;
@@ -10672,7 +10623,7 @@ var import_mini39 = require("zod/mini");
10672
10623
  var RulesyncSubagentFrontmatterSchema = import_mini39.z.looseObject({
10673
10624
  targets: import_mini39.z._default(RulesyncTargetsSchema, ["*"]),
10674
10625
  name: import_mini39.z.string(),
10675
- description: import_mini39.z.string()
10626
+ description: import_mini39.z.optional(import_mini39.z.string())
10676
10627
  });
10677
10628
  var RulesyncSubagent = class _RulesyncSubagent extends RulesyncFile {
10678
10629
  frontmatter;
@@ -10743,7 +10694,7 @@ var RulesyncSubagent = class _RulesyncSubagent extends RulesyncFile {
10743
10694
  // src/features/subagents/claudecode-subagent.ts
10744
10695
  var ClaudecodeSubagentFrontmatterSchema = import_mini40.z.looseObject({
10745
10696
  name: import_mini40.z.string(),
10746
- description: import_mini40.z.string(),
10697
+ description: import_mini40.z.optional(import_mini40.z.string()),
10747
10698
  model: import_mini40.z.optional(import_mini40.z.string()),
10748
10699
  tools: import_mini40.z.optional(import_mini40.z.union([import_mini40.z.string(), import_mini40.z.array(import_mini40.z.string())])),
10749
10700
  permissionMode: import_mini40.z.optional(import_mini40.z.string()),
@@ -10953,7 +10904,7 @@ var CodexCliSubagent = class _CodexCliSubagent extends ToolSubagent {
10953
10904
  const rulesyncFrontmatter = {
10954
10905
  targets: ["codexcli"],
10955
10906
  name,
10956
- description: description ?? "",
10907
+ description,
10957
10908
  // Only include codexcli section if there are fields
10958
10909
  ...Object.keys(codexcliSection).length > 0 && { codexcli: codexcliSection }
10959
10910
  };
@@ -11066,7 +11017,7 @@ var import_mini42 = require("zod/mini");
11066
11017
  var REQUIRED_TOOL = "agent/runSubagent";
11067
11018
  var CopilotSubagentFrontmatterSchema = import_mini42.z.looseObject({
11068
11019
  name: import_mini42.z.string(),
11069
- description: import_mini42.z.string(),
11020
+ description: import_mini42.z.optional(import_mini42.z.string()),
11070
11021
  tools: import_mini42.z.optional(import_mini42.z.union([import_mini42.z.string(), import_mini42.z.array(import_mini42.z.string())]))
11071
11022
  });
11072
11023
  var normalizeTools = (tools) => {
@@ -11231,7 +11182,7 @@ var import_node_path85 = require("path");
11231
11182
  var import_mini43 = require("zod/mini");
11232
11183
  var CursorSubagentFrontmatterSchema = import_mini43.z.looseObject({
11233
11184
  name: import_mini43.z.string(),
11234
- description: import_mini43.z.string()
11185
+ description: import_mini43.z.optional(import_mini43.z.string())
11235
11186
  });
11236
11187
  var CursorSubagent = class _CursorSubagent extends ToolSubagent {
11237
11188
  frontmatter;
@@ -11436,7 +11387,7 @@ var KiroSubagent = class _KiroSubagent extends ToolSubagent {
11436
11387
  const rulesyncFrontmatter = {
11437
11388
  targets: ["kiro"],
11438
11389
  name,
11439
- description: description ?? "",
11390
+ description: description ?? void 0,
11440
11391
  // Only include kiro section if there are fields
11441
11392
  ...Object.keys(kiroSection).length > 0 && { kiro: kiroSection }
11442
11393
  };
@@ -11547,7 +11498,7 @@ var KiroSubagent = class _KiroSubagent extends ToolSubagent {
11547
11498
  var import_node_path87 = require("path");
11548
11499
  var import_mini45 = require("zod/mini");
11549
11500
  var OpenCodeSubagentFrontmatterSchema = import_mini45.z.looseObject({
11550
- description: import_mini45.z.string(),
11501
+ description: import_mini45.z.optional(import_mini45.z.string()),
11551
11502
  mode: import_mini45.z._default(import_mini45.z.string(), "subagent"),
11552
11503
  name: import_mini45.z.optional(import_mini45.z.string())
11553
11504
  });
@@ -12377,7 +12328,7 @@ var globStrategy = {
12377
12328
  },
12378
12329
  exportRulesyncData: ({ description, ...frontmatter }) => ({
12379
12330
  globs: parseGlobsString(frontmatter.globs),
12380
- description: description || "",
12331
+ description,
12381
12332
  antigravity: frontmatter
12382
12333
  })
12383
12334
  };
@@ -12389,7 +12340,7 @@ var manualStrategy = {
12389
12340
  }),
12390
12341
  exportRulesyncData: ({ description, ...frontmatter }) => ({
12391
12342
  globs: [],
12392
- description: description || "",
12343
+ description,
12393
12344
  antigravity: frontmatter
12394
12345
  })
12395
12346
  };
@@ -12401,7 +12352,7 @@ var alwaysOnStrategy = {
12401
12352
  }),
12402
12353
  exportRulesyncData: ({ description, ...frontmatter }) => ({
12403
12354
  globs: ["**/*"],
12404
- description: description || "",
12355
+ description,
12405
12356
  antigravity: frontmatter
12406
12357
  })
12407
12358
  };
@@ -12414,7 +12365,7 @@ var modelDecisionStrategy = {
12414
12365
  }),
12415
12366
  exportRulesyncData: ({ description, ...frontmatter }) => ({
12416
12367
  globs: [],
12417
- description: description || "",
12368
+ description,
12418
12369
  antigravity: frontmatter
12419
12370
  })
12420
12371
  };
@@ -12429,7 +12380,7 @@ var unknownStrategy = {
12429
12380
  },
12430
12381
  exportRulesyncData: ({ description, ...frontmatter }) => ({
12431
12382
  globs: frontmatter.globs ? parseGlobsString(frontmatter.globs) : ["**/*"],
12432
- description: description || "",
12383
+ description,
12433
12384
  antigravity: frontmatter
12434
12385
  })
12435
12386
  };
@@ -12451,7 +12402,7 @@ var inferenceStrategy = {
12451
12402
  },
12452
12403
  exportRulesyncData: ({ description, ...frontmatter }) => ({
12453
12404
  globs: frontmatter.globs ? parseGlobsString(frontmatter.globs) : ["**/*"],
12454
- description: description || "",
12405
+ description,
12455
12406
  antigravity: frontmatter
12456
12407
  })
12457
12408
  };
@@ -12581,7 +12532,6 @@ var AntigravityRule = class _AntigravityRule extends ToolRule {
12581
12532
  const strategy = STRATEGIES.find((s) => s.canHandle(this.frontmatter.trigger));
12582
12533
  let rulesyncData = {
12583
12534
  globs: [],
12584
- description: "",
12585
12535
  antigravity: this.frontmatter
12586
12536
  };
12587
12537
  if (strategy) {
@@ -12651,7 +12601,6 @@ var AugmentcodeLegacyRule = class _AugmentcodeLegacyRule extends ToolRule {
12651
12601
  const rulesyncFrontmatter = {
12652
12602
  root: this.isRoot(),
12653
12603
  targets: ["*"],
12654
- description: "",
12655
12604
  globs: this.isRoot() ? ["**/*"] : []
12656
12605
  };
12657
12606
  return new RulesyncRule({
@@ -12826,6 +12775,12 @@ var ClaudecodeLegacyRule = class _ClaudecodeLegacyRule extends ToolRule {
12826
12775
  relativeDirPath: ".",
12827
12776
  relativeFilePath: "CLAUDE.md"
12828
12777
  },
12778
+ alternativeRoots: [
12779
+ {
12780
+ relativeDirPath: ".claude",
12781
+ relativeFilePath: "CLAUDE.md"
12782
+ }
12783
+ ],
12829
12784
  nonRoot: {
12830
12785
  relativeDirPath: buildToolPath(".claude", "memories", excludeToolDir)
12831
12786
  }
@@ -12835,18 +12790,19 @@ var ClaudecodeLegacyRule = class _ClaudecodeLegacyRule extends ToolRule {
12835
12790
  baseDir = process.cwd(),
12836
12791
  relativeFilePath,
12837
12792
  validate = true,
12838
- global = false
12793
+ global = false,
12794
+ relativeDirPath: overrideDirPath
12839
12795
  }) {
12840
12796
  const paths = this.getSettablePaths({ global });
12841
12797
  const isRoot = relativeFilePath === paths.root.relativeFilePath;
12842
12798
  if (isRoot) {
12843
- const relativePath2 = paths.root.relativeFilePath;
12799
+ const rootDirPath = overrideDirPath ?? paths.root.relativeDirPath;
12844
12800
  const fileContent2 = await readFileContent(
12845
- (0, import_node_path95.join)(baseDir, paths.root.relativeDirPath, relativePath2)
12801
+ (0, import_node_path95.join)(baseDir, rootDirPath, paths.root.relativeFilePath)
12846
12802
  );
12847
12803
  return new _ClaudecodeLegacyRule({
12848
12804
  baseDir,
12849
- relativeDirPath: paths.root.relativeDirPath,
12805
+ relativeDirPath: rootDirPath,
12850
12806
  relativeFilePath: paths.root.relativeFilePath,
12851
12807
  fileContent: fileContent2,
12852
12808
  validate,
@@ -12941,6 +12897,12 @@ var ClaudecodeRule = class _ClaudecodeRule extends ToolRule {
12941
12897
  relativeDirPath: ".",
12942
12898
  relativeFilePath: "CLAUDE.md"
12943
12899
  },
12900
+ alternativeRoots: [
12901
+ {
12902
+ relativeDirPath: ".claude",
12903
+ relativeFilePath: "CLAUDE.md"
12904
+ }
12905
+ ],
12944
12906
  nonRoot: {
12945
12907
  relativeDirPath: buildToolPath(".claude", "rules", excludeToolDir)
12946
12908
  }
@@ -12973,17 +12935,19 @@ var ClaudecodeRule = class _ClaudecodeRule extends ToolRule {
12973
12935
  baseDir = process.cwd(),
12974
12936
  relativeFilePath,
12975
12937
  validate = true,
12976
- global = false
12938
+ global = false,
12939
+ relativeDirPath: overrideDirPath
12977
12940
  }) {
12978
12941
  const paths = this.getSettablePaths({ global });
12979
12942
  const isRoot = relativeFilePath === paths.root.relativeFilePath;
12980
12943
  if (isRoot) {
12944
+ const rootDirPath = overrideDirPath ?? paths.root.relativeDirPath;
12981
12945
  const fileContent2 = await readFileContent(
12982
- (0, import_node_path96.join)(baseDir, paths.root.relativeDirPath, paths.root.relativeFilePath)
12946
+ (0, import_node_path96.join)(baseDir, rootDirPath, paths.root.relativeFilePath)
12983
12947
  );
12984
12948
  return new _ClaudecodeRule({
12985
12949
  baseDir,
12986
- relativeDirPath: paths.root.relativeDirPath,
12950
+ relativeDirPath: rootDirPath,
12987
12951
  relativeFilePath: paths.root.relativeFilePath,
12988
12952
  frontmatter: {},
12989
12953
  body: fileContent2.trim(),
@@ -15340,35 +15304,62 @@ var RulesProcessor = class extends FeatureProcessor {
15340
15304
  const settablePaths = factory.class.getSettablePaths({
15341
15305
  global: this.global
15342
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
+ };
15343
15321
  const rootToolRules = await (async () => {
15344
15322
  if (!settablePaths.root) {
15345
15323
  return [];
15346
15324
  }
15347
- const rootFilePaths = await findFilesByGlobs(
15325
+ const uniqueRootFilePaths = await findFilesWithFallback(
15348
15326
  (0, import_node_path113.join)(
15349
15327
  this.baseDir,
15350
15328
  settablePaths.root.relativeDirPath ?? ".",
15351
15329
  settablePaths.root.relativeFilePath
15352
- )
15330
+ ),
15331
+ settablePaths.alternativeRoots,
15332
+ (alt) => (0, import_node_path113.join)(this.baseDir, alt.relativeDirPath, alt.relativeFilePath)
15353
15333
  );
15354
15334
  if (forDeletion) {
15355
- return rootFilePaths.map(
15356
- (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({
15357
15342
  baseDir: this.baseDir,
15358
- relativeDirPath: settablePaths.root?.relativeDirPath ?? ".",
15343
+ relativeDirPath,
15359
15344
  relativeFilePath: (0, import_node_path113.basename)(filePath),
15360
15345
  global: this.global
15361
- })
15362
- ).filter((rule) => rule.isDeletable());
15346
+ });
15347
+ }).filter((rule) => rule.isDeletable());
15363
15348
  }
15364
15349
  return await Promise.all(
15365
- rootFilePaths.map(
15366
- (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({
15367
15357
  baseDir: this.baseDir,
15368
15358
  relativeFilePath: (0, import_node_path113.basename)(filePath),
15359
+ relativeDirPath,
15369
15360
  global: this.global
15370
- })
15371
- )
15361
+ });
15362
+ })
15372
15363
  );
15373
15364
  })();
15374
15365
  logger.debug(`Found ${rootToolRules.length} root tool rule files`);
@@ -15382,17 +15373,24 @@ var RulesProcessor = class extends FeatureProcessor {
15382
15373
  if (!settablePaths.root) {
15383
15374
  return [];
15384
15375
  }
15385
- const localRootFilePaths = await findFilesByGlobs(
15386
- (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")
15387
15380
  );
15388
- return localRootFilePaths.map(
15389
- (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({
15390
15388
  baseDir: this.baseDir,
15391
- relativeDirPath: settablePaths.root?.relativeDirPath ?? ".",
15389
+ relativeDirPath,
15392
15390
  relativeFilePath: (0, import_node_path113.basename)(filePath),
15393
15391
  global: this.global
15394
- })
15395
- ).filter((rule) => rule.isDeletable());
15392
+ });
15393
+ }).filter((rule) => rule.isDeletable());
15396
15394
  })();
15397
15395
  logger.debug(`Found ${localRootToolRules.length} local root tool rule files for deletion`);
15398
15396
  const nonRootToolRules = await (async () => {
@@ -20194,7 +20192,7 @@ async function updateCommand(currentVersion, options) {
20194
20192
  }
20195
20193
 
20196
20194
  // src/cli/index.ts
20197
- var getVersion = () => "7.12.0";
20195
+ var getVersion = () => "7.12.1";
20198
20196
  var main = async () => {
20199
20197
  const program = new import_commander.Command();
20200
20198
  const version = getVersion();