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.
package/dist/index.cjs CHANGED
@@ -934,7 +934,7 @@ var ToolCommand = class extends AiFile {
934
934
 
935
935
  // src/features/commands/simulated-command.ts
936
936
  var SimulatedCommandFrontmatterSchema = import_mini4.z.object({
937
- description: import_mini4.z.string()
937
+ description: import_mini4.z.optional(import_mini4.z.string())
938
938
  });
939
939
  var SimulatedCommand = class _SimulatedCommand extends ToolCommand {
940
940
  frontmatter;
@@ -1121,7 +1121,7 @@ var RulesyncFile = class extends AiFile {
1121
1121
  // src/features/commands/rulesync-command.ts
1122
1122
  var RulesyncCommandFrontmatterSchema = import_mini5.z.looseObject({
1123
1123
  targets: import_mini5.z._default(RulesyncTargetsSchema, ["*"]),
1124
- description: import_mini5.z.string()
1124
+ description: import_mini5.z.optional(import_mini5.z.string())
1125
1125
  });
1126
1126
  var RulesyncCommand = class _RulesyncCommand extends RulesyncFile {
1127
1127
  frontmatter;
@@ -1209,7 +1209,7 @@ var AntigravityWorkflowFrontmatterSchema = import_mini6.z.looseObject({
1209
1209
  turbo: import_mini6.z.optional(import_mini6.z.boolean())
1210
1210
  });
1211
1211
  var AntigravityCommandFrontmatterSchema = import_mini6.z.looseObject({
1212
- description: import_mini6.z.string(),
1212
+ description: import_mini6.z.optional(import_mini6.z.string()),
1213
1213
  // Support for workflow-specific configuration
1214
1214
  ...AntigravityWorkflowFrontmatterSchema.shape
1215
1215
  });
@@ -1382,7 +1382,7 @@ ${body}${turboDirective}`;
1382
1382
  var import_node_path9 = require("path");
1383
1383
  var import_mini7 = require("zod/mini");
1384
1384
  var ClaudecodeCommandFrontmatterSchema = import_mini7.z.looseObject({
1385
- description: import_mini7.z.string(),
1385
+ description: import_mini7.z.optional(import_mini7.z.string()),
1386
1386
  "allowed-tools": import_mini7.z.optional(import_mini7.z.union([import_mini7.z.string(), import_mini7.z.array(import_mini7.z.string())])),
1387
1387
  "argument-hint": import_mini7.z.optional(import_mini7.z.string()),
1388
1388
  model: import_mini7.z.optional(import_mini7.z.string()),
@@ -1537,8 +1537,7 @@ var ClineCommand = class _ClineCommand extends ToolCommand {
1537
1537
  }
1538
1538
  toRulesyncCommand() {
1539
1539
  const rulesyncFrontmatter = {
1540
- targets: ["*"],
1541
- description: ""
1540
+ targets: ["*"]
1542
1541
  };
1543
1542
  return new RulesyncCommand({
1544
1543
  baseDir: process.cwd(),
@@ -1623,8 +1622,7 @@ var CodexcliCommand = class _CodexcliCommand extends ToolCommand {
1623
1622
  }
1624
1623
  toRulesyncCommand() {
1625
1624
  const rulesyncFrontmatter = {
1626
- targets: ["*"],
1627
- description: ""
1625
+ targets: ["*"]
1628
1626
  };
1629
1627
  return new RulesyncCommand({
1630
1628
  baseDir: ".",
@@ -1702,7 +1700,7 @@ var import_node_path12 = require("path");
1702
1700
  var import_mini8 = require("zod/mini");
1703
1701
  var CopilotCommandFrontmatterSchema = import_mini8.z.looseObject({
1704
1702
  mode: import_mini8.z.optional(import_mini8.z.string()),
1705
- description: import_mini8.z.string()
1703
+ description: import_mini8.z.optional(import_mini8.z.string())
1706
1704
  });
1707
1705
  var CopilotCommand = class _CopilotCommand extends ToolCommand {
1708
1706
  frontmatter;
@@ -1885,7 +1883,7 @@ var CursorCommand = class _CursorCommand extends ToolCommand {
1885
1883
  return this.frontmatter;
1886
1884
  }
1887
1885
  toRulesyncCommand() {
1888
- const { description = "", ...restFields } = this.frontmatter;
1886
+ const { description, ...restFields } = this.frontmatter;
1889
1887
  const rulesyncFrontmatter = {
1890
1888
  targets: ["*"],
1891
1889
  description,
@@ -2079,7 +2077,7 @@ var GeminiCliCommand = class _GeminiCliCommand extends ToolCommand {
2079
2077
  }
2080
2078
  return {
2081
2079
  ...result.data,
2082
- description: result.data.description || ""
2080
+ description: result.data.description
2083
2081
  };
2084
2082
  } catch (error) {
2085
2083
  throw new Error(
@@ -2101,7 +2099,7 @@ var GeminiCliCommand = class _GeminiCliCommand extends ToolCommand {
2101
2099
  const { description, prompt: _prompt, ...restFields } = this.frontmatter;
2102
2100
  const rulesyncFrontmatter = {
2103
2101
  targets: ["geminicli"],
2104
- description: description ?? "",
2102
+ description,
2105
2103
  // Preserve extra fields in geminicli section (excluding prompt which is the body)
2106
2104
  ...Object.keys(restFields).length > 0 && { geminicli: restFields }
2107
2105
  };
@@ -2130,8 +2128,9 @@ var GeminiCliCommand = class _GeminiCliCommand extends ToolCommand {
2130
2128
  prompt: rulesyncCommand.getBody(),
2131
2129
  ...geminicliFields
2132
2130
  };
2133
- const tomlContent = `description = "${geminiFrontmatter.description}"
2134
- prompt = """
2131
+ const descriptionLine = geminiFrontmatter.description !== void 0 ? `description = "${geminiFrontmatter.description}"
2132
+ ` : "";
2133
+ const tomlContent = `${descriptionLine}prompt = """
2135
2134
  ${geminiFrontmatter.prompt}
2136
2135
  """`;
2137
2136
  const paths = this.getSettablePaths({ global });
@@ -2201,8 +2200,7 @@ var KiloCommand = class _KiloCommand extends ToolCommand {
2201
2200
  }
2202
2201
  toRulesyncCommand() {
2203
2202
  const rulesyncFrontmatter = {
2204
- targets: ["*"],
2205
- description: ""
2203
+ targets: ["*"]
2206
2204
  };
2207
2205
  return new RulesyncCommand({
2208
2206
  baseDir: process.cwd(),
@@ -2282,8 +2280,7 @@ var KiroCommand = class _KiroCommand extends ToolCommand {
2282
2280
  }
2283
2281
  toRulesyncCommand() {
2284
2282
  const rulesyncFrontmatter = {
2285
- targets: ["*"],
2286
- description: ""
2283
+ targets: ["*"]
2287
2284
  };
2288
2285
  return new RulesyncCommand({
2289
2286
  baseDir: process.cwd(),
@@ -2357,7 +2354,7 @@ var KiroCommand = class _KiroCommand extends ToolCommand {
2357
2354
  var import_node_path18 = require("path");
2358
2355
  var import_mini11 = require("zod/mini");
2359
2356
  var OpenCodeCommandFrontmatterSchema = import_mini11.z.looseObject({
2360
- description: import_mini11.z.string(),
2357
+ description: import_mini11.z.optional(import_mini11.z.string()),
2361
2358
  agent: (0, import_mini11.optional)(import_mini11.z.string()),
2362
2359
  subtask: (0, import_mini11.optional)(import_mini11.z.boolean()),
2363
2360
  model: (0, import_mini11.optional)(import_mini11.z.string())
@@ -2497,7 +2494,7 @@ var OpenCodeCommand = class _OpenCodeCommand extends ToolCommand {
2497
2494
  var import_node_path19 = require("path");
2498
2495
  var import_mini12 = require("zod/mini");
2499
2496
  var RooCommandFrontmatterSchema = import_mini12.z.looseObject({
2500
- description: import_mini12.z.string(),
2497
+ description: import_mini12.z.optional(import_mini12.z.string()),
2501
2498
  "argument-hint": (0, import_mini12.optional)(import_mini12.z.string())
2502
2499
  });
2503
2500
  var RooCommand = class _RooCommand extends ToolCommand {
@@ -3036,7 +3033,7 @@ var HookDefinitionSchema = import_mini14.z.looseObject({
3036
3033
  type: import_mini14.z.optional(import_mini14.z.enum(["command", "prompt"])),
3037
3034
  timeout: import_mini14.z.optional(import_mini14.z.number()),
3038
3035
  matcher: import_mini14.z.optional(safeString),
3039
- prompt: import_mini14.z.optional(import_mini14.z.string()),
3036
+ prompt: import_mini14.z.optional(safeString),
3040
3037
  loop_limit: import_mini14.z.optional(import_mini14.z.nullable(import_mini14.z.number())),
3041
3038
  name: import_mini14.z.optional(safeString),
3042
3039
  description: import_mini14.z.optional(safeString)
@@ -3088,7 +3085,7 @@ var OPENCODE_HOOK_EVENTS = [
3088
3085
  var COPILOT_HOOK_EVENTS = [
3089
3086
  "sessionStart",
3090
3087
  "sessionEnd",
3091
- "afterSubmitPrompt",
3088
+ "beforeSubmitPrompt",
3092
3089
  "preToolUse",
3093
3090
  "postToolUse",
3094
3091
  "afterError"
@@ -3199,7 +3196,7 @@ var CANONICAL_TO_OPENCODE_EVENT_NAMES = {
3199
3196
  var CANONICAL_TO_COPILOT_EVENT_NAMES = {
3200
3197
  sessionStart: "sessionStart",
3201
3198
  sessionEnd: "sessionEnd",
3202
- afterSubmitPrompt: "userPromptSubmitted",
3199
+ beforeSubmitPrompt: "userPromptSubmitted",
3203
3200
  preToolUse: "preToolUse",
3204
3201
  postToolUse: "postToolUse",
3205
3202
  afterError: "errorOccurred"
@@ -3227,6 +3224,104 @@ var GEMINICLI_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(
3227
3224
  // src/features/hooks/claudecode-hooks.ts
3228
3225
  var import_node_path22 = require("path");
3229
3226
 
3227
+ // src/features/hooks/tool-hooks-converter.ts
3228
+ function isToolMatcherEntry(x) {
3229
+ if (x === null || typeof x !== "object") {
3230
+ return false;
3231
+ }
3232
+ if ("matcher" in x && typeof x.matcher !== "string") {
3233
+ return false;
3234
+ }
3235
+ if ("hooks" in x && !Array.isArray(x.hooks)) {
3236
+ return false;
3237
+ }
3238
+ return true;
3239
+ }
3240
+ function canonicalToToolHooks({
3241
+ config,
3242
+ toolOverrideHooks,
3243
+ converterConfig
3244
+ }) {
3245
+ const supported = new Set(converterConfig.supportedEvents);
3246
+ const sharedHooks = {};
3247
+ for (const [event, defs] of Object.entries(config.hooks)) {
3248
+ if (supported.has(event)) {
3249
+ sharedHooks[event] = defs;
3250
+ }
3251
+ }
3252
+ const effectiveHooks = {
3253
+ ...sharedHooks,
3254
+ ...toolOverrideHooks
3255
+ };
3256
+ const result = {};
3257
+ for (const [eventName, definitions] of Object.entries(effectiveHooks)) {
3258
+ const toolEventName = converterConfig.canonicalToToolEventNames[eventName] ?? eventName;
3259
+ const byMatcher = /* @__PURE__ */ new Map();
3260
+ for (const def of definitions) {
3261
+ const key = def.matcher ?? "";
3262
+ const list = byMatcher.get(key);
3263
+ if (list) list.push(def);
3264
+ else byMatcher.set(key, [def]);
3265
+ }
3266
+ const entries = [];
3267
+ for (const [matcherKey, defs] of byMatcher) {
3268
+ const hooks = defs.map((def) => {
3269
+ const command = def.command !== void 0 && def.command !== null && !def.command.startsWith("$") ? `${converterConfig.projectDirVar}/${def.command.replace(/^\.\//, "")}` : def.command;
3270
+ return {
3271
+ type: def.type ?? "command",
3272
+ ...command !== void 0 && command !== null && { command },
3273
+ ...def.timeout !== void 0 && def.timeout !== null && { timeout: def.timeout },
3274
+ ...def.prompt !== void 0 && def.prompt !== null && { prompt: def.prompt }
3275
+ };
3276
+ });
3277
+ entries.push(matcherKey ? { matcher: matcherKey, hooks } : { hooks });
3278
+ }
3279
+ result[toolEventName] = entries;
3280
+ }
3281
+ return result;
3282
+ }
3283
+ function toolHooksToCanonical({
3284
+ hooks,
3285
+ converterConfig
3286
+ }) {
3287
+ if (hooks === null || hooks === void 0 || typeof hooks !== "object") {
3288
+ return {};
3289
+ }
3290
+ const canonical = {};
3291
+ for (const [toolEventName, matcherEntries] of Object.entries(hooks)) {
3292
+ const eventName = converterConfig.toolToCanonicalEventNames[toolEventName] ?? toolEventName;
3293
+ if (!Array.isArray(matcherEntries)) continue;
3294
+ const defs = [];
3295
+ for (const rawEntry of matcherEntries) {
3296
+ if (!isToolMatcherEntry(rawEntry)) continue;
3297
+ const hookDefs = rawEntry.hooks ?? [];
3298
+ for (const h of hookDefs) {
3299
+ const cmd = typeof h.command === "string" ? h.command : void 0;
3300
+ const command = typeof cmd === "string" && cmd.includes(`${converterConfig.projectDirVar}/`) ? cmd.replace(
3301
+ new RegExp(
3302
+ `^${converterConfig.projectDirVar.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\/?`
3303
+ ),
3304
+ "./"
3305
+ ) : cmd;
3306
+ const hookType = h.type === "command" || h.type === "prompt" ? h.type : "command";
3307
+ const timeout = typeof h.timeout === "number" ? h.timeout : void 0;
3308
+ const prompt = typeof h.prompt === "string" ? h.prompt : void 0;
3309
+ defs.push({
3310
+ type: hookType,
3311
+ ...command !== void 0 && command !== null && { command },
3312
+ ...timeout !== void 0 && timeout !== null && { timeout },
3313
+ ...prompt !== void 0 && prompt !== null && { prompt },
3314
+ ...rawEntry.matcher !== void 0 && rawEntry.matcher !== null && rawEntry.matcher !== "" && { matcher: rawEntry.matcher }
3315
+ });
3316
+ }
3317
+ }
3318
+ if (defs.length > 0) {
3319
+ canonical[eventName] = defs;
3320
+ }
3321
+ }
3322
+ return canonical;
3323
+ }
3324
+
3230
3325
  // src/types/tool-file.ts
3231
3326
  var ToolFile = class extends AiFile {
3232
3327
  };
@@ -3317,91 +3412,12 @@ var ToolHooks = class extends ToolFile {
3317
3412
  };
3318
3413
 
3319
3414
  // src/features/hooks/claudecode-hooks.ts
3320
- function canonicalToClaudeHooks(config) {
3321
- const claudeSupported = new Set(CLAUDE_HOOK_EVENTS);
3322
- const sharedHooks = {};
3323
- for (const [event, defs] of Object.entries(config.hooks)) {
3324
- if (claudeSupported.has(event)) {
3325
- sharedHooks[event] = defs;
3326
- }
3327
- }
3328
- const effectiveHooks = {
3329
- ...sharedHooks,
3330
- ...config.claudecode?.hooks
3331
- };
3332
- const claude = {};
3333
- for (const [eventName, definitions] of Object.entries(effectiveHooks)) {
3334
- const claudeEventName = CANONICAL_TO_CLAUDE_EVENT_NAMES[eventName] ?? eventName;
3335
- const byMatcher = /* @__PURE__ */ new Map();
3336
- for (const def of definitions) {
3337
- const key = def.matcher ?? "";
3338
- const list = byMatcher.get(key);
3339
- if (list) list.push(def);
3340
- else byMatcher.set(key, [def]);
3341
- }
3342
- const entries = [];
3343
- for (const [matcherKey, defs] of byMatcher) {
3344
- const hooks = defs.map((def) => {
3345
- const command = def.command !== void 0 && def.command !== null && !def.command.startsWith("$") ? `$CLAUDE_PROJECT_DIR/${def.command.replace(/^\.\//, "")}` : def.command;
3346
- return {
3347
- type: def.type ?? "command",
3348
- ...command !== void 0 && command !== null && { command },
3349
- ...def.timeout !== void 0 && def.timeout !== null && { timeout: def.timeout },
3350
- ...def.prompt !== void 0 && def.prompt !== null && { prompt: def.prompt }
3351
- };
3352
- });
3353
- entries.push(matcherKey ? { matcher: matcherKey, hooks } : { hooks });
3354
- }
3355
- claude[claudeEventName] = entries;
3356
- }
3357
- return claude;
3358
- }
3359
- function isClaudeMatcherEntry(x) {
3360
- if (x === null || typeof x !== "object") {
3361
- return false;
3362
- }
3363
- if ("matcher" in x && typeof x.matcher !== "string") {
3364
- return false;
3365
- }
3366
- if ("hooks" in x && !Array.isArray(x.hooks)) {
3367
- return false;
3368
- }
3369
- return true;
3370
- }
3371
- function claudeHooksToCanonical(claudeHooks) {
3372
- if (claudeHooks === null || claudeHooks === void 0 || typeof claudeHooks !== "object") {
3373
- return {};
3374
- }
3375
- const canonical = {};
3376
- for (const [claudeEventName, matcherEntries] of Object.entries(claudeHooks)) {
3377
- const eventName = CLAUDE_TO_CANONICAL_EVENT_NAMES[claudeEventName] ?? claudeEventName;
3378
- if (!Array.isArray(matcherEntries)) continue;
3379
- const defs = [];
3380
- for (const rawEntry of matcherEntries) {
3381
- if (!isClaudeMatcherEntry(rawEntry)) continue;
3382
- const entry = rawEntry;
3383
- const hooks = entry.hooks ?? [];
3384
- for (const h of hooks) {
3385
- const cmd = typeof h.command === "string" ? h.command : void 0;
3386
- const command = typeof cmd === "string" && cmd.includes("$CLAUDE_PROJECT_DIR/") ? cmd.replace(/^\$CLAUDE_PROJECT_DIR\/?/, "./") : cmd;
3387
- const hookType = h.type === "command" || h.type === "prompt" ? h.type : "command";
3388
- const timeout = typeof h.timeout === "number" ? h.timeout : void 0;
3389
- const prompt = typeof h.prompt === "string" ? h.prompt : void 0;
3390
- defs.push({
3391
- type: hookType,
3392
- ...command !== void 0 && command !== null && { command },
3393
- ...timeout !== void 0 && timeout !== null && { timeout },
3394
- ...prompt !== void 0 && prompt !== null && { prompt },
3395
- ...entry.matcher !== void 0 && entry.matcher !== null && entry.matcher !== "" && { matcher: entry.matcher }
3396
- });
3397
- }
3398
- }
3399
- if (defs.length > 0) {
3400
- canonical[eventName] = defs;
3401
- }
3402
- }
3403
- return canonical;
3404
- }
3415
+ var CLAUDE_CONVERTER_CONFIG = {
3416
+ supportedEvents: CLAUDE_HOOK_EVENTS,
3417
+ canonicalToToolEventNames: CANONICAL_TO_CLAUDE_EVENT_NAMES,
3418
+ toolToCanonicalEventNames: CLAUDE_TO_CANONICAL_EVENT_NAMES,
3419
+ projectDirVar: "$CLAUDE_PROJECT_DIR"
3420
+ };
3405
3421
  var ClaudecodeHooks = class _ClaudecodeHooks extends ToolHooks {
3406
3422
  constructor(params) {
3407
3423
  super({
@@ -3453,7 +3469,11 @@ var ClaudecodeHooks = class _ClaudecodeHooks extends ToolHooks {
3453
3469
  );
3454
3470
  }
3455
3471
  const config = rulesyncHooks.getJson();
3456
- const claudeHooks = canonicalToClaudeHooks(config);
3472
+ const claudeHooks = canonicalToToolHooks({
3473
+ config,
3474
+ toolOverrideHooks: config.claudecode?.hooks,
3475
+ converterConfig: CLAUDE_CONVERTER_CONFIG
3476
+ });
3457
3477
  const merged = { ...settings, hooks: claudeHooks };
3458
3478
  const fileContent = JSON.stringify(merged, null, 2);
3459
3479
  return new _ClaudecodeHooks({
@@ -3476,7 +3496,10 @@ var ClaudecodeHooks = class _ClaudecodeHooks extends ToolHooks {
3476
3496
  }
3477
3497
  );
3478
3498
  }
3479
- const hooks = claudeHooksToCanonical(settings.hooks);
3499
+ const hooks = toolHooksToCanonical({
3500
+ hooks: settings.hooks,
3501
+ converterConfig: CLAUDE_CONVERTER_CONFIG
3502
+ });
3480
3503
  return this.toRulesyncHooksDefault({
3481
3504
  fileContent: JSON.stringify({ version: 1, hooks }, null, 2)
3482
3505
  });
@@ -3785,91 +3808,12 @@ var CursorHooks = class _CursorHooks extends ToolHooks {
3785
3808
 
3786
3809
  // src/features/hooks/factorydroid-hooks.ts
3787
3810
  var import_node_path25 = require("path");
3788
- function canonicalToFactorydroidHooks(config) {
3789
- const supported = new Set(FACTORYDROID_HOOK_EVENTS);
3790
- const sharedHooks = {};
3791
- for (const [event, defs] of Object.entries(config.hooks)) {
3792
- if (supported.has(event)) {
3793
- sharedHooks[event] = defs;
3794
- }
3795
- }
3796
- const effectiveHooks = {
3797
- ...sharedHooks,
3798
- ...config.factorydroid?.hooks
3799
- };
3800
- const result = {};
3801
- for (const [eventName, definitions] of Object.entries(effectiveHooks)) {
3802
- const pascalEventName = CANONICAL_TO_FACTORYDROID_EVENT_NAMES[eventName] ?? eventName;
3803
- const byMatcher = /* @__PURE__ */ new Map();
3804
- for (const def of definitions) {
3805
- const key = def.matcher ?? "";
3806
- const list = byMatcher.get(key);
3807
- if (list) list.push(def);
3808
- else byMatcher.set(key, [def]);
3809
- }
3810
- const entries = [];
3811
- for (const [matcherKey, defs] of byMatcher) {
3812
- const hooks = defs.map((def) => {
3813
- const command = def.command !== void 0 && def.command !== null && !def.command.startsWith("$") ? `$FACTORY_PROJECT_DIR/${def.command.replace(/^\.\//, "")}` : def.command;
3814
- return {
3815
- type: def.type ?? "command",
3816
- ...command !== void 0 && command !== null && { command },
3817
- ...def.timeout !== void 0 && def.timeout !== null && { timeout: def.timeout },
3818
- ...def.prompt !== void 0 && def.prompt !== null && { prompt: def.prompt }
3819
- };
3820
- });
3821
- entries.push(matcherKey ? { matcher: matcherKey, hooks } : { hooks });
3822
- }
3823
- result[pascalEventName] = entries;
3824
- }
3825
- return result;
3826
- }
3827
- function isFactorydroidMatcherEntry(x) {
3828
- if (x === null || typeof x !== "object") {
3829
- return false;
3830
- }
3831
- if ("matcher" in x && typeof x.matcher !== "string") {
3832
- return false;
3833
- }
3834
- if ("hooks" in x && !Array.isArray(x.hooks)) {
3835
- return false;
3836
- }
3837
- return true;
3838
- }
3839
- function factorydroidHooksToCanonical(hooks) {
3840
- if (hooks === null || hooks === void 0 || typeof hooks !== "object") {
3841
- return {};
3842
- }
3843
- const canonical = {};
3844
- for (const [pascalEventName, matcherEntries] of Object.entries(hooks)) {
3845
- const eventName = FACTORYDROID_TO_CANONICAL_EVENT_NAMES[pascalEventName] ?? pascalEventName;
3846
- if (!Array.isArray(matcherEntries)) continue;
3847
- const defs = [];
3848
- for (const rawEntry of matcherEntries) {
3849
- if (!isFactorydroidMatcherEntry(rawEntry)) continue;
3850
- const entry = rawEntry;
3851
- const hookDefs = entry.hooks ?? [];
3852
- for (const h of hookDefs) {
3853
- const cmd = typeof h.command === "string" ? h.command : void 0;
3854
- const command = typeof cmd === "string" && cmd.includes("$FACTORY_PROJECT_DIR/") ? cmd.replace(/^\$FACTORY_PROJECT_DIR\/?/, "./") : cmd;
3855
- const hookType = h.type === "command" || h.type === "prompt" ? h.type : "command";
3856
- const timeout = typeof h.timeout === "number" ? h.timeout : void 0;
3857
- const prompt = typeof h.prompt === "string" ? h.prompt : void 0;
3858
- defs.push({
3859
- type: hookType,
3860
- ...command !== void 0 && command !== null && { command },
3861
- ...timeout !== void 0 && timeout !== null && { timeout },
3862
- ...prompt !== void 0 && prompt !== null && { prompt },
3863
- ...entry.matcher !== void 0 && entry.matcher !== null && entry.matcher !== "" && { matcher: entry.matcher }
3864
- });
3865
- }
3866
- }
3867
- if (defs.length > 0) {
3868
- canonical[eventName] = defs;
3869
- }
3870
- }
3871
- return canonical;
3872
- }
3811
+ var FACTORYDROID_CONVERTER_CONFIG = {
3812
+ supportedEvents: FACTORYDROID_HOOK_EVENTS,
3813
+ canonicalToToolEventNames: CANONICAL_TO_FACTORYDROID_EVENT_NAMES,
3814
+ toolToCanonicalEventNames: FACTORYDROID_TO_CANONICAL_EVENT_NAMES,
3815
+ projectDirVar: "$FACTORY_PROJECT_DIR"
3816
+ };
3873
3817
  var FactorydroidHooks = class _FactorydroidHooks extends ToolHooks {
3874
3818
  constructor(params) {
3875
3819
  super({
@@ -3921,7 +3865,11 @@ var FactorydroidHooks = class _FactorydroidHooks extends ToolHooks {
3921
3865
  );
3922
3866
  }
3923
3867
  const config = rulesyncHooks.getJson();
3924
- const factorydroidHooks = canonicalToFactorydroidHooks(config);
3868
+ const factorydroidHooks = canonicalToToolHooks({
3869
+ config,
3870
+ toolOverrideHooks: config.factorydroid?.hooks,
3871
+ converterConfig: FACTORYDROID_CONVERTER_CONFIG
3872
+ });
3925
3873
  const merged = { ...settings, hooks: factorydroidHooks };
3926
3874
  const fileContent = JSON.stringify(merged, null, 2);
3927
3875
  return new _FactorydroidHooks({
@@ -3944,7 +3892,10 @@ var FactorydroidHooks = class _FactorydroidHooks extends ToolHooks {
3944
3892
  }
3945
3893
  );
3946
3894
  }
3947
- const hooks = factorydroidHooksToCanonical(settings.hooks);
3895
+ const hooks = toolHooksToCanonical({
3896
+ hooks: settings.hooks,
3897
+ converterConfig: FACTORYDROID_CONVERTER_CONFIG
3898
+ });
3948
3899
  return this.toRulesyncHooksDefault({
3949
3900
  fileContent: JSON.stringify({ version: 1, hooks }, null, 2)
3950
3901
  });
@@ -10741,7 +10692,7 @@ var ToolSubagent = class extends ToolFile {
10741
10692
  // src/features/subagents/simulated-subagent.ts
10742
10693
  var SimulatedSubagentFrontmatterSchema = import_mini39.z.object({
10743
10694
  name: import_mini39.z.string(),
10744
- description: import_mini39.z.string()
10695
+ description: import_mini39.z.optional(import_mini39.z.string())
10745
10696
  });
10746
10697
  var SimulatedSubagent = class extends ToolSubagent {
10747
10698
  frontmatter;
@@ -10965,7 +10916,7 @@ var import_mini40 = require("zod/mini");
10965
10916
  var RulesyncSubagentFrontmatterSchema = import_mini40.z.looseObject({
10966
10917
  targets: import_mini40.z._default(RulesyncTargetsSchema, ["*"]),
10967
10918
  name: import_mini40.z.string(),
10968
- description: import_mini40.z.string()
10919
+ description: import_mini40.z.optional(import_mini40.z.string())
10969
10920
  });
10970
10921
  var RulesyncSubagent = class _RulesyncSubagent extends RulesyncFile {
10971
10922
  frontmatter;
@@ -11036,7 +10987,7 @@ var RulesyncSubagent = class _RulesyncSubagent extends RulesyncFile {
11036
10987
  // src/features/subagents/claudecode-subagent.ts
11037
10988
  var ClaudecodeSubagentFrontmatterSchema = import_mini41.z.looseObject({
11038
10989
  name: import_mini41.z.string(),
11039
- description: import_mini41.z.string(),
10990
+ description: import_mini41.z.optional(import_mini41.z.string()),
11040
10991
  model: import_mini41.z.optional(import_mini41.z.string()),
11041
10992
  tools: import_mini41.z.optional(import_mini41.z.union([import_mini41.z.string(), import_mini41.z.array(import_mini41.z.string())])),
11042
10993
  permissionMode: import_mini41.z.optional(import_mini41.z.string()),
@@ -11246,7 +11197,7 @@ var CodexCliSubagent = class _CodexCliSubagent extends ToolSubagent {
11246
11197
  const rulesyncFrontmatter = {
11247
11198
  targets: ["codexcli"],
11248
11199
  name,
11249
- description: description ?? "",
11200
+ description,
11250
11201
  // Only include codexcli section if there are fields
11251
11202
  ...Object.keys(codexcliSection).length > 0 && { codexcli: codexcliSection }
11252
11203
  };
@@ -11359,7 +11310,7 @@ var import_mini43 = require("zod/mini");
11359
11310
  var REQUIRED_TOOL = "agent/runSubagent";
11360
11311
  var CopilotSubagentFrontmatterSchema = import_mini43.z.looseObject({
11361
11312
  name: import_mini43.z.string(),
11362
- description: import_mini43.z.string(),
11313
+ description: import_mini43.z.optional(import_mini43.z.string()),
11363
11314
  tools: import_mini43.z.optional(import_mini43.z.union([import_mini43.z.string(), import_mini43.z.array(import_mini43.z.string())]))
11364
11315
  });
11365
11316
  var normalizeTools = (tools) => {
@@ -11524,7 +11475,7 @@ var import_node_path86 = require("path");
11524
11475
  var import_mini44 = require("zod/mini");
11525
11476
  var CursorSubagentFrontmatterSchema = import_mini44.z.looseObject({
11526
11477
  name: import_mini44.z.string(),
11527
- description: import_mini44.z.string()
11478
+ description: import_mini44.z.optional(import_mini44.z.string())
11528
11479
  });
11529
11480
  var CursorSubagent = class _CursorSubagent extends ToolSubagent {
11530
11481
  frontmatter;
@@ -11729,7 +11680,7 @@ var KiroSubagent = class _KiroSubagent extends ToolSubagent {
11729
11680
  const rulesyncFrontmatter = {
11730
11681
  targets: ["kiro"],
11731
11682
  name,
11732
- description: description ?? "",
11683
+ description: description ?? void 0,
11733
11684
  // Only include kiro section if there are fields
11734
11685
  ...Object.keys(kiroSection).length > 0 && { kiro: kiroSection }
11735
11686
  };
@@ -11840,7 +11791,7 @@ var KiroSubagent = class _KiroSubagent extends ToolSubagent {
11840
11791
  var import_node_path88 = require("path");
11841
11792
  var import_mini46 = require("zod/mini");
11842
11793
  var OpenCodeSubagentFrontmatterSchema = import_mini46.z.looseObject({
11843
- description: import_mini46.z.string(),
11794
+ description: import_mini46.z.optional(import_mini46.z.string()),
11844
11795
  mode: import_mini46.z._default(import_mini46.z.string(), "subagent"),
11845
11796
  name: import_mini46.z.optional(import_mini46.z.string())
11846
11797
  });
@@ -12670,7 +12621,7 @@ var globStrategy = {
12670
12621
  },
12671
12622
  exportRulesyncData: ({ description, ...frontmatter }) => ({
12672
12623
  globs: parseGlobsString(frontmatter.globs),
12673
- description: description || "",
12624
+ description,
12674
12625
  antigravity: frontmatter
12675
12626
  })
12676
12627
  };
@@ -12682,7 +12633,7 @@ var manualStrategy = {
12682
12633
  }),
12683
12634
  exportRulesyncData: ({ description, ...frontmatter }) => ({
12684
12635
  globs: [],
12685
- description: description || "",
12636
+ description,
12686
12637
  antigravity: frontmatter
12687
12638
  })
12688
12639
  };
@@ -12694,7 +12645,7 @@ var alwaysOnStrategy = {
12694
12645
  }),
12695
12646
  exportRulesyncData: ({ description, ...frontmatter }) => ({
12696
12647
  globs: ["**/*"],
12697
- description: description || "",
12648
+ description,
12698
12649
  antigravity: frontmatter
12699
12650
  })
12700
12651
  };
@@ -12707,7 +12658,7 @@ var modelDecisionStrategy = {
12707
12658
  }),
12708
12659
  exportRulesyncData: ({ description, ...frontmatter }) => ({
12709
12660
  globs: [],
12710
- description: description || "",
12661
+ description,
12711
12662
  antigravity: frontmatter
12712
12663
  })
12713
12664
  };
@@ -12722,7 +12673,7 @@ var unknownStrategy = {
12722
12673
  },
12723
12674
  exportRulesyncData: ({ description, ...frontmatter }) => ({
12724
12675
  globs: frontmatter.globs ? parseGlobsString(frontmatter.globs) : ["**/*"],
12725
- description: description || "",
12676
+ description,
12726
12677
  antigravity: frontmatter
12727
12678
  })
12728
12679
  };
@@ -12744,7 +12695,7 @@ var inferenceStrategy = {
12744
12695
  },
12745
12696
  exportRulesyncData: ({ description, ...frontmatter }) => ({
12746
12697
  globs: frontmatter.globs ? parseGlobsString(frontmatter.globs) : ["**/*"],
12747
- description: description || "",
12698
+ description,
12748
12699
  antigravity: frontmatter
12749
12700
  })
12750
12701
  };
@@ -12874,7 +12825,6 @@ var AntigravityRule = class _AntigravityRule extends ToolRule {
12874
12825
  const strategy = STRATEGIES.find((s) => s.canHandle(this.frontmatter.trigger));
12875
12826
  let rulesyncData = {
12876
12827
  globs: [],
12877
- description: "",
12878
12828
  antigravity: this.frontmatter
12879
12829
  };
12880
12830
  if (strategy) {
@@ -12944,7 +12894,6 @@ var AugmentcodeLegacyRule = class _AugmentcodeLegacyRule extends ToolRule {
12944
12894
  const rulesyncFrontmatter = {
12945
12895
  root: this.isRoot(),
12946
12896
  targets: ["*"],
12947
- description: "",
12948
12897
  globs: this.isRoot() ? ["**/*"] : []
12949
12898
  };
12950
12899
  return new RulesyncRule({
@@ -13119,6 +13068,12 @@ var ClaudecodeLegacyRule = class _ClaudecodeLegacyRule extends ToolRule {
13119
13068
  relativeDirPath: ".",
13120
13069
  relativeFilePath: "CLAUDE.md"
13121
13070
  },
13071
+ alternativeRoots: [
13072
+ {
13073
+ relativeDirPath: ".claude",
13074
+ relativeFilePath: "CLAUDE.md"
13075
+ }
13076
+ ],
13122
13077
  nonRoot: {
13123
13078
  relativeDirPath: buildToolPath(".claude", "memories", excludeToolDir)
13124
13079
  }
@@ -13128,18 +13083,19 @@ var ClaudecodeLegacyRule = class _ClaudecodeLegacyRule extends ToolRule {
13128
13083
  baseDir = process.cwd(),
13129
13084
  relativeFilePath,
13130
13085
  validate = true,
13131
- global = false
13086
+ global = false,
13087
+ relativeDirPath: overrideDirPath
13132
13088
  }) {
13133
13089
  const paths = this.getSettablePaths({ global });
13134
13090
  const isRoot = relativeFilePath === paths.root.relativeFilePath;
13135
13091
  if (isRoot) {
13136
- const relativePath2 = paths.root.relativeFilePath;
13092
+ const rootDirPath = overrideDirPath ?? paths.root.relativeDirPath;
13137
13093
  const fileContent2 = await readFileContent(
13138
- (0, import_node_path96.join)(baseDir, paths.root.relativeDirPath, relativePath2)
13094
+ (0, import_node_path96.join)(baseDir, rootDirPath, paths.root.relativeFilePath)
13139
13095
  );
13140
13096
  return new _ClaudecodeLegacyRule({
13141
13097
  baseDir,
13142
- relativeDirPath: paths.root.relativeDirPath,
13098
+ relativeDirPath: rootDirPath,
13143
13099
  relativeFilePath: paths.root.relativeFilePath,
13144
13100
  fileContent: fileContent2,
13145
13101
  validate,
@@ -13234,6 +13190,12 @@ var ClaudecodeRule = class _ClaudecodeRule extends ToolRule {
13234
13190
  relativeDirPath: ".",
13235
13191
  relativeFilePath: "CLAUDE.md"
13236
13192
  },
13193
+ alternativeRoots: [
13194
+ {
13195
+ relativeDirPath: ".claude",
13196
+ relativeFilePath: "CLAUDE.md"
13197
+ }
13198
+ ],
13237
13199
  nonRoot: {
13238
13200
  relativeDirPath: buildToolPath(".claude", "rules", excludeToolDir)
13239
13201
  }
@@ -13266,17 +13228,19 @@ var ClaudecodeRule = class _ClaudecodeRule extends ToolRule {
13266
13228
  baseDir = process.cwd(),
13267
13229
  relativeFilePath,
13268
13230
  validate = true,
13269
- global = false
13231
+ global = false,
13232
+ relativeDirPath: overrideDirPath
13270
13233
  }) {
13271
13234
  const paths = this.getSettablePaths({ global });
13272
13235
  const isRoot = relativeFilePath === paths.root.relativeFilePath;
13273
13236
  if (isRoot) {
13237
+ const rootDirPath = overrideDirPath ?? paths.root.relativeDirPath;
13274
13238
  const fileContent2 = await readFileContent(
13275
- (0, import_node_path97.join)(baseDir, paths.root.relativeDirPath, paths.root.relativeFilePath)
13239
+ (0, import_node_path97.join)(baseDir, rootDirPath, paths.root.relativeFilePath)
13276
13240
  );
13277
13241
  return new _ClaudecodeRule({
13278
13242
  baseDir,
13279
- relativeDirPath: paths.root.relativeDirPath,
13243
+ relativeDirPath: rootDirPath,
13280
13244
  relativeFilePath: paths.root.relativeFilePath,
13281
13245
  frontmatter: {},
13282
13246
  body: fileContent2.trim(),
@@ -15633,35 +15597,62 @@ var RulesProcessor = class extends FeatureProcessor {
15633
15597
  const settablePaths = factory.class.getSettablePaths({
15634
15598
  global: this.global
15635
15599
  });
15600
+ const resolveRelativeDirPath = (filePath) => {
15601
+ const dirName = (0, import_node_path114.dirname)((0, import_node_path114.relative)(this.baseDir, filePath));
15602
+ return dirName === "" ? "." : dirName;
15603
+ };
15604
+ const findFilesWithFallback = async (primaryGlob, alternativeRoots, buildAltGlob) => {
15605
+ const primaryFilePaths = await findFilesByGlobs(primaryGlob);
15606
+ if (primaryFilePaths.length > 0) {
15607
+ return primaryFilePaths;
15608
+ }
15609
+ if (alternativeRoots) {
15610
+ return findFilesByGlobs(alternativeRoots.map(buildAltGlob));
15611
+ }
15612
+ return [];
15613
+ };
15636
15614
  const rootToolRules = await (async () => {
15637
15615
  if (!settablePaths.root) {
15638
15616
  return [];
15639
15617
  }
15640
- const rootFilePaths = await findFilesByGlobs(
15618
+ const uniqueRootFilePaths = await findFilesWithFallback(
15641
15619
  (0, import_node_path114.join)(
15642
15620
  this.baseDir,
15643
15621
  settablePaths.root.relativeDirPath ?? ".",
15644
15622
  settablePaths.root.relativeFilePath
15645
- )
15623
+ ),
15624
+ settablePaths.alternativeRoots,
15625
+ (alt) => (0, import_node_path114.join)(this.baseDir, alt.relativeDirPath, alt.relativeFilePath)
15646
15626
  );
15647
15627
  if (forDeletion) {
15648
- return rootFilePaths.map(
15649
- (filePath) => factory.class.forDeletion({
15628
+ return uniqueRootFilePaths.map((filePath) => {
15629
+ const relativeDirPath = resolveRelativeDirPath(filePath);
15630
+ checkPathTraversal({
15631
+ relativePath: relativeDirPath,
15632
+ intendedRootDir: this.baseDir
15633
+ });
15634
+ return factory.class.forDeletion({
15650
15635
  baseDir: this.baseDir,
15651
- relativeDirPath: settablePaths.root?.relativeDirPath ?? ".",
15636
+ relativeDirPath,
15652
15637
  relativeFilePath: (0, import_node_path114.basename)(filePath),
15653
15638
  global: this.global
15654
- })
15655
- ).filter((rule) => rule.isDeletable());
15639
+ });
15640
+ }).filter((rule) => rule.isDeletable());
15656
15641
  }
15657
15642
  return await Promise.all(
15658
- rootFilePaths.map(
15659
- (filePath) => factory.class.fromFile({
15643
+ uniqueRootFilePaths.map((filePath) => {
15644
+ const relativeDirPath = resolveRelativeDirPath(filePath);
15645
+ checkPathTraversal({
15646
+ relativePath: relativeDirPath,
15647
+ intendedRootDir: this.baseDir
15648
+ });
15649
+ return factory.class.fromFile({
15660
15650
  baseDir: this.baseDir,
15661
15651
  relativeFilePath: (0, import_node_path114.basename)(filePath),
15652
+ relativeDirPath,
15662
15653
  global: this.global
15663
- })
15664
- )
15654
+ });
15655
+ })
15665
15656
  );
15666
15657
  })();
15667
15658
  logger.debug(`Found ${rootToolRules.length} root tool rule files`);
@@ -15675,17 +15666,24 @@ var RulesProcessor = class extends FeatureProcessor {
15675
15666
  if (!settablePaths.root) {
15676
15667
  return [];
15677
15668
  }
15678
- const localRootFilePaths = await findFilesByGlobs(
15679
- (0, import_node_path114.join)(this.baseDir, settablePaths.root.relativeDirPath ?? ".", "CLAUDE.local.md")
15669
+ const uniqueLocalRootFilePaths = await findFilesWithFallback(
15670
+ (0, import_node_path114.join)(this.baseDir, settablePaths.root.relativeDirPath ?? ".", "CLAUDE.local.md"),
15671
+ settablePaths.alternativeRoots,
15672
+ (alt) => (0, import_node_path114.join)(this.baseDir, alt.relativeDirPath, "CLAUDE.local.md")
15680
15673
  );
15681
- return localRootFilePaths.map(
15682
- (filePath) => factory.class.forDeletion({
15674
+ return uniqueLocalRootFilePaths.map((filePath) => {
15675
+ const relativeDirPath = resolveRelativeDirPath(filePath);
15676
+ checkPathTraversal({
15677
+ relativePath: relativeDirPath,
15678
+ intendedRootDir: this.baseDir
15679
+ });
15680
+ return factory.class.forDeletion({
15683
15681
  baseDir: this.baseDir,
15684
- relativeDirPath: settablePaths.root?.relativeDirPath ?? ".",
15682
+ relativeDirPath,
15685
15683
  relativeFilePath: (0, import_node_path114.basename)(filePath),
15686
15684
  global: this.global
15687
- })
15688
- ).filter((rule) => rule.isDeletable());
15685
+ });
15686
+ }).filter((rule) => rule.isDeletable());
15689
15687
  })();
15690
15688
  logger.debug(`Found ${localRootToolRules.length} local root tool rule files for deletion`);
15691
15689
  const nonRootToolRules = await (async () => {