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.
@@ -906,7 +906,7 @@ var ToolCommand = class extends AiFile {
906
906
 
907
907
  // src/features/commands/simulated-command.ts
908
908
  var SimulatedCommandFrontmatterSchema = z4.object({
909
- description: z4.string()
909
+ description: z4.optional(z4.string())
910
910
  });
911
911
  var SimulatedCommand = class _SimulatedCommand extends ToolCommand {
912
912
  frontmatter;
@@ -1093,7 +1093,7 @@ var RulesyncFile = class extends AiFile {
1093
1093
  // src/features/commands/rulesync-command.ts
1094
1094
  var RulesyncCommandFrontmatterSchema = z5.looseObject({
1095
1095
  targets: z5._default(RulesyncTargetsSchema, ["*"]),
1096
- description: z5.string()
1096
+ description: z5.optional(z5.string())
1097
1097
  });
1098
1098
  var RulesyncCommand = class _RulesyncCommand extends RulesyncFile {
1099
1099
  frontmatter;
@@ -1181,7 +1181,7 @@ var AntigravityWorkflowFrontmatterSchema = z6.looseObject({
1181
1181
  turbo: z6.optional(z6.boolean())
1182
1182
  });
1183
1183
  var AntigravityCommandFrontmatterSchema = z6.looseObject({
1184
- description: z6.string(),
1184
+ description: z6.optional(z6.string()),
1185
1185
  // Support for workflow-specific configuration
1186
1186
  ...AntigravityWorkflowFrontmatterSchema.shape
1187
1187
  });
@@ -1354,7 +1354,7 @@ ${body}${turboDirective}`;
1354
1354
  import { join as join8 } from "path";
1355
1355
  import { z as z7 } from "zod/mini";
1356
1356
  var ClaudecodeCommandFrontmatterSchema = z7.looseObject({
1357
- description: z7.string(),
1357
+ description: z7.optional(z7.string()),
1358
1358
  "allowed-tools": z7.optional(z7.union([z7.string(), z7.array(z7.string())])),
1359
1359
  "argument-hint": z7.optional(z7.string()),
1360
1360
  model: z7.optional(z7.string()),
@@ -1509,8 +1509,7 @@ var ClineCommand = class _ClineCommand extends ToolCommand {
1509
1509
  }
1510
1510
  toRulesyncCommand() {
1511
1511
  const rulesyncFrontmatter = {
1512
- targets: ["*"],
1513
- description: ""
1512
+ targets: ["*"]
1514
1513
  };
1515
1514
  return new RulesyncCommand({
1516
1515
  baseDir: process.cwd(),
@@ -1595,8 +1594,7 @@ var CodexcliCommand = class _CodexcliCommand extends ToolCommand {
1595
1594
  }
1596
1595
  toRulesyncCommand() {
1597
1596
  const rulesyncFrontmatter = {
1598
- targets: ["*"],
1599
- description: ""
1597
+ targets: ["*"]
1600
1598
  };
1601
1599
  return new RulesyncCommand({
1602
1600
  baseDir: ".",
@@ -1674,7 +1672,7 @@ import { join as join11 } from "path";
1674
1672
  import { z as z8 } from "zod/mini";
1675
1673
  var CopilotCommandFrontmatterSchema = z8.looseObject({
1676
1674
  mode: z8.optional(z8.string()),
1677
- description: z8.string()
1675
+ description: z8.optional(z8.string())
1678
1676
  });
1679
1677
  var CopilotCommand = class _CopilotCommand extends ToolCommand {
1680
1678
  frontmatter;
@@ -1857,7 +1855,7 @@ var CursorCommand = class _CursorCommand extends ToolCommand {
1857
1855
  return this.frontmatter;
1858
1856
  }
1859
1857
  toRulesyncCommand() {
1860
- const { description = "", ...restFields } = this.frontmatter;
1858
+ const { description, ...restFields } = this.frontmatter;
1861
1859
  const rulesyncFrontmatter = {
1862
1860
  targets: ["*"],
1863
1861
  description,
@@ -2051,7 +2049,7 @@ var GeminiCliCommand = class _GeminiCliCommand extends ToolCommand {
2051
2049
  }
2052
2050
  return {
2053
2051
  ...result.data,
2054
- description: result.data.description || ""
2052
+ description: result.data.description
2055
2053
  };
2056
2054
  } catch (error) {
2057
2055
  throw new Error(
@@ -2073,7 +2071,7 @@ var GeminiCliCommand = class _GeminiCliCommand extends ToolCommand {
2073
2071
  const { description, prompt: _prompt, ...restFields } = this.frontmatter;
2074
2072
  const rulesyncFrontmatter = {
2075
2073
  targets: ["geminicli"],
2076
- description: description ?? "",
2074
+ description,
2077
2075
  // Preserve extra fields in geminicli section (excluding prompt which is the body)
2078
2076
  ...Object.keys(restFields).length > 0 && { geminicli: restFields }
2079
2077
  };
@@ -2102,8 +2100,9 @@ var GeminiCliCommand = class _GeminiCliCommand extends ToolCommand {
2102
2100
  prompt: rulesyncCommand.getBody(),
2103
2101
  ...geminicliFields
2104
2102
  };
2105
- const tomlContent = `description = "${geminiFrontmatter.description}"
2106
- prompt = """
2103
+ const descriptionLine = geminiFrontmatter.description !== void 0 ? `description = "${geminiFrontmatter.description}"
2104
+ ` : "";
2105
+ const tomlContent = `${descriptionLine}prompt = """
2107
2106
  ${geminiFrontmatter.prompt}
2108
2107
  """`;
2109
2108
  const paths = this.getSettablePaths({ global });
@@ -2173,8 +2172,7 @@ var KiloCommand = class _KiloCommand extends ToolCommand {
2173
2172
  }
2174
2173
  toRulesyncCommand() {
2175
2174
  const rulesyncFrontmatter = {
2176
- targets: ["*"],
2177
- description: ""
2175
+ targets: ["*"]
2178
2176
  };
2179
2177
  return new RulesyncCommand({
2180
2178
  baseDir: process.cwd(),
@@ -2254,8 +2252,7 @@ var KiroCommand = class _KiroCommand extends ToolCommand {
2254
2252
  }
2255
2253
  toRulesyncCommand() {
2256
2254
  const rulesyncFrontmatter = {
2257
- targets: ["*"],
2258
- description: ""
2255
+ targets: ["*"]
2259
2256
  };
2260
2257
  return new RulesyncCommand({
2261
2258
  baseDir: process.cwd(),
@@ -2329,7 +2326,7 @@ var KiroCommand = class _KiroCommand extends ToolCommand {
2329
2326
  import { join as join17 } from "path";
2330
2327
  import { optional as optional2, z as z11 } from "zod/mini";
2331
2328
  var OpenCodeCommandFrontmatterSchema = z11.looseObject({
2332
- description: z11.string(),
2329
+ description: z11.optional(z11.string()),
2333
2330
  agent: optional2(z11.string()),
2334
2331
  subtask: optional2(z11.boolean()),
2335
2332
  model: optional2(z11.string())
@@ -2469,7 +2466,7 @@ var OpenCodeCommand = class _OpenCodeCommand extends ToolCommand {
2469
2466
  import { join as join18 } from "path";
2470
2467
  import { optional as optional3, z as z12 } from "zod/mini";
2471
2468
  var RooCommandFrontmatterSchema = z12.looseObject({
2472
- description: z12.string(),
2469
+ description: z12.optional(z12.string()),
2473
2470
  "argument-hint": optional3(z12.string())
2474
2471
  });
2475
2472
  var RooCommand = class _RooCommand extends ToolCommand {
@@ -3008,7 +3005,7 @@ var HookDefinitionSchema = z14.looseObject({
3008
3005
  type: z14.optional(z14.enum(["command", "prompt"])),
3009
3006
  timeout: z14.optional(z14.number()),
3010
3007
  matcher: z14.optional(safeString),
3011
- prompt: z14.optional(z14.string()),
3008
+ prompt: z14.optional(safeString),
3012
3009
  loop_limit: z14.optional(z14.nullable(z14.number())),
3013
3010
  name: z14.optional(safeString),
3014
3011
  description: z14.optional(safeString)
@@ -3060,7 +3057,7 @@ var OPENCODE_HOOK_EVENTS = [
3060
3057
  var COPILOT_HOOK_EVENTS = [
3061
3058
  "sessionStart",
3062
3059
  "sessionEnd",
3063
- "afterSubmitPrompt",
3060
+ "beforeSubmitPrompt",
3064
3061
  "preToolUse",
3065
3062
  "postToolUse",
3066
3063
  "afterError"
@@ -3171,7 +3168,7 @@ var CANONICAL_TO_OPENCODE_EVENT_NAMES = {
3171
3168
  var CANONICAL_TO_COPILOT_EVENT_NAMES = {
3172
3169
  sessionStart: "sessionStart",
3173
3170
  sessionEnd: "sessionEnd",
3174
- afterSubmitPrompt: "userPromptSubmitted",
3171
+ beforeSubmitPrompt: "userPromptSubmitted",
3175
3172
  preToolUse: "preToolUse",
3176
3173
  postToolUse: "postToolUse",
3177
3174
  afterError: "errorOccurred"
@@ -3199,6 +3196,107 @@ var GEMINICLI_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(
3199
3196
  // src/features/hooks/claudecode-hooks.ts
3200
3197
  import { join as join21 } from "path";
3201
3198
 
3199
+ // src/features/hooks/tool-hooks-converter.ts
3200
+ function isToolMatcherEntry(x) {
3201
+ if (x === null || typeof x !== "object") {
3202
+ return false;
3203
+ }
3204
+ if ("matcher" in x && typeof x.matcher !== "string") {
3205
+ return false;
3206
+ }
3207
+ if ("hooks" in x && !Array.isArray(x.hooks)) {
3208
+ return false;
3209
+ }
3210
+ return true;
3211
+ }
3212
+ function canonicalToToolHooks({
3213
+ config,
3214
+ toolOverrideHooks,
3215
+ converterConfig
3216
+ }) {
3217
+ const supported = new Set(converterConfig.supportedEvents);
3218
+ const sharedHooks = {};
3219
+ for (const [event, defs] of Object.entries(config.hooks)) {
3220
+ if (supported.has(event)) {
3221
+ sharedHooks[event] = defs;
3222
+ }
3223
+ }
3224
+ const effectiveHooks = {
3225
+ ...sharedHooks,
3226
+ ...toolOverrideHooks
3227
+ };
3228
+ const result = {};
3229
+ for (const [eventName, definitions] of Object.entries(effectiveHooks)) {
3230
+ const toolEventName = converterConfig.canonicalToToolEventNames[eventName] ?? eventName;
3231
+ const byMatcher = /* @__PURE__ */ new Map();
3232
+ for (const def of definitions) {
3233
+ const key = def.matcher ?? "";
3234
+ const list = byMatcher.get(key);
3235
+ if (list) list.push(def);
3236
+ else byMatcher.set(key, [def]);
3237
+ }
3238
+ const entries = [];
3239
+ for (const [matcherKey, defs] of byMatcher) {
3240
+ const hooks = defs.map((def) => {
3241
+ const commandText = def.command;
3242
+ const trimmedCommand = typeof commandText === "string" ? commandText.trimStart() : void 0;
3243
+ const shouldPrefix = typeof trimmedCommand === "string" && !trimmedCommand.startsWith("$") && (!converterConfig.prefixDotRelativeCommandsOnly || trimmedCommand.startsWith("."));
3244
+ const command = shouldPrefix && typeof trimmedCommand === "string" ? `${converterConfig.projectDirVar}/${trimmedCommand.replace(/^\.\//, "")}` : def.command;
3245
+ return {
3246
+ type: def.type ?? "command",
3247
+ ...command !== void 0 && command !== null && { command },
3248
+ ...def.timeout !== void 0 && def.timeout !== null && { timeout: def.timeout },
3249
+ ...def.prompt !== void 0 && def.prompt !== null && { prompt: def.prompt }
3250
+ };
3251
+ });
3252
+ entries.push(matcherKey ? { matcher: matcherKey, hooks } : { hooks });
3253
+ }
3254
+ result[toolEventName] = entries;
3255
+ }
3256
+ return result;
3257
+ }
3258
+ function toolHooksToCanonical({
3259
+ hooks,
3260
+ converterConfig
3261
+ }) {
3262
+ if (hooks === null || hooks === void 0 || typeof hooks !== "object") {
3263
+ return {};
3264
+ }
3265
+ const canonical = {};
3266
+ for (const [toolEventName, matcherEntries] of Object.entries(hooks)) {
3267
+ const eventName = converterConfig.toolToCanonicalEventNames[toolEventName] ?? toolEventName;
3268
+ if (!Array.isArray(matcherEntries)) continue;
3269
+ const defs = [];
3270
+ for (const rawEntry of matcherEntries) {
3271
+ if (!isToolMatcherEntry(rawEntry)) continue;
3272
+ const hookDefs = rawEntry.hooks ?? [];
3273
+ for (const h of hookDefs) {
3274
+ const cmd = typeof h.command === "string" ? h.command : void 0;
3275
+ const command = typeof cmd === "string" && cmd.includes(`${converterConfig.projectDirVar}/`) ? cmd.replace(
3276
+ new RegExp(
3277
+ `^${converterConfig.projectDirVar.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\/?`
3278
+ ),
3279
+ "./"
3280
+ ) : cmd;
3281
+ const hookType = h.type === "command" || h.type === "prompt" ? h.type : "command";
3282
+ const timeout = typeof h.timeout === "number" ? h.timeout : void 0;
3283
+ const prompt = typeof h.prompt === "string" ? h.prompt : void 0;
3284
+ defs.push({
3285
+ type: hookType,
3286
+ ...command !== void 0 && command !== null && { command },
3287
+ ...timeout !== void 0 && timeout !== null && { timeout },
3288
+ ...prompt !== void 0 && prompt !== null && { prompt },
3289
+ ...rawEntry.matcher !== void 0 && rawEntry.matcher !== null && rawEntry.matcher !== "" && { matcher: rawEntry.matcher }
3290
+ });
3291
+ }
3292
+ }
3293
+ if (defs.length > 0) {
3294
+ canonical[eventName] = defs;
3295
+ }
3296
+ }
3297
+ return canonical;
3298
+ }
3299
+
3202
3300
  // src/types/tool-file.ts
3203
3301
  var ToolFile = class extends AiFile {
3204
3302
  };
@@ -3289,91 +3387,13 @@ var ToolHooks = class extends ToolFile {
3289
3387
  };
3290
3388
 
3291
3389
  // src/features/hooks/claudecode-hooks.ts
3292
- function canonicalToClaudeHooks(config) {
3293
- const claudeSupported = new Set(CLAUDE_HOOK_EVENTS);
3294
- const sharedHooks = {};
3295
- for (const [event, defs] of Object.entries(config.hooks)) {
3296
- if (claudeSupported.has(event)) {
3297
- sharedHooks[event] = defs;
3298
- }
3299
- }
3300
- const effectiveHooks = {
3301
- ...sharedHooks,
3302
- ...config.claudecode?.hooks
3303
- };
3304
- const claude = {};
3305
- for (const [eventName, definitions] of Object.entries(effectiveHooks)) {
3306
- const claudeEventName = CANONICAL_TO_CLAUDE_EVENT_NAMES[eventName] ?? eventName;
3307
- const byMatcher = /* @__PURE__ */ new Map();
3308
- for (const def of definitions) {
3309
- const key = def.matcher ?? "";
3310
- const list = byMatcher.get(key);
3311
- if (list) list.push(def);
3312
- else byMatcher.set(key, [def]);
3313
- }
3314
- const entries = [];
3315
- for (const [matcherKey, defs] of byMatcher) {
3316
- const hooks = defs.map((def) => {
3317
- const command = def.command !== void 0 && def.command !== null && !def.command.startsWith("$") ? `$CLAUDE_PROJECT_DIR/${def.command.replace(/^\.\//, "")}` : def.command;
3318
- return {
3319
- type: def.type ?? "command",
3320
- ...command !== void 0 && command !== null && { command },
3321
- ...def.timeout !== void 0 && def.timeout !== null && { timeout: def.timeout },
3322
- ...def.prompt !== void 0 && def.prompt !== null && { prompt: def.prompt }
3323
- };
3324
- });
3325
- entries.push(matcherKey ? { matcher: matcherKey, hooks } : { hooks });
3326
- }
3327
- claude[claudeEventName] = entries;
3328
- }
3329
- return claude;
3330
- }
3331
- function isClaudeMatcherEntry(x) {
3332
- if (x === null || typeof x !== "object") {
3333
- return false;
3334
- }
3335
- if ("matcher" in x && typeof x.matcher !== "string") {
3336
- return false;
3337
- }
3338
- if ("hooks" in x && !Array.isArray(x.hooks)) {
3339
- return false;
3340
- }
3341
- return true;
3342
- }
3343
- function claudeHooksToCanonical(claudeHooks) {
3344
- if (claudeHooks === null || claudeHooks === void 0 || typeof claudeHooks !== "object") {
3345
- return {};
3346
- }
3347
- const canonical = {};
3348
- for (const [claudeEventName, matcherEntries] of Object.entries(claudeHooks)) {
3349
- const eventName = CLAUDE_TO_CANONICAL_EVENT_NAMES[claudeEventName] ?? claudeEventName;
3350
- if (!Array.isArray(matcherEntries)) continue;
3351
- const defs = [];
3352
- for (const rawEntry of matcherEntries) {
3353
- if (!isClaudeMatcherEntry(rawEntry)) continue;
3354
- const entry = rawEntry;
3355
- const hooks = entry.hooks ?? [];
3356
- for (const h of hooks) {
3357
- const cmd = typeof h.command === "string" ? h.command : void 0;
3358
- const command = typeof cmd === "string" && cmd.includes("$CLAUDE_PROJECT_DIR/") ? cmd.replace(/^\$CLAUDE_PROJECT_DIR\/?/, "./") : cmd;
3359
- const hookType = h.type === "command" || h.type === "prompt" ? h.type : "command";
3360
- const timeout = typeof h.timeout === "number" ? h.timeout : void 0;
3361
- const prompt = typeof h.prompt === "string" ? h.prompt : void 0;
3362
- defs.push({
3363
- type: hookType,
3364
- ...command !== void 0 && command !== null && { command },
3365
- ...timeout !== void 0 && timeout !== null && { timeout },
3366
- ...prompt !== void 0 && prompt !== null && { prompt },
3367
- ...entry.matcher !== void 0 && entry.matcher !== null && entry.matcher !== "" && { matcher: entry.matcher }
3368
- });
3369
- }
3370
- }
3371
- if (defs.length > 0) {
3372
- canonical[eventName] = defs;
3373
- }
3374
- }
3375
- return canonical;
3376
- }
3390
+ var CLAUDE_CONVERTER_CONFIG = {
3391
+ supportedEvents: CLAUDE_HOOK_EVENTS,
3392
+ canonicalToToolEventNames: CANONICAL_TO_CLAUDE_EVENT_NAMES,
3393
+ toolToCanonicalEventNames: CLAUDE_TO_CANONICAL_EVENT_NAMES,
3394
+ projectDirVar: "$CLAUDE_PROJECT_DIR",
3395
+ prefixDotRelativeCommandsOnly: true
3396
+ };
3377
3397
  var ClaudecodeHooks = class _ClaudecodeHooks extends ToolHooks {
3378
3398
  constructor(params) {
3379
3399
  super({
@@ -3425,7 +3445,11 @@ var ClaudecodeHooks = class _ClaudecodeHooks extends ToolHooks {
3425
3445
  );
3426
3446
  }
3427
3447
  const config = rulesyncHooks.getJson();
3428
- const claudeHooks = canonicalToClaudeHooks(config);
3448
+ const claudeHooks = canonicalToToolHooks({
3449
+ config,
3450
+ toolOverrideHooks: config.claudecode?.hooks,
3451
+ converterConfig: CLAUDE_CONVERTER_CONFIG
3452
+ });
3429
3453
  const merged = { ...settings, hooks: claudeHooks };
3430
3454
  const fileContent = JSON.stringify(merged, null, 2);
3431
3455
  return new _ClaudecodeHooks({
@@ -3448,7 +3472,10 @@ var ClaudecodeHooks = class _ClaudecodeHooks extends ToolHooks {
3448
3472
  }
3449
3473
  );
3450
3474
  }
3451
- const hooks = claudeHooksToCanonical(settings.hooks);
3475
+ const hooks = toolHooksToCanonical({
3476
+ hooks: settings.hooks,
3477
+ converterConfig: CLAUDE_CONVERTER_CONFIG
3478
+ });
3452
3479
  return this.toRulesyncHooksDefault({
3453
3480
  fileContent: JSON.stringify({ version: 1, hooks }, null, 2)
3454
3481
  });
@@ -3757,91 +3784,12 @@ var CursorHooks = class _CursorHooks extends ToolHooks {
3757
3784
 
3758
3785
  // src/features/hooks/factorydroid-hooks.ts
3759
3786
  import { join as join24 } from "path";
3760
- function canonicalToFactorydroidHooks(config) {
3761
- const supported = new Set(FACTORYDROID_HOOK_EVENTS);
3762
- const sharedHooks = {};
3763
- for (const [event, defs] of Object.entries(config.hooks)) {
3764
- if (supported.has(event)) {
3765
- sharedHooks[event] = defs;
3766
- }
3767
- }
3768
- const effectiveHooks = {
3769
- ...sharedHooks,
3770
- ...config.factorydroid?.hooks
3771
- };
3772
- const result = {};
3773
- for (const [eventName, definitions] of Object.entries(effectiveHooks)) {
3774
- const pascalEventName = CANONICAL_TO_FACTORYDROID_EVENT_NAMES[eventName] ?? eventName;
3775
- const byMatcher = /* @__PURE__ */ new Map();
3776
- for (const def of definitions) {
3777
- const key = def.matcher ?? "";
3778
- const list = byMatcher.get(key);
3779
- if (list) list.push(def);
3780
- else byMatcher.set(key, [def]);
3781
- }
3782
- const entries = [];
3783
- for (const [matcherKey, defs] of byMatcher) {
3784
- const hooks = defs.map((def) => {
3785
- const command = def.command !== void 0 && def.command !== null && !def.command.startsWith("$") ? `$FACTORY_PROJECT_DIR/${def.command.replace(/^\.\//, "")}` : def.command;
3786
- return {
3787
- type: def.type ?? "command",
3788
- ...command !== void 0 && command !== null && { command },
3789
- ...def.timeout !== void 0 && def.timeout !== null && { timeout: def.timeout },
3790
- ...def.prompt !== void 0 && def.prompt !== null && { prompt: def.prompt }
3791
- };
3792
- });
3793
- entries.push(matcherKey ? { matcher: matcherKey, hooks } : { hooks });
3794
- }
3795
- result[pascalEventName] = entries;
3796
- }
3797
- return result;
3798
- }
3799
- function isFactorydroidMatcherEntry(x) {
3800
- if (x === null || typeof x !== "object") {
3801
- return false;
3802
- }
3803
- if ("matcher" in x && typeof x.matcher !== "string") {
3804
- return false;
3805
- }
3806
- if ("hooks" in x && !Array.isArray(x.hooks)) {
3807
- return false;
3808
- }
3809
- return true;
3810
- }
3811
- function factorydroidHooksToCanonical(hooks) {
3812
- if (hooks === null || hooks === void 0 || typeof hooks !== "object") {
3813
- return {};
3814
- }
3815
- const canonical = {};
3816
- for (const [pascalEventName, matcherEntries] of Object.entries(hooks)) {
3817
- const eventName = FACTORYDROID_TO_CANONICAL_EVENT_NAMES[pascalEventName] ?? pascalEventName;
3818
- if (!Array.isArray(matcherEntries)) continue;
3819
- const defs = [];
3820
- for (const rawEntry of matcherEntries) {
3821
- if (!isFactorydroidMatcherEntry(rawEntry)) continue;
3822
- const entry = rawEntry;
3823
- const hookDefs = entry.hooks ?? [];
3824
- for (const h of hookDefs) {
3825
- const cmd = typeof h.command === "string" ? h.command : void 0;
3826
- const command = typeof cmd === "string" && cmd.includes("$FACTORY_PROJECT_DIR/") ? cmd.replace(/^\$FACTORY_PROJECT_DIR\/?/, "./") : cmd;
3827
- const hookType = h.type === "command" || h.type === "prompt" ? h.type : "command";
3828
- const timeout = typeof h.timeout === "number" ? h.timeout : void 0;
3829
- const prompt = typeof h.prompt === "string" ? h.prompt : void 0;
3830
- defs.push({
3831
- type: hookType,
3832
- ...command !== void 0 && command !== null && { command },
3833
- ...timeout !== void 0 && timeout !== null && { timeout },
3834
- ...prompt !== void 0 && prompt !== null && { prompt },
3835
- ...entry.matcher !== void 0 && entry.matcher !== null && entry.matcher !== "" && { matcher: entry.matcher }
3836
- });
3837
- }
3838
- }
3839
- if (defs.length > 0) {
3840
- canonical[eventName] = defs;
3841
- }
3842
- }
3843
- return canonical;
3844
- }
3787
+ var FACTORYDROID_CONVERTER_CONFIG = {
3788
+ supportedEvents: FACTORYDROID_HOOK_EVENTS,
3789
+ canonicalToToolEventNames: CANONICAL_TO_FACTORYDROID_EVENT_NAMES,
3790
+ toolToCanonicalEventNames: FACTORYDROID_TO_CANONICAL_EVENT_NAMES,
3791
+ projectDirVar: "$FACTORY_PROJECT_DIR"
3792
+ };
3845
3793
  var FactorydroidHooks = class _FactorydroidHooks extends ToolHooks {
3846
3794
  constructor(params) {
3847
3795
  super({
@@ -3893,7 +3841,11 @@ var FactorydroidHooks = class _FactorydroidHooks extends ToolHooks {
3893
3841
  );
3894
3842
  }
3895
3843
  const config = rulesyncHooks.getJson();
3896
- const factorydroidHooks = canonicalToFactorydroidHooks(config);
3844
+ const factorydroidHooks = canonicalToToolHooks({
3845
+ config,
3846
+ toolOverrideHooks: config.factorydroid?.hooks,
3847
+ converterConfig: FACTORYDROID_CONVERTER_CONFIG
3848
+ });
3897
3849
  const merged = { ...settings, hooks: factorydroidHooks };
3898
3850
  const fileContent = JSON.stringify(merged, null, 2);
3899
3851
  return new _FactorydroidHooks({
@@ -3916,7 +3868,10 @@ var FactorydroidHooks = class _FactorydroidHooks extends ToolHooks {
3916
3868
  }
3917
3869
  );
3918
3870
  }
3919
- const hooks = factorydroidHooksToCanonical(settings.hooks);
3871
+ const hooks = toolHooksToCanonical({
3872
+ hooks: settings.hooks,
3873
+ converterConfig: FACTORYDROID_CONVERTER_CONFIG
3874
+ });
3920
3875
  return this.toRulesyncHooksDefault({
3921
3876
  fileContent: JSON.stringify({ version: 1, hooks }, null, 2)
3922
3877
  });
@@ -3967,7 +3922,10 @@ function canonicalToGeminicliHooks(config) {
3967
3922
  const entries = [];
3968
3923
  for (const [matcherKey, defs] of byMatcher) {
3969
3924
  const hooks = defs.map((def) => {
3970
- const command = def.command !== void 0 && def.command !== null && !def.command.startsWith("$") ? `$GEMINI_PROJECT_DIR/${def.command.replace(/^\.\//, "")}` : def.command;
3925
+ const commandText = def.command;
3926
+ const trimmedCommand = typeof commandText === "string" ? commandText.trimStart() : void 0;
3927
+ const shouldPrefix = typeof trimmedCommand === "string" && !trimmedCommand.startsWith("$") && trimmedCommand.startsWith(".");
3928
+ const command = shouldPrefix && typeof trimmedCommand === "string" ? `$GEMINI_PROJECT_DIR/${trimmedCommand.replace(/^\.\//, "")}` : def.command;
3971
3929
  return {
3972
3930
  type: def.type ?? "command",
3973
3931
  ...command !== void 0 && command !== null && { command },
@@ -7444,7 +7402,7 @@ var McpProcessor = class extends FeatureProcessor {
7444
7402
  };
7445
7403
 
7446
7404
  // src/features/rules/rules-processor.ts
7447
- import { basename as basename10, join as join113, relative as relative5 } from "path";
7405
+ import { basename as basename10, dirname as dirname3, join as join113, relative as relative5 } from "path";
7448
7406
  import { encode } from "@toon-format/toon";
7449
7407
  import { z as z54 } from "zod/mini";
7450
7408
 
@@ -10713,7 +10671,7 @@ var ToolSubagent = class extends ToolFile {
10713
10671
  // src/features/subagents/simulated-subagent.ts
10714
10672
  var SimulatedSubagentFrontmatterSchema = z39.object({
10715
10673
  name: z39.string(),
10716
- description: z39.string()
10674
+ description: z39.optional(z39.string())
10717
10675
  });
10718
10676
  var SimulatedSubagent = class extends ToolSubagent {
10719
10677
  frontmatter;
@@ -10937,7 +10895,7 @@ import { z as z40 } from "zod/mini";
10937
10895
  var RulesyncSubagentFrontmatterSchema = z40.looseObject({
10938
10896
  targets: z40._default(RulesyncTargetsSchema, ["*"]),
10939
10897
  name: z40.string(),
10940
- description: z40.string()
10898
+ description: z40.optional(z40.string())
10941
10899
  });
10942
10900
  var RulesyncSubagent = class _RulesyncSubagent extends RulesyncFile {
10943
10901
  frontmatter;
@@ -11008,7 +10966,7 @@ var RulesyncSubagent = class _RulesyncSubagent extends RulesyncFile {
11008
10966
  // src/features/subagents/claudecode-subagent.ts
11009
10967
  var ClaudecodeSubagentFrontmatterSchema = z41.looseObject({
11010
10968
  name: z41.string(),
11011
- description: z41.string(),
10969
+ description: z41.optional(z41.string()),
11012
10970
  model: z41.optional(z41.string()),
11013
10971
  tools: z41.optional(z41.union([z41.string(), z41.array(z41.string())])),
11014
10972
  permissionMode: z41.optional(z41.string()),
@@ -11218,7 +11176,7 @@ var CodexCliSubagent = class _CodexCliSubagent extends ToolSubagent {
11218
11176
  const rulesyncFrontmatter = {
11219
11177
  targets: ["codexcli"],
11220
11178
  name,
11221
- description: description ?? "",
11179
+ description,
11222
11180
  // Only include codexcli section if there are fields
11223
11181
  ...Object.keys(codexcliSection).length > 0 && { codexcli: codexcliSection }
11224
11182
  };
@@ -11331,7 +11289,7 @@ import { z as z43 } from "zod/mini";
11331
11289
  var REQUIRED_TOOL = "agent/runSubagent";
11332
11290
  var CopilotSubagentFrontmatterSchema = z43.looseObject({
11333
11291
  name: z43.string(),
11334
- description: z43.string(),
11292
+ description: z43.optional(z43.string()),
11335
11293
  tools: z43.optional(z43.union([z43.string(), z43.array(z43.string())]))
11336
11294
  });
11337
11295
  var normalizeTools = (tools) => {
@@ -11496,7 +11454,7 @@ import { join as join85 } from "path";
11496
11454
  import { z as z44 } from "zod/mini";
11497
11455
  var CursorSubagentFrontmatterSchema = z44.looseObject({
11498
11456
  name: z44.string(),
11499
- description: z44.string()
11457
+ description: z44.optional(z44.string())
11500
11458
  });
11501
11459
  var CursorSubagent = class _CursorSubagent extends ToolSubagent {
11502
11460
  frontmatter;
@@ -11701,7 +11659,7 @@ var KiroSubagent = class _KiroSubagent extends ToolSubagent {
11701
11659
  const rulesyncFrontmatter = {
11702
11660
  targets: ["kiro"],
11703
11661
  name,
11704
- description: description ?? "",
11662
+ description: description ?? void 0,
11705
11663
  // Only include kiro section if there are fields
11706
11664
  ...Object.keys(kiroSection).length > 0 && { kiro: kiroSection }
11707
11665
  };
@@ -11812,7 +11770,7 @@ var KiroSubagent = class _KiroSubagent extends ToolSubagent {
11812
11770
  import { basename as basename8, join as join87 } from "path";
11813
11771
  import { z as z46 } from "zod/mini";
11814
11772
  var OpenCodeSubagentFrontmatterSchema = z46.looseObject({
11815
- description: z46.string(),
11773
+ description: z46.optional(z46.string()),
11816
11774
  mode: z46._default(z46.string(), "subagent"),
11817
11775
  name: z46.optional(z46.string())
11818
11776
  });
@@ -12642,7 +12600,7 @@ var globStrategy = {
12642
12600
  },
12643
12601
  exportRulesyncData: ({ description, ...frontmatter }) => ({
12644
12602
  globs: parseGlobsString(frontmatter.globs),
12645
- description: description || "",
12603
+ description,
12646
12604
  antigravity: frontmatter
12647
12605
  })
12648
12606
  };
@@ -12654,7 +12612,7 @@ var manualStrategy = {
12654
12612
  }),
12655
12613
  exportRulesyncData: ({ description, ...frontmatter }) => ({
12656
12614
  globs: [],
12657
- description: description || "",
12615
+ description,
12658
12616
  antigravity: frontmatter
12659
12617
  })
12660
12618
  };
@@ -12666,7 +12624,7 @@ var alwaysOnStrategy = {
12666
12624
  }),
12667
12625
  exportRulesyncData: ({ description, ...frontmatter }) => ({
12668
12626
  globs: ["**/*"],
12669
- description: description || "",
12627
+ description,
12670
12628
  antigravity: frontmatter
12671
12629
  })
12672
12630
  };
@@ -12679,7 +12637,7 @@ var modelDecisionStrategy = {
12679
12637
  }),
12680
12638
  exportRulesyncData: ({ description, ...frontmatter }) => ({
12681
12639
  globs: [],
12682
- description: description || "",
12640
+ description,
12683
12641
  antigravity: frontmatter
12684
12642
  })
12685
12643
  };
@@ -12694,7 +12652,7 @@ var unknownStrategy = {
12694
12652
  },
12695
12653
  exportRulesyncData: ({ description, ...frontmatter }) => ({
12696
12654
  globs: frontmatter.globs ? parseGlobsString(frontmatter.globs) : ["**/*"],
12697
- description: description || "",
12655
+ description,
12698
12656
  antigravity: frontmatter
12699
12657
  })
12700
12658
  };
@@ -12716,7 +12674,7 @@ var inferenceStrategy = {
12716
12674
  },
12717
12675
  exportRulesyncData: ({ description, ...frontmatter }) => ({
12718
12676
  globs: frontmatter.globs ? parseGlobsString(frontmatter.globs) : ["**/*"],
12719
- description: description || "",
12677
+ description,
12720
12678
  antigravity: frontmatter
12721
12679
  })
12722
12680
  };
@@ -12846,7 +12804,6 @@ var AntigravityRule = class _AntigravityRule extends ToolRule {
12846
12804
  const strategy = STRATEGIES.find((s) => s.canHandle(this.frontmatter.trigger));
12847
12805
  let rulesyncData = {
12848
12806
  globs: [],
12849
- description: "",
12850
12807
  antigravity: this.frontmatter
12851
12808
  };
12852
12809
  if (strategy) {
@@ -12916,7 +12873,6 @@ var AugmentcodeLegacyRule = class _AugmentcodeLegacyRule extends ToolRule {
12916
12873
  const rulesyncFrontmatter = {
12917
12874
  root: this.isRoot(),
12918
12875
  targets: ["*"],
12919
- description: "",
12920
12876
  globs: this.isRoot() ? ["**/*"] : []
12921
12877
  };
12922
12878
  return new RulesyncRule({
@@ -13091,6 +13047,12 @@ var ClaudecodeLegacyRule = class _ClaudecodeLegacyRule extends ToolRule {
13091
13047
  relativeDirPath: ".",
13092
13048
  relativeFilePath: "CLAUDE.md"
13093
13049
  },
13050
+ alternativeRoots: [
13051
+ {
13052
+ relativeDirPath: ".claude",
13053
+ relativeFilePath: "CLAUDE.md"
13054
+ }
13055
+ ],
13094
13056
  nonRoot: {
13095
13057
  relativeDirPath: buildToolPath(".claude", "memories", excludeToolDir)
13096
13058
  }
@@ -13100,18 +13062,19 @@ var ClaudecodeLegacyRule = class _ClaudecodeLegacyRule extends ToolRule {
13100
13062
  baseDir = process.cwd(),
13101
13063
  relativeFilePath,
13102
13064
  validate = true,
13103
- global = false
13065
+ global = false,
13066
+ relativeDirPath: overrideDirPath
13104
13067
  }) {
13105
13068
  const paths = this.getSettablePaths({ global });
13106
13069
  const isRoot = relativeFilePath === paths.root.relativeFilePath;
13107
13070
  if (isRoot) {
13108
- const relativePath2 = paths.root.relativeFilePath;
13071
+ const rootDirPath = overrideDirPath ?? paths.root.relativeDirPath;
13109
13072
  const fileContent2 = await readFileContent(
13110
- join95(baseDir, paths.root.relativeDirPath, relativePath2)
13073
+ join95(baseDir, rootDirPath, paths.root.relativeFilePath)
13111
13074
  );
13112
13075
  return new _ClaudecodeLegacyRule({
13113
13076
  baseDir,
13114
- relativeDirPath: paths.root.relativeDirPath,
13077
+ relativeDirPath: rootDirPath,
13115
13078
  relativeFilePath: paths.root.relativeFilePath,
13116
13079
  fileContent: fileContent2,
13117
13080
  validate,
@@ -13206,6 +13169,12 @@ var ClaudecodeRule = class _ClaudecodeRule extends ToolRule {
13206
13169
  relativeDirPath: ".",
13207
13170
  relativeFilePath: "CLAUDE.md"
13208
13171
  },
13172
+ alternativeRoots: [
13173
+ {
13174
+ relativeDirPath: ".claude",
13175
+ relativeFilePath: "CLAUDE.md"
13176
+ }
13177
+ ],
13209
13178
  nonRoot: {
13210
13179
  relativeDirPath: buildToolPath(".claude", "rules", excludeToolDir)
13211
13180
  }
@@ -13238,17 +13207,19 @@ var ClaudecodeRule = class _ClaudecodeRule extends ToolRule {
13238
13207
  baseDir = process.cwd(),
13239
13208
  relativeFilePath,
13240
13209
  validate = true,
13241
- global = false
13210
+ global = false,
13211
+ relativeDirPath: overrideDirPath
13242
13212
  }) {
13243
13213
  const paths = this.getSettablePaths({ global });
13244
13214
  const isRoot = relativeFilePath === paths.root.relativeFilePath;
13245
13215
  if (isRoot) {
13216
+ const rootDirPath = overrideDirPath ?? paths.root.relativeDirPath;
13246
13217
  const fileContent2 = await readFileContent(
13247
- join96(baseDir, paths.root.relativeDirPath, paths.root.relativeFilePath)
13218
+ join96(baseDir, rootDirPath, paths.root.relativeFilePath)
13248
13219
  );
13249
13220
  return new _ClaudecodeRule({
13250
13221
  baseDir,
13251
- relativeDirPath: paths.root.relativeDirPath,
13222
+ relativeDirPath: rootDirPath,
13252
13223
  relativeFilePath: paths.root.relativeFilePath,
13253
13224
  frontmatter: {},
13254
13225
  body: fileContent2.trim(),
@@ -15605,35 +15576,62 @@ var RulesProcessor = class extends FeatureProcessor {
15605
15576
  const settablePaths = factory.class.getSettablePaths({
15606
15577
  global: this.global
15607
15578
  });
15579
+ const resolveRelativeDirPath = (filePath) => {
15580
+ const dirName = dirname3(relative5(this.baseDir, filePath));
15581
+ return dirName === "" ? "." : dirName;
15582
+ };
15583
+ const findFilesWithFallback = async (primaryGlob, alternativeRoots, buildAltGlob) => {
15584
+ const primaryFilePaths = await findFilesByGlobs(primaryGlob);
15585
+ if (primaryFilePaths.length > 0) {
15586
+ return primaryFilePaths;
15587
+ }
15588
+ if (alternativeRoots) {
15589
+ return findFilesByGlobs(alternativeRoots.map(buildAltGlob));
15590
+ }
15591
+ return [];
15592
+ };
15608
15593
  const rootToolRules = await (async () => {
15609
15594
  if (!settablePaths.root) {
15610
15595
  return [];
15611
15596
  }
15612
- const rootFilePaths = await findFilesByGlobs(
15597
+ const uniqueRootFilePaths = await findFilesWithFallback(
15613
15598
  join113(
15614
15599
  this.baseDir,
15615
15600
  settablePaths.root.relativeDirPath ?? ".",
15616
15601
  settablePaths.root.relativeFilePath
15617
- )
15602
+ ),
15603
+ settablePaths.alternativeRoots,
15604
+ (alt) => join113(this.baseDir, alt.relativeDirPath, alt.relativeFilePath)
15618
15605
  );
15619
15606
  if (forDeletion) {
15620
- return rootFilePaths.map(
15621
- (filePath) => factory.class.forDeletion({
15607
+ return uniqueRootFilePaths.map((filePath) => {
15608
+ const relativeDirPath = resolveRelativeDirPath(filePath);
15609
+ checkPathTraversal({
15610
+ relativePath: relativeDirPath,
15611
+ intendedRootDir: this.baseDir
15612
+ });
15613
+ return factory.class.forDeletion({
15622
15614
  baseDir: this.baseDir,
15623
- relativeDirPath: settablePaths.root?.relativeDirPath ?? ".",
15615
+ relativeDirPath,
15624
15616
  relativeFilePath: basename10(filePath),
15625
15617
  global: this.global
15626
- })
15627
- ).filter((rule) => rule.isDeletable());
15618
+ });
15619
+ }).filter((rule) => rule.isDeletable());
15628
15620
  }
15629
15621
  return await Promise.all(
15630
- rootFilePaths.map(
15631
- (filePath) => factory.class.fromFile({
15622
+ uniqueRootFilePaths.map((filePath) => {
15623
+ const relativeDirPath = resolveRelativeDirPath(filePath);
15624
+ checkPathTraversal({
15625
+ relativePath: relativeDirPath,
15626
+ intendedRootDir: this.baseDir
15627
+ });
15628
+ return factory.class.fromFile({
15632
15629
  baseDir: this.baseDir,
15633
15630
  relativeFilePath: basename10(filePath),
15631
+ relativeDirPath,
15634
15632
  global: this.global
15635
- })
15636
- )
15633
+ });
15634
+ })
15637
15635
  );
15638
15636
  })();
15639
15637
  logger.debug(`Found ${rootToolRules.length} root tool rule files`);
@@ -15647,17 +15645,24 @@ var RulesProcessor = class extends FeatureProcessor {
15647
15645
  if (!settablePaths.root) {
15648
15646
  return [];
15649
15647
  }
15650
- const localRootFilePaths = await findFilesByGlobs(
15651
- join113(this.baseDir, settablePaths.root.relativeDirPath ?? ".", "CLAUDE.local.md")
15648
+ const uniqueLocalRootFilePaths = await findFilesWithFallback(
15649
+ join113(this.baseDir, settablePaths.root.relativeDirPath ?? ".", "CLAUDE.local.md"),
15650
+ settablePaths.alternativeRoots,
15651
+ (alt) => join113(this.baseDir, alt.relativeDirPath, "CLAUDE.local.md")
15652
15652
  );
15653
- return localRootFilePaths.map(
15654
- (filePath) => factory.class.forDeletion({
15653
+ return uniqueLocalRootFilePaths.map((filePath) => {
15654
+ const relativeDirPath = resolveRelativeDirPath(filePath);
15655
+ checkPathTraversal({
15656
+ relativePath: relativeDirPath,
15657
+ intendedRootDir: this.baseDir
15658
+ });
15659
+ return factory.class.forDeletion({
15655
15660
  baseDir: this.baseDir,
15656
- relativeDirPath: settablePaths.root?.relativeDirPath ?? ".",
15661
+ relativeDirPath,
15657
15662
  relativeFilePath: basename10(filePath),
15658
15663
  global: this.global
15659
- })
15660
- ).filter((rule) => rule.isDeletable());
15664
+ });
15665
+ }).filter((rule) => rule.isDeletable());
15661
15666
  })();
15662
15667
  logger.debug(`Found ${localRootToolRules.length} local root tool rule files for deletion`);
15663
15668
  const nonRootToolRules = await (async () => {