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.
@@ -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,104 @@ 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 command = def.command !== void 0 && def.command !== null && !def.command.startsWith("$") ? `${converterConfig.projectDirVar}/${def.command.replace(/^\.\//, "")}` : def.command;
3242
+ return {
3243
+ type: def.type ?? "command",
3244
+ ...command !== void 0 && command !== null && { command },
3245
+ ...def.timeout !== void 0 && def.timeout !== null && { timeout: def.timeout },
3246
+ ...def.prompt !== void 0 && def.prompt !== null && { prompt: def.prompt }
3247
+ };
3248
+ });
3249
+ entries.push(matcherKey ? { matcher: matcherKey, hooks } : { hooks });
3250
+ }
3251
+ result[toolEventName] = entries;
3252
+ }
3253
+ return result;
3254
+ }
3255
+ function toolHooksToCanonical({
3256
+ hooks,
3257
+ converterConfig
3258
+ }) {
3259
+ if (hooks === null || hooks === void 0 || typeof hooks !== "object") {
3260
+ return {};
3261
+ }
3262
+ const canonical = {};
3263
+ for (const [toolEventName, matcherEntries] of Object.entries(hooks)) {
3264
+ const eventName = converterConfig.toolToCanonicalEventNames[toolEventName] ?? toolEventName;
3265
+ if (!Array.isArray(matcherEntries)) continue;
3266
+ const defs = [];
3267
+ for (const rawEntry of matcherEntries) {
3268
+ if (!isToolMatcherEntry(rawEntry)) continue;
3269
+ const hookDefs = rawEntry.hooks ?? [];
3270
+ for (const h of hookDefs) {
3271
+ const cmd = typeof h.command === "string" ? h.command : void 0;
3272
+ const command = typeof cmd === "string" && cmd.includes(`${converterConfig.projectDirVar}/`) ? cmd.replace(
3273
+ new RegExp(
3274
+ `^${converterConfig.projectDirVar.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\/?`
3275
+ ),
3276
+ "./"
3277
+ ) : cmd;
3278
+ const hookType = h.type === "command" || h.type === "prompt" ? h.type : "command";
3279
+ const timeout = typeof h.timeout === "number" ? h.timeout : void 0;
3280
+ const prompt = typeof h.prompt === "string" ? h.prompt : void 0;
3281
+ defs.push({
3282
+ type: hookType,
3283
+ ...command !== void 0 && command !== null && { command },
3284
+ ...timeout !== void 0 && timeout !== null && { timeout },
3285
+ ...prompt !== void 0 && prompt !== null && { prompt },
3286
+ ...rawEntry.matcher !== void 0 && rawEntry.matcher !== null && rawEntry.matcher !== "" && { matcher: rawEntry.matcher }
3287
+ });
3288
+ }
3289
+ }
3290
+ if (defs.length > 0) {
3291
+ canonical[eventName] = defs;
3292
+ }
3293
+ }
3294
+ return canonical;
3295
+ }
3296
+
3202
3297
  // src/types/tool-file.ts
3203
3298
  var ToolFile = class extends AiFile {
3204
3299
  };
@@ -3289,91 +3384,12 @@ var ToolHooks = class extends ToolFile {
3289
3384
  };
3290
3385
 
3291
3386
  // 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
- }
3387
+ var CLAUDE_CONVERTER_CONFIG = {
3388
+ supportedEvents: CLAUDE_HOOK_EVENTS,
3389
+ canonicalToToolEventNames: CANONICAL_TO_CLAUDE_EVENT_NAMES,
3390
+ toolToCanonicalEventNames: CLAUDE_TO_CANONICAL_EVENT_NAMES,
3391
+ projectDirVar: "$CLAUDE_PROJECT_DIR"
3392
+ };
3377
3393
  var ClaudecodeHooks = class _ClaudecodeHooks extends ToolHooks {
3378
3394
  constructor(params) {
3379
3395
  super({
@@ -3425,7 +3441,11 @@ var ClaudecodeHooks = class _ClaudecodeHooks extends ToolHooks {
3425
3441
  );
3426
3442
  }
3427
3443
  const config = rulesyncHooks.getJson();
3428
- const claudeHooks = canonicalToClaudeHooks(config);
3444
+ const claudeHooks = canonicalToToolHooks({
3445
+ config,
3446
+ toolOverrideHooks: config.claudecode?.hooks,
3447
+ converterConfig: CLAUDE_CONVERTER_CONFIG
3448
+ });
3429
3449
  const merged = { ...settings, hooks: claudeHooks };
3430
3450
  const fileContent = JSON.stringify(merged, null, 2);
3431
3451
  return new _ClaudecodeHooks({
@@ -3448,7 +3468,10 @@ var ClaudecodeHooks = class _ClaudecodeHooks extends ToolHooks {
3448
3468
  }
3449
3469
  );
3450
3470
  }
3451
- const hooks = claudeHooksToCanonical(settings.hooks);
3471
+ const hooks = toolHooksToCanonical({
3472
+ hooks: settings.hooks,
3473
+ converterConfig: CLAUDE_CONVERTER_CONFIG
3474
+ });
3452
3475
  return this.toRulesyncHooksDefault({
3453
3476
  fileContent: JSON.stringify({ version: 1, hooks }, null, 2)
3454
3477
  });
@@ -3757,91 +3780,12 @@ var CursorHooks = class _CursorHooks extends ToolHooks {
3757
3780
 
3758
3781
  // src/features/hooks/factorydroid-hooks.ts
3759
3782
  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
- }
3783
+ var FACTORYDROID_CONVERTER_CONFIG = {
3784
+ supportedEvents: FACTORYDROID_HOOK_EVENTS,
3785
+ canonicalToToolEventNames: CANONICAL_TO_FACTORYDROID_EVENT_NAMES,
3786
+ toolToCanonicalEventNames: FACTORYDROID_TO_CANONICAL_EVENT_NAMES,
3787
+ projectDirVar: "$FACTORY_PROJECT_DIR"
3788
+ };
3845
3789
  var FactorydroidHooks = class _FactorydroidHooks extends ToolHooks {
3846
3790
  constructor(params) {
3847
3791
  super({
@@ -3893,7 +3837,11 @@ var FactorydroidHooks = class _FactorydroidHooks extends ToolHooks {
3893
3837
  );
3894
3838
  }
3895
3839
  const config = rulesyncHooks.getJson();
3896
- const factorydroidHooks = canonicalToFactorydroidHooks(config);
3840
+ const factorydroidHooks = canonicalToToolHooks({
3841
+ config,
3842
+ toolOverrideHooks: config.factorydroid?.hooks,
3843
+ converterConfig: FACTORYDROID_CONVERTER_CONFIG
3844
+ });
3897
3845
  const merged = { ...settings, hooks: factorydroidHooks };
3898
3846
  const fileContent = JSON.stringify(merged, null, 2);
3899
3847
  return new _FactorydroidHooks({
@@ -3916,7 +3864,10 @@ var FactorydroidHooks = class _FactorydroidHooks extends ToolHooks {
3916
3864
  }
3917
3865
  );
3918
3866
  }
3919
- const hooks = factorydroidHooksToCanonical(settings.hooks);
3867
+ const hooks = toolHooksToCanonical({
3868
+ hooks: settings.hooks,
3869
+ converterConfig: FACTORYDROID_CONVERTER_CONFIG
3870
+ });
3920
3871
  return this.toRulesyncHooksDefault({
3921
3872
  fileContent: JSON.stringify({ version: 1, hooks }, null, 2)
3922
3873
  });
@@ -7444,7 +7395,7 @@ var McpProcessor = class extends FeatureProcessor {
7444
7395
  };
7445
7396
 
7446
7397
  // src/features/rules/rules-processor.ts
7447
- import { basename as basename10, join as join113, relative as relative5 } from "path";
7398
+ import { basename as basename10, dirname as dirname3, join as join113, relative as relative5 } from "path";
7448
7399
  import { encode } from "@toon-format/toon";
7449
7400
  import { z as z54 } from "zod/mini";
7450
7401
 
@@ -10713,7 +10664,7 @@ var ToolSubagent = class extends ToolFile {
10713
10664
  // src/features/subagents/simulated-subagent.ts
10714
10665
  var SimulatedSubagentFrontmatterSchema = z39.object({
10715
10666
  name: z39.string(),
10716
- description: z39.string()
10667
+ description: z39.optional(z39.string())
10717
10668
  });
10718
10669
  var SimulatedSubagent = class extends ToolSubagent {
10719
10670
  frontmatter;
@@ -10937,7 +10888,7 @@ import { z as z40 } from "zod/mini";
10937
10888
  var RulesyncSubagentFrontmatterSchema = z40.looseObject({
10938
10889
  targets: z40._default(RulesyncTargetsSchema, ["*"]),
10939
10890
  name: z40.string(),
10940
- description: z40.string()
10891
+ description: z40.optional(z40.string())
10941
10892
  });
10942
10893
  var RulesyncSubagent = class _RulesyncSubagent extends RulesyncFile {
10943
10894
  frontmatter;
@@ -11008,7 +10959,7 @@ var RulesyncSubagent = class _RulesyncSubagent extends RulesyncFile {
11008
10959
  // src/features/subagents/claudecode-subagent.ts
11009
10960
  var ClaudecodeSubagentFrontmatterSchema = z41.looseObject({
11010
10961
  name: z41.string(),
11011
- description: z41.string(),
10962
+ description: z41.optional(z41.string()),
11012
10963
  model: z41.optional(z41.string()),
11013
10964
  tools: z41.optional(z41.union([z41.string(), z41.array(z41.string())])),
11014
10965
  permissionMode: z41.optional(z41.string()),
@@ -11218,7 +11169,7 @@ var CodexCliSubagent = class _CodexCliSubagent extends ToolSubagent {
11218
11169
  const rulesyncFrontmatter = {
11219
11170
  targets: ["codexcli"],
11220
11171
  name,
11221
- description: description ?? "",
11172
+ description,
11222
11173
  // Only include codexcli section if there are fields
11223
11174
  ...Object.keys(codexcliSection).length > 0 && { codexcli: codexcliSection }
11224
11175
  };
@@ -11331,7 +11282,7 @@ import { z as z43 } from "zod/mini";
11331
11282
  var REQUIRED_TOOL = "agent/runSubagent";
11332
11283
  var CopilotSubagentFrontmatterSchema = z43.looseObject({
11333
11284
  name: z43.string(),
11334
- description: z43.string(),
11285
+ description: z43.optional(z43.string()),
11335
11286
  tools: z43.optional(z43.union([z43.string(), z43.array(z43.string())]))
11336
11287
  });
11337
11288
  var normalizeTools = (tools) => {
@@ -11496,7 +11447,7 @@ import { join as join85 } from "path";
11496
11447
  import { z as z44 } from "zod/mini";
11497
11448
  var CursorSubagentFrontmatterSchema = z44.looseObject({
11498
11449
  name: z44.string(),
11499
- description: z44.string()
11450
+ description: z44.optional(z44.string())
11500
11451
  });
11501
11452
  var CursorSubagent = class _CursorSubagent extends ToolSubagent {
11502
11453
  frontmatter;
@@ -11701,7 +11652,7 @@ var KiroSubagent = class _KiroSubagent extends ToolSubagent {
11701
11652
  const rulesyncFrontmatter = {
11702
11653
  targets: ["kiro"],
11703
11654
  name,
11704
- description: description ?? "",
11655
+ description: description ?? void 0,
11705
11656
  // Only include kiro section if there are fields
11706
11657
  ...Object.keys(kiroSection).length > 0 && { kiro: kiroSection }
11707
11658
  };
@@ -11812,7 +11763,7 @@ var KiroSubagent = class _KiroSubagent extends ToolSubagent {
11812
11763
  import { basename as basename8, join as join87 } from "path";
11813
11764
  import { z as z46 } from "zod/mini";
11814
11765
  var OpenCodeSubagentFrontmatterSchema = z46.looseObject({
11815
- description: z46.string(),
11766
+ description: z46.optional(z46.string()),
11816
11767
  mode: z46._default(z46.string(), "subagent"),
11817
11768
  name: z46.optional(z46.string())
11818
11769
  });
@@ -12642,7 +12593,7 @@ var globStrategy = {
12642
12593
  },
12643
12594
  exportRulesyncData: ({ description, ...frontmatter }) => ({
12644
12595
  globs: parseGlobsString(frontmatter.globs),
12645
- description: description || "",
12596
+ description,
12646
12597
  antigravity: frontmatter
12647
12598
  })
12648
12599
  };
@@ -12654,7 +12605,7 @@ var manualStrategy = {
12654
12605
  }),
12655
12606
  exportRulesyncData: ({ description, ...frontmatter }) => ({
12656
12607
  globs: [],
12657
- description: description || "",
12608
+ description,
12658
12609
  antigravity: frontmatter
12659
12610
  })
12660
12611
  };
@@ -12666,7 +12617,7 @@ var alwaysOnStrategy = {
12666
12617
  }),
12667
12618
  exportRulesyncData: ({ description, ...frontmatter }) => ({
12668
12619
  globs: ["**/*"],
12669
- description: description || "",
12620
+ description,
12670
12621
  antigravity: frontmatter
12671
12622
  })
12672
12623
  };
@@ -12679,7 +12630,7 @@ var modelDecisionStrategy = {
12679
12630
  }),
12680
12631
  exportRulesyncData: ({ description, ...frontmatter }) => ({
12681
12632
  globs: [],
12682
- description: description || "",
12633
+ description,
12683
12634
  antigravity: frontmatter
12684
12635
  })
12685
12636
  };
@@ -12694,7 +12645,7 @@ var unknownStrategy = {
12694
12645
  },
12695
12646
  exportRulesyncData: ({ description, ...frontmatter }) => ({
12696
12647
  globs: frontmatter.globs ? parseGlobsString(frontmatter.globs) : ["**/*"],
12697
- description: description || "",
12648
+ description,
12698
12649
  antigravity: frontmatter
12699
12650
  })
12700
12651
  };
@@ -12716,7 +12667,7 @@ var inferenceStrategy = {
12716
12667
  },
12717
12668
  exportRulesyncData: ({ description, ...frontmatter }) => ({
12718
12669
  globs: frontmatter.globs ? parseGlobsString(frontmatter.globs) : ["**/*"],
12719
- description: description || "",
12670
+ description,
12720
12671
  antigravity: frontmatter
12721
12672
  })
12722
12673
  };
@@ -12846,7 +12797,6 @@ var AntigravityRule = class _AntigravityRule extends ToolRule {
12846
12797
  const strategy = STRATEGIES.find((s) => s.canHandle(this.frontmatter.trigger));
12847
12798
  let rulesyncData = {
12848
12799
  globs: [],
12849
- description: "",
12850
12800
  antigravity: this.frontmatter
12851
12801
  };
12852
12802
  if (strategy) {
@@ -12916,7 +12866,6 @@ var AugmentcodeLegacyRule = class _AugmentcodeLegacyRule extends ToolRule {
12916
12866
  const rulesyncFrontmatter = {
12917
12867
  root: this.isRoot(),
12918
12868
  targets: ["*"],
12919
- description: "",
12920
12869
  globs: this.isRoot() ? ["**/*"] : []
12921
12870
  };
12922
12871
  return new RulesyncRule({
@@ -13091,6 +13040,12 @@ var ClaudecodeLegacyRule = class _ClaudecodeLegacyRule extends ToolRule {
13091
13040
  relativeDirPath: ".",
13092
13041
  relativeFilePath: "CLAUDE.md"
13093
13042
  },
13043
+ alternativeRoots: [
13044
+ {
13045
+ relativeDirPath: ".claude",
13046
+ relativeFilePath: "CLAUDE.md"
13047
+ }
13048
+ ],
13094
13049
  nonRoot: {
13095
13050
  relativeDirPath: buildToolPath(".claude", "memories", excludeToolDir)
13096
13051
  }
@@ -13100,18 +13055,19 @@ var ClaudecodeLegacyRule = class _ClaudecodeLegacyRule extends ToolRule {
13100
13055
  baseDir = process.cwd(),
13101
13056
  relativeFilePath,
13102
13057
  validate = true,
13103
- global = false
13058
+ global = false,
13059
+ relativeDirPath: overrideDirPath
13104
13060
  }) {
13105
13061
  const paths = this.getSettablePaths({ global });
13106
13062
  const isRoot = relativeFilePath === paths.root.relativeFilePath;
13107
13063
  if (isRoot) {
13108
- const relativePath2 = paths.root.relativeFilePath;
13064
+ const rootDirPath = overrideDirPath ?? paths.root.relativeDirPath;
13109
13065
  const fileContent2 = await readFileContent(
13110
- join95(baseDir, paths.root.relativeDirPath, relativePath2)
13066
+ join95(baseDir, rootDirPath, paths.root.relativeFilePath)
13111
13067
  );
13112
13068
  return new _ClaudecodeLegacyRule({
13113
13069
  baseDir,
13114
- relativeDirPath: paths.root.relativeDirPath,
13070
+ relativeDirPath: rootDirPath,
13115
13071
  relativeFilePath: paths.root.relativeFilePath,
13116
13072
  fileContent: fileContent2,
13117
13073
  validate,
@@ -13206,6 +13162,12 @@ var ClaudecodeRule = class _ClaudecodeRule extends ToolRule {
13206
13162
  relativeDirPath: ".",
13207
13163
  relativeFilePath: "CLAUDE.md"
13208
13164
  },
13165
+ alternativeRoots: [
13166
+ {
13167
+ relativeDirPath: ".claude",
13168
+ relativeFilePath: "CLAUDE.md"
13169
+ }
13170
+ ],
13209
13171
  nonRoot: {
13210
13172
  relativeDirPath: buildToolPath(".claude", "rules", excludeToolDir)
13211
13173
  }
@@ -13238,17 +13200,19 @@ var ClaudecodeRule = class _ClaudecodeRule extends ToolRule {
13238
13200
  baseDir = process.cwd(),
13239
13201
  relativeFilePath,
13240
13202
  validate = true,
13241
- global = false
13203
+ global = false,
13204
+ relativeDirPath: overrideDirPath
13242
13205
  }) {
13243
13206
  const paths = this.getSettablePaths({ global });
13244
13207
  const isRoot = relativeFilePath === paths.root.relativeFilePath;
13245
13208
  if (isRoot) {
13209
+ const rootDirPath = overrideDirPath ?? paths.root.relativeDirPath;
13246
13210
  const fileContent2 = await readFileContent(
13247
- join96(baseDir, paths.root.relativeDirPath, paths.root.relativeFilePath)
13211
+ join96(baseDir, rootDirPath, paths.root.relativeFilePath)
13248
13212
  );
13249
13213
  return new _ClaudecodeRule({
13250
13214
  baseDir,
13251
- relativeDirPath: paths.root.relativeDirPath,
13215
+ relativeDirPath: rootDirPath,
13252
13216
  relativeFilePath: paths.root.relativeFilePath,
13253
13217
  frontmatter: {},
13254
13218
  body: fileContent2.trim(),
@@ -15605,35 +15569,62 @@ var RulesProcessor = class extends FeatureProcessor {
15605
15569
  const settablePaths = factory.class.getSettablePaths({
15606
15570
  global: this.global
15607
15571
  });
15572
+ const resolveRelativeDirPath = (filePath) => {
15573
+ const dirName = dirname3(relative5(this.baseDir, filePath));
15574
+ return dirName === "" ? "." : dirName;
15575
+ };
15576
+ const findFilesWithFallback = async (primaryGlob, alternativeRoots, buildAltGlob) => {
15577
+ const primaryFilePaths = await findFilesByGlobs(primaryGlob);
15578
+ if (primaryFilePaths.length > 0) {
15579
+ return primaryFilePaths;
15580
+ }
15581
+ if (alternativeRoots) {
15582
+ return findFilesByGlobs(alternativeRoots.map(buildAltGlob));
15583
+ }
15584
+ return [];
15585
+ };
15608
15586
  const rootToolRules = await (async () => {
15609
15587
  if (!settablePaths.root) {
15610
15588
  return [];
15611
15589
  }
15612
- const rootFilePaths = await findFilesByGlobs(
15590
+ const uniqueRootFilePaths = await findFilesWithFallback(
15613
15591
  join113(
15614
15592
  this.baseDir,
15615
15593
  settablePaths.root.relativeDirPath ?? ".",
15616
15594
  settablePaths.root.relativeFilePath
15617
- )
15595
+ ),
15596
+ settablePaths.alternativeRoots,
15597
+ (alt) => join113(this.baseDir, alt.relativeDirPath, alt.relativeFilePath)
15618
15598
  );
15619
15599
  if (forDeletion) {
15620
- return rootFilePaths.map(
15621
- (filePath) => factory.class.forDeletion({
15600
+ return uniqueRootFilePaths.map((filePath) => {
15601
+ const relativeDirPath = resolveRelativeDirPath(filePath);
15602
+ checkPathTraversal({
15603
+ relativePath: relativeDirPath,
15604
+ intendedRootDir: this.baseDir
15605
+ });
15606
+ return factory.class.forDeletion({
15622
15607
  baseDir: this.baseDir,
15623
- relativeDirPath: settablePaths.root?.relativeDirPath ?? ".",
15608
+ relativeDirPath,
15624
15609
  relativeFilePath: basename10(filePath),
15625
15610
  global: this.global
15626
- })
15627
- ).filter((rule) => rule.isDeletable());
15611
+ });
15612
+ }).filter((rule) => rule.isDeletable());
15628
15613
  }
15629
15614
  return await Promise.all(
15630
- rootFilePaths.map(
15631
- (filePath) => factory.class.fromFile({
15615
+ uniqueRootFilePaths.map((filePath) => {
15616
+ const relativeDirPath = resolveRelativeDirPath(filePath);
15617
+ checkPathTraversal({
15618
+ relativePath: relativeDirPath,
15619
+ intendedRootDir: this.baseDir
15620
+ });
15621
+ return factory.class.fromFile({
15632
15622
  baseDir: this.baseDir,
15633
15623
  relativeFilePath: basename10(filePath),
15624
+ relativeDirPath,
15634
15625
  global: this.global
15635
- })
15636
- )
15626
+ });
15627
+ })
15637
15628
  );
15638
15629
  })();
15639
15630
  logger.debug(`Found ${rootToolRules.length} root tool rule files`);
@@ -15647,17 +15638,24 @@ var RulesProcessor = class extends FeatureProcessor {
15647
15638
  if (!settablePaths.root) {
15648
15639
  return [];
15649
15640
  }
15650
- const localRootFilePaths = await findFilesByGlobs(
15651
- join113(this.baseDir, settablePaths.root.relativeDirPath ?? ".", "CLAUDE.local.md")
15641
+ const uniqueLocalRootFilePaths = await findFilesWithFallback(
15642
+ join113(this.baseDir, settablePaths.root.relativeDirPath ?? ".", "CLAUDE.local.md"),
15643
+ settablePaths.alternativeRoots,
15644
+ (alt) => join113(this.baseDir, alt.relativeDirPath, "CLAUDE.local.md")
15652
15645
  );
15653
- return localRootFilePaths.map(
15654
- (filePath) => factory.class.forDeletion({
15646
+ return uniqueLocalRootFilePaths.map((filePath) => {
15647
+ const relativeDirPath = resolveRelativeDirPath(filePath);
15648
+ checkPathTraversal({
15649
+ relativePath: relativeDirPath,
15650
+ intendedRootDir: this.baseDir
15651
+ });
15652
+ return factory.class.forDeletion({
15655
15653
  baseDir: this.baseDir,
15656
- relativeDirPath: settablePaths.root?.relativeDirPath ?? ".",
15654
+ relativeDirPath,
15657
15655
  relativeFilePath: basename10(filePath),
15658
15656
  global: this.global
15659
- })
15660
- ).filter((rule) => rule.isDeletable());
15657
+ });
15658
+ }).filter((rule) => rule.isDeletable());
15661
15659
  })();
15662
15660
  logger.debug(`Found ${localRootToolRules.length} local root tool rule files for deletion`);
15663
15661
  const nonRootToolRules = await (async () => {