rulesync 7.11.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
@@ -759,10 +759,9 @@ function parseFrontmatter(content, filePath) {
759
759
  body = result.content;
760
760
  } catch (error) {
761
761
  if (filePath) {
762
- throw new Error(
763
- `Failed to parse frontmatter in ${filePath}: ${error instanceof Error ? error.message : String(error)}`,
764
- { cause: error }
765
- );
762
+ throw new Error(`Failed to parse frontmatter in ${filePath}: ${formatError(error)}`, {
763
+ cause: error
764
+ });
766
765
  }
767
766
  throw error;
768
767
  }
@@ -935,7 +934,7 @@ var ToolCommand = class extends AiFile {
935
934
 
936
935
  // src/features/commands/simulated-command.ts
937
936
  var SimulatedCommandFrontmatterSchema = import_mini4.z.object({
938
- description: import_mini4.z.string()
937
+ description: import_mini4.z.optional(import_mini4.z.string())
939
938
  });
940
939
  var SimulatedCommand = class _SimulatedCommand extends ToolCommand {
941
940
  frontmatter;
@@ -1122,7 +1121,7 @@ var RulesyncFile = class extends AiFile {
1122
1121
  // src/features/commands/rulesync-command.ts
1123
1122
  var RulesyncCommandFrontmatterSchema = import_mini5.z.looseObject({
1124
1123
  targets: import_mini5.z._default(RulesyncTargetsSchema, ["*"]),
1125
- description: import_mini5.z.string()
1124
+ description: import_mini5.z.optional(import_mini5.z.string())
1126
1125
  });
1127
1126
  var RulesyncCommand = class _RulesyncCommand extends RulesyncFile {
1128
1127
  frontmatter;
@@ -1210,7 +1209,7 @@ var AntigravityWorkflowFrontmatterSchema = import_mini6.z.looseObject({
1210
1209
  turbo: import_mini6.z.optional(import_mini6.z.boolean())
1211
1210
  });
1212
1211
  var AntigravityCommandFrontmatterSchema = import_mini6.z.looseObject({
1213
- description: import_mini6.z.string(),
1212
+ description: import_mini6.z.optional(import_mini6.z.string()),
1214
1213
  // Support for workflow-specific configuration
1215
1214
  ...AntigravityWorkflowFrontmatterSchema.shape
1216
1215
  });
@@ -1383,7 +1382,7 @@ ${body}${turboDirective}`;
1383
1382
  var import_node_path9 = require("path");
1384
1383
  var import_mini7 = require("zod/mini");
1385
1384
  var ClaudecodeCommandFrontmatterSchema = import_mini7.z.looseObject({
1386
- description: import_mini7.z.string(),
1385
+ description: import_mini7.z.optional(import_mini7.z.string()),
1387
1386
  "allowed-tools": import_mini7.z.optional(import_mini7.z.union([import_mini7.z.string(), import_mini7.z.array(import_mini7.z.string())])),
1388
1387
  "argument-hint": import_mini7.z.optional(import_mini7.z.string()),
1389
1388
  model: import_mini7.z.optional(import_mini7.z.string()),
@@ -1538,8 +1537,7 @@ var ClineCommand = class _ClineCommand extends ToolCommand {
1538
1537
  }
1539
1538
  toRulesyncCommand() {
1540
1539
  const rulesyncFrontmatter = {
1541
- targets: ["*"],
1542
- description: ""
1540
+ targets: ["*"]
1543
1541
  };
1544
1542
  return new RulesyncCommand({
1545
1543
  baseDir: process.cwd(),
@@ -1624,8 +1622,7 @@ var CodexcliCommand = class _CodexcliCommand extends ToolCommand {
1624
1622
  }
1625
1623
  toRulesyncCommand() {
1626
1624
  const rulesyncFrontmatter = {
1627
- targets: ["*"],
1628
- description: ""
1625
+ targets: ["*"]
1629
1626
  };
1630
1627
  return new RulesyncCommand({
1631
1628
  baseDir: ".",
@@ -1703,7 +1700,7 @@ var import_node_path12 = require("path");
1703
1700
  var import_mini8 = require("zod/mini");
1704
1701
  var CopilotCommandFrontmatterSchema = import_mini8.z.looseObject({
1705
1702
  mode: import_mini8.z.optional(import_mini8.z.string()),
1706
- description: import_mini8.z.string()
1703
+ description: import_mini8.z.optional(import_mini8.z.string())
1707
1704
  });
1708
1705
  var CopilotCommand = class _CopilotCommand extends ToolCommand {
1709
1706
  frontmatter;
@@ -1886,7 +1883,7 @@ var CursorCommand = class _CursorCommand extends ToolCommand {
1886
1883
  return this.frontmatter;
1887
1884
  }
1888
1885
  toRulesyncCommand() {
1889
- const { description = "", ...restFields } = this.frontmatter;
1886
+ const { description, ...restFields } = this.frontmatter;
1890
1887
  const rulesyncFrontmatter = {
1891
1888
  targets: ["*"],
1892
1889
  description,
@@ -2080,7 +2077,7 @@ var GeminiCliCommand = class _GeminiCliCommand extends ToolCommand {
2080
2077
  }
2081
2078
  return {
2082
2079
  ...result.data,
2083
- description: result.data.description || ""
2080
+ description: result.data.description
2084
2081
  };
2085
2082
  } catch (error) {
2086
2083
  throw new Error(
@@ -2102,7 +2099,7 @@ var GeminiCliCommand = class _GeminiCliCommand extends ToolCommand {
2102
2099
  const { description, prompt: _prompt, ...restFields } = this.frontmatter;
2103
2100
  const rulesyncFrontmatter = {
2104
2101
  targets: ["geminicli"],
2105
- description: description ?? "",
2102
+ description,
2106
2103
  // Preserve extra fields in geminicli section (excluding prompt which is the body)
2107
2104
  ...Object.keys(restFields).length > 0 && { geminicli: restFields }
2108
2105
  };
@@ -2131,8 +2128,9 @@ var GeminiCliCommand = class _GeminiCliCommand extends ToolCommand {
2131
2128
  prompt: rulesyncCommand.getBody(),
2132
2129
  ...geminicliFields
2133
2130
  };
2134
- const tomlContent = `description = "${geminiFrontmatter.description}"
2135
- prompt = """
2131
+ const descriptionLine = geminiFrontmatter.description !== void 0 ? `description = "${geminiFrontmatter.description}"
2132
+ ` : "";
2133
+ const tomlContent = `${descriptionLine}prompt = """
2136
2134
  ${geminiFrontmatter.prompt}
2137
2135
  """`;
2138
2136
  const paths = this.getSettablePaths({ global });
@@ -2202,8 +2200,7 @@ var KiloCommand = class _KiloCommand extends ToolCommand {
2202
2200
  }
2203
2201
  toRulesyncCommand() {
2204
2202
  const rulesyncFrontmatter = {
2205
- targets: ["*"],
2206
- description: ""
2203
+ targets: ["*"]
2207
2204
  };
2208
2205
  return new RulesyncCommand({
2209
2206
  baseDir: process.cwd(),
@@ -2283,8 +2280,7 @@ var KiroCommand = class _KiroCommand extends ToolCommand {
2283
2280
  }
2284
2281
  toRulesyncCommand() {
2285
2282
  const rulesyncFrontmatter = {
2286
- targets: ["*"],
2287
- description: ""
2283
+ targets: ["*"]
2288
2284
  };
2289
2285
  return new RulesyncCommand({
2290
2286
  baseDir: process.cwd(),
@@ -2358,7 +2354,7 @@ var KiroCommand = class _KiroCommand extends ToolCommand {
2358
2354
  var import_node_path18 = require("path");
2359
2355
  var import_mini11 = require("zod/mini");
2360
2356
  var OpenCodeCommandFrontmatterSchema = import_mini11.z.looseObject({
2361
- description: import_mini11.z.string(),
2357
+ description: import_mini11.z.optional(import_mini11.z.string()),
2362
2358
  agent: (0, import_mini11.optional)(import_mini11.z.string()),
2363
2359
  subtask: (0, import_mini11.optional)(import_mini11.z.boolean()),
2364
2360
  model: (0, import_mini11.optional)(import_mini11.z.string())
@@ -2498,7 +2494,7 @@ var OpenCodeCommand = class _OpenCodeCommand extends ToolCommand {
2498
2494
  var import_node_path19 = require("path");
2499
2495
  var import_mini12 = require("zod/mini");
2500
2496
  var RooCommandFrontmatterSchema = import_mini12.z.looseObject({
2501
- description: import_mini12.z.string(),
2497
+ description: import_mini12.z.optional(import_mini12.z.string()),
2502
2498
  "argument-hint": (0, import_mini12.optional)(import_mini12.z.string())
2503
2499
  });
2504
2500
  var RooCommand = class _RooCommand extends ToolCommand {
@@ -3037,7 +3033,7 @@ var HookDefinitionSchema = import_mini14.z.looseObject({
3037
3033
  type: import_mini14.z.optional(import_mini14.z.enum(["command", "prompt"])),
3038
3034
  timeout: import_mini14.z.optional(import_mini14.z.number()),
3039
3035
  matcher: import_mini14.z.optional(safeString),
3040
- prompt: import_mini14.z.optional(import_mini14.z.string()),
3036
+ prompt: import_mini14.z.optional(safeString),
3041
3037
  loop_limit: import_mini14.z.optional(import_mini14.z.nullable(import_mini14.z.number())),
3042
3038
  name: import_mini14.z.optional(safeString),
3043
3039
  description: import_mini14.z.optional(safeString)
@@ -3089,7 +3085,7 @@ var OPENCODE_HOOK_EVENTS = [
3089
3085
  var COPILOT_HOOK_EVENTS = [
3090
3086
  "sessionStart",
3091
3087
  "sessionEnd",
3092
- "afterSubmitPrompt",
3088
+ "beforeSubmitPrompt",
3093
3089
  "preToolUse",
3094
3090
  "postToolUse",
3095
3091
  "afterError"
@@ -3200,7 +3196,7 @@ var CANONICAL_TO_OPENCODE_EVENT_NAMES = {
3200
3196
  var CANONICAL_TO_COPILOT_EVENT_NAMES = {
3201
3197
  sessionStart: "sessionStart",
3202
3198
  sessionEnd: "sessionEnd",
3203
- afterSubmitPrompt: "userPromptSubmitted",
3199
+ beforeSubmitPrompt: "userPromptSubmitted",
3204
3200
  preToolUse: "preToolUse",
3205
3201
  postToolUse: "postToolUse",
3206
3202
  afterError: "errorOccurred"
@@ -3228,6 +3224,104 @@ var GEMINICLI_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(
3228
3224
  // src/features/hooks/claudecode-hooks.ts
3229
3225
  var import_node_path22 = require("path");
3230
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
+
3231
3325
  // src/types/tool-file.ts
3232
3326
  var ToolFile = class extends AiFile {
3233
3327
  };
@@ -3318,91 +3412,12 @@ var ToolHooks = class extends ToolFile {
3318
3412
  };
3319
3413
 
3320
3414
  // src/features/hooks/claudecode-hooks.ts
3321
- function canonicalToClaudeHooks(config) {
3322
- const claudeSupported = new Set(CLAUDE_HOOK_EVENTS);
3323
- const sharedHooks = {};
3324
- for (const [event, defs] of Object.entries(config.hooks)) {
3325
- if (claudeSupported.has(event)) {
3326
- sharedHooks[event] = defs;
3327
- }
3328
- }
3329
- const effectiveHooks = {
3330
- ...sharedHooks,
3331
- ...config.claudecode?.hooks
3332
- };
3333
- const claude = {};
3334
- for (const [eventName, definitions] of Object.entries(effectiveHooks)) {
3335
- const claudeEventName = CANONICAL_TO_CLAUDE_EVENT_NAMES[eventName] ?? eventName;
3336
- const byMatcher = /* @__PURE__ */ new Map();
3337
- for (const def of definitions) {
3338
- const key = def.matcher ?? "";
3339
- const list = byMatcher.get(key);
3340
- if (list) list.push(def);
3341
- else byMatcher.set(key, [def]);
3342
- }
3343
- const entries = [];
3344
- for (const [matcherKey, defs] of byMatcher) {
3345
- const hooks = defs.map((def) => {
3346
- const command = def.command !== void 0 && def.command !== null && !def.command.startsWith("$") ? `$CLAUDE_PROJECT_DIR/${def.command.replace(/^\.\//, "")}` : def.command;
3347
- return {
3348
- type: def.type ?? "command",
3349
- ...command !== void 0 && command !== null && { command },
3350
- ...def.timeout !== void 0 && def.timeout !== null && { timeout: def.timeout },
3351
- ...def.prompt !== void 0 && def.prompt !== null && { prompt: def.prompt }
3352
- };
3353
- });
3354
- entries.push(matcherKey ? { matcher: matcherKey, hooks } : { hooks });
3355
- }
3356
- claude[claudeEventName] = entries;
3357
- }
3358
- return claude;
3359
- }
3360
- function isClaudeMatcherEntry(x) {
3361
- if (x === null || typeof x !== "object") {
3362
- return false;
3363
- }
3364
- if ("matcher" in x && typeof x.matcher !== "string") {
3365
- return false;
3366
- }
3367
- if ("hooks" in x && !Array.isArray(x.hooks)) {
3368
- return false;
3369
- }
3370
- return true;
3371
- }
3372
- function claudeHooksToCanonical(claudeHooks) {
3373
- if (claudeHooks === null || claudeHooks === void 0 || typeof claudeHooks !== "object") {
3374
- return {};
3375
- }
3376
- const canonical = {};
3377
- for (const [claudeEventName, matcherEntries] of Object.entries(claudeHooks)) {
3378
- const eventName = CLAUDE_TO_CANONICAL_EVENT_NAMES[claudeEventName] ?? claudeEventName;
3379
- if (!Array.isArray(matcherEntries)) continue;
3380
- const defs = [];
3381
- for (const rawEntry of matcherEntries) {
3382
- if (!isClaudeMatcherEntry(rawEntry)) continue;
3383
- const entry = rawEntry;
3384
- const hooks = entry.hooks ?? [];
3385
- for (const h of hooks) {
3386
- const cmd = typeof h.command === "string" ? h.command : void 0;
3387
- const command = typeof cmd === "string" && cmd.includes("$CLAUDE_PROJECT_DIR/") ? cmd.replace(/^\$CLAUDE_PROJECT_DIR\/?/, "./") : cmd;
3388
- const hookType = h.type === "command" || h.type === "prompt" ? h.type : "command";
3389
- const timeout = typeof h.timeout === "number" ? h.timeout : void 0;
3390
- const prompt = typeof h.prompt === "string" ? h.prompt : void 0;
3391
- defs.push({
3392
- type: hookType,
3393
- ...command !== void 0 && command !== null && { command },
3394
- ...timeout !== void 0 && timeout !== null && { timeout },
3395
- ...prompt !== void 0 && prompt !== null && { prompt },
3396
- ...entry.matcher !== void 0 && entry.matcher !== null && entry.matcher !== "" && { matcher: entry.matcher }
3397
- });
3398
- }
3399
- }
3400
- if (defs.length > 0) {
3401
- canonical[eventName] = defs;
3402
- }
3403
- }
3404
- return canonical;
3405
- }
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
+ };
3406
3421
  var ClaudecodeHooks = class _ClaudecodeHooks extends ToolHooks {
3407
3422
  constructor(params) {
3408
3423
  super({
@@ -3454,7 +3469,11 @@ var ClaudecodeHooks = class _ClaudecodeHooks extends ToolHooks {
3454
3469
  );
3455
3470
  }
3456
3471
  const config = rulesyncHooks.getJson();
3457
- const claudeHooks = canonicalToClaudeHooks(config);
3472
+ const claudeHooks = canonicalToToolHooks({
3473
+ config,
3474
+ toolOverrideHooks: config.claudecode?.hooks,
3475
+ converterConfig: CLAUDE_CONVERTER_CONFIG
3476
+ });
3458
3477
  const merged = { ...settings, hooks: claudeHooks };
3459
3478
  const fileContent = JSON.stringify(merged, null, 2);
3460
3479
  return new _ClaudecodeHooks({
@@ -3477,7 +3496,10 @@ var ClaudecodeHooks = class _ClaudecodeHooks extends ToolHooks {
3477
3496
  }
3478
3497
  );
3479
3498
  }
3480
- const hooks = claudeHooksToCanonical(settings.hooks);
3499
+ const hooks = toolHooksToCanonical({
3500
+ hooks: settings.hooks,
3501
+ converterConfig: CLAUDE_CONVERTER_CONFIG
3502
+ });
3481
3503
  return this.toRulesyncHooksDefault({
3482
3504
  fileContent: JSON.stringify({ version: 1, hooks }, null, 2)
3483
3505
  });
@@ -3786,91 +3808,12 @@ var CursorHooks = class _CursorHooks extends ToolHooks {
3786
3808
 
3787
3809
  // src/features/hooks/factorydroid-hooks.ts
3788
3810
  var import_node_path25 = require("path");
3789
- function canonicalToFactorydroidHooks(config) {
3790
- const supported = new Set(FACTORYDROID_HOOK_EVENTS);
3791
- const sharedHooks = {};
3792
- for (const [event, defs] of Object.entries(config.hooks)) {
3793
- if (supported.has(event)) {
3794
- sharedHooks[event] = defs;
3795
- }
3796
- }
3797
- const effectiveHooks = {
3798
- ...sharedHooks,
3799
- ...config.factorydroid?.hooks
3800
- };
3801
- const result = {};
3802
- for (const [eventName, definitions] of Object.entries(effectiveHooks)) {
3803
- const pascalEventName = CANONICAL_TO_FACTORYDROID_EVENT_NAMES[eventName] ?? eventName;
3804
- const byMatcher = /* @__PURE__ */ new Map();
3805
- for (const def of definitions) {
3806
- const key = def.matcher ?? "";
3807
- const list = byMatcher.get(key);
3808
- if (list) list.push(def);
3809
- else byMatcher.set(key, [def]);
3810
- }
3811
- const entries = [];
3812
- for (const [matcherKey, defs] of byMatcher) {
3813
- const hooks = defs.map((def) => {
3814
- const command = def.command !== void 0 && def.command !== null && !def.command.startsWith("$") ? `$FACTORY_PROJECT_DIR/${def.command.replace(/^\.\//, "")}` : def.command;
3815
- return {
3816
- type: def.type ?? "command",
3817
- ...command !== void 0 && command !== null && { command },
3818
- ...def.timeout !== void 0 && def.timeout !== null && { timeout: def.timeout },
3819
- ...def.prompt !== void 0 && def.prompt !== null && { prompt: def.prompt }
3820
- };
3821
- });
3822
- entries.push(matcherKey ? { matcher: matcherKey, hooks } : { hooks });
3823
- }
3824
- result[pascalEventName] = entries;
3825
- }
3826
- return result;
3827
- }
3828
- function isFactorydroidMatcherEntry(x) {
3829
- if (x === null || typeof x !== "object") {
3830
- return false;
3831
- }
3832
- if ("matcher" in x && typeof x.matcher !== "string") {
3833
- return false;
3834
- }
3835
- if ("hooks" in x && !Array.isArray(x.hooks)) {
3836
- return false;
3837
- }
3838
- return true;
3839
- }
3840
- function factorydroidHooksToCanonical(hooks) {
3841
- if (hooks === null || hooks === void 0 || typeof hooks !== "object") {
3842
- return {};
3843
- }
3844
- const canonical = {};
3845
- for (const [pascalEventName, matcherEntries] of Object.entries(hooks)) {
3846
- const eventName = FACTORYDROID_TO_CANONICAL_EVENT_NAMES[pascalEventName] ?? pascalEventName;
3847
- if (!Array.isArray(matcherEntries)) continue;
3848
- const defs = [];
3849
- for (const rawEntry of matcherEntries) {
3850
- if (!isFactorydroidMatcherEntry(rawEntry)) continue;
3851
- const entry = rawEntry;
3852
- const hookDefs = entry.hooks ?? [];
3853
- for (const h of hookDefs) {
3854
- const cmd = typeof h.command === "string" ? h.command : void 0;
3855
- const command = typeof cmd === "string" && cmd.includes("$FACTORY_PROJECT_DIR/") ? cmd.replace(/^\$FACTORY_PROJECT_DIR\/?/, "./") : cmd;
3856
- const hookType = h.type === "command" || h.type === "prompt" ? h.type : "command";
3857
- const timeout = typeof h.timeout === "number" ? h.timeout : void 0;
3858
- const prompt = typeof h.prompt === "string" ? h.prompt : void 0;
3859
- defs.push({
3860
- type: hookType,
3861
- ...command !== void 0 && command !== null && { command },
3862
- ...timeout !== void 0 && timeout !== null && { timeout },
3863
- ...prompt !== void 0 && prompt !== null && { prompt },
3864
- ...entry.matcher !== void 0 && entry.matcher !== null && entry.matcher !== "" && { matcher: entry.matcher }
3865
- });
3866
- }
3867
- }
3868
- if (defs.length > 0) {
3869
- canonical[eventName] = defs;
3870
- }
3871
- }
3872
- return canonical;
3873
- }
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
+ };
3874
3817
  var FactorydroidHooks = class _FactorydroidHooks extends ToolHooks {
3875
3818
  constructor(params) {
3876
3819
  super({
@@ -3922,7 +3865,11 @@ var FactorydroidHooks = class _FactorydroidHooks extends ToolHooks {
3922
3865
  );
3923
3866
  }
3924
3867
  const config = rulesyncHooks.getJson();
3925
- const factorydroidHooks = canonicalToFactorydroidHooks(config);
3868
+ const factorydroidHooks = canonicalToToolHooks({
3869
+ config,
3870
+ toolOverrideHooks: config.factorydroid?.hooks,
3871
+ converterConfig: FACTORYDROID_CONVERTER_CONFIG
3872
+ });
3926
3873
  const merged = { ...settings, hooks: factorydroidHooks };
3927
3874
  const fileContent = JSON.stringify(merged, null, 2);
3928
3875
  return new _FactorydroidHooks({
@@ -3945,7 +3892,10 @@ var FactorydroidHooks = class _FactorydroidHooks extends ToolHooks {
3945
3892
  }
3946
3893
  );
3947
3894
  }
3948
- const hooks = factorydroidHooksToCanonical(settings.hooks);
3895
+ const hooks = toolHooksToCanonical({
3896
+ hooks: settings.hooks,
3897
+ converterConfig: FACTORYDROID_CONVERTER_CONFIG
3898
+ });
3949
3899
  return this.toRulesyncHooksDefault({
3950
3900
  fileContent: JSON.stringify({ version: 1, hooks }, null, 2)
3951
3901
  });
@@ -8073,7 +8023,8 @@ var RulesyncSkillFrontmatterSchemaInternal = import_mini24.z.looseObject({
8073
8023
  targets: import_mini24.z._default(RulesyncTargetsSchema, ["*"]),
8074
8024
  claudecode: import_mini24.z.optional(
8075
8025
  import_mini24.z.looseObject({
8076
- "allowed-tools": import_mini24.z.optional(import_mini24.z.array(import_mini24.z.string()))
8026
+ "allowed-tools": import_mini24.z.optional(import_mini24.z.array(import_mini24.z.string())),
8027
+ model: import_mini24.z.optional(import_mini24.z.string())
8077
8028
  })
8078
8029
  ),
8079
8030
  codexcli: import_mini24.z.optional(
@@ -8509,7 +8460,8 @@ var import_mini27 = require("zod/mini");
8509
8460
  var ClaudecodeSkillFrontmatterSchema = import_mini27.z.looseObject({
8510
8461
  name: import_mini27.z.string(),
8511
8462
  description: import_mini27.z.string(),
8512
- "allowed-tools": import_mini27.z.optional(import_mini27.z.array(import_mini27.z.string()))
8463
+ "allowed-tools": import_mini27.z.optional(import_mini27.z.array(import_mini27.z.string())),
8464
+ model: import_mini27.z.optional(import_mini27.z.string())
8513
8465
  });
8514
8466
  var ClaudecodeSkill = class _ClaudecodeSkill extends ToolSkill {
8515
8467
  constructor({
@@ -8575,15 +8527,15 @@ var ClaudecodeSkill = class _ClaudecodeSkill extends ToolSkill {
8575
8527
  }
8576
8528
  toRulesyncSkill() {
8577
8529
  const frontmatter = this.getFrontmatter();
8530
+ const claudecodeSection = {
8531
+ ...frontmatter["allowed-tools"] && { "allowed-tools": frontmatter["allowed-tools"] },
8532
+ ...frontmatter.model && { model: frontmatter.model }
8533
+ };
8578
8534
  const rulesyncFrontmatter = {
8579
8535
  name: frontmatter.name,
8580
8536
  description: frontmatter.description,
8581
8537
  targets: ["*"],
8582
- ...frontmatter["allowed-tools"] && {
8583
- claudecode: {
8584
- "allowed-tools": frontmatter["allowed-tools"]
8585
- }
8586
- }
8538
+ ...Object.keys(claudecodeSection).length > 0 && { claudecode: claudecodeSection }
8587
8539
  };
8588
8540
  return new RulesyncSkill({
8589
8541
  baseDir: this.baseDir,
@@ -8605,7 +8557,12 @@ var ClaudecodeSkill = class _ClaudecodeSkill extends ToolSkill {
8605
8557
  const claudecodeFrontmatter = {
8606
8558
  name: rulesyncFrontmatter.name,
8607
8559
  description: rulesyncFrontmatter.description,
8608
- "allowed-tools": rulesyncFrontmatter.claudecode?.["allowed-tools"]
8560
+ ...rulesyncFrontmatter.claudecode?.["allowed-tools"] && {
8561
+ "allowed-tools": rulesyncFrontmatter.claudecode["allowed-tools"]
8562
+ },
8563
+ ...rulesyncFrontmatter.claudecode?.model && {
8564
+ model: rulesyncFrontmatter.claudecode.model
8565
+ }
8609
8566
  };
8610
8567
  const settablePaths = _ClaudecodeSkill.getSettablePaths({ global });
8611
8568
  return new _ClaudecodeSkill({
@@ -10735,7 +10692,7 @@ var ToolSubagent = class extends ToolFile {
10735
10692
  // src/features/subagents/simulated-subagent.ts
10736
10693
  var SimulatedSubagentFrontmatterSchema = import_mini39.z.object({
10737
10694
  name: import_mini39.z.string(),
10738
- description: import_mini39.z.string()
10695
+ description: import_mini39.z.optional(import_mini39.z.string())
10739
10696
  });
10740
10697
  var SimulatedSubagent = class extends ToolSubagent {
10741
10698
  frontmatter;
@@ -10959,7 +10916,7 @@ var import_mini40 = require("zod/mini");
10959
10916
  var RulesyncSubagentFrontmatterSchema = import_mini40.z.looseObject({
10960
10917
  targets: import_mini40.z._default(RulesyncTargetsSchema, ["*"]),
10961
10918
  name: import_mini40.z.string(),
10962
- description: import_mini40.z.string()
10919
+ description: import_mini40.z.optional(import_mini40.z.string())
10963
10920
  });
10964
10921
  var RulesyncSubagent = class _RulesyncSubagent extends RulesyncFile {
10965
10922
  frontmatter;
@@ -11030,7 +10987,7 @@ var RulesyncSubagent = class _RulesyncSubagent extends RulesyncFile {
11030
10987
  // src/features/subagents/claudecode-subagent.ts
11031
10988
  var ClaudecodeSubagentFrontmatterSchema = import_mini41.z.looseObject({
11032
10989
  name: import_mini41.z.string(),
11033
- description: import_mini41.z.string(),
10990
+ description: import_mini41.z.optional(import_mini41.z.string()),
11034
10991
  model: import_mini41.z.optional(import_mini41.z.string()),
11035
10992
  tools: import_mini41.z.optional(import_mini41.z.union([import_mini41.z.string(), import_mini41.z.array(import_mini41.z.string())])),
11036
10993
  permissionMode: import_mini41.z.optional(import_mini41.z.string()),
@@ -11240,7 +11197,7 @@ var CodexCliSubagent = class _CodexCliSubagent extends ToolSubagent {
11240
11197
  const rulesyncFrontmatter = {
11241
11198
  targets: ["codexcli"],
11242
11199
  name,
11243
- description: description ?? "",
11200
+ description,
11244
11201
  // Only include codexcli section if there are fields
11245
11202
  ...Object.keys(codexcliSection).length > 0 && { codexcli: codexcliSection }
11246
11203
  };
@@ -11353,7 +11310,7 @@ var import_mini43 = require("zod/mini");
11353
11310
  var REQUIRED_TOOL = "agent/runSubagent";
11354
11311
  var CopilotSubagentFrontmatterSchema = import_mini43.z.looseObject({
11355
11312
  name: import_mini43.z.string(),
11356
- description: import_mini43.z.string(),
11313
+ description: import_mini43.z.optional(import_mini43.z.string()),
11357
11314
  tools: import_mini43.z.optional(import_mini43.z.union([import_mini43.z.string(), import_mini43.z.array(import_mini43.z.string())]))
11358
11315
  });
11359
11316
  var normalizeTools = (tools) => {
@@ -11518,7 +11475,7 @@ var import_node_path86 = require("path");
11518
11475
  var import_mini44 = require("zod/mini");
11519
11476
  var CursorSubagentFrontmatterSchema = import_mini44.z.looseObject({
11520
11477
  name: import_mini44.z.string(),
11521
- description: import_mini44.z.string()
11478
+ description: import_mini44.z.optional(import_mini44.z.string())
11522
11479
  });
11523
11480
  var CursorSubagent = class _CursorSubagent extends ToolSubagent {
11524
11481
  frontmatter;
@@ -11723,7 +11680,7 @@ var KiroSubagent = class _KiroSubagent extends ToolSubagent {
11723
11680
  const rulesyncFrontmatter = {
11724
11681
  targets: ["kiro"],
11725
11682
  name,
11726
- description: description ?? "",
11683
+ description: description ?? void 0,
11727
11684
  // Only include kiro section if there are fields
11728
11685
  ...Object.keys(kiroSection).length > 0 && { kiro: kiroSection }
11729
11686
  };
@@ -11834,7 +11791,7 @@ var KiroSubagent = class _KiroSubagent extends ToolSubagent {
11834
11791
  var import_node_path88 = require("path");
11835
11792
  var import_mini46 = require("zod/mini");
11836
11793
  var OpenCodeSubagentFrontmatterSchema = import_mini46.z.looseObject({
11837
- description: import_mini46.z.string(),
11794
+ description: import_mini46.z.optional(import_mini46.z.string()),
11838
11795
  mode: import_mini46.z._default(import_mini46.z.string(), "subagent"),
11839
11796
  name: import_mini46.z.optional(import_mini46.z.string())
11840
11797
  });
@@ -12664,7 +12621,7 @@ var globStrategy = {
12664
12621
  },
12665
12622
  exportRulesyncData: ({ description, ...frontmatter }) => ({
12666
12623
  globs: parseGlobsString(frontmatter.globs),
12667
- description: description || "",
12624
+ description,
12668
12625
  antigravity: frontmatter
12669
12626
  })
12670
12627
  };
@@ -12676,7 +12633,7 @@ var manualStrategy = {
12676
12633
  }),
12677
12634
  exportRulesyncData: ({ description, ...frontmatter }) => ({
12678
12635
  globs: [],
12679
- description: description || "",
12636
+ description,
12680
12637
  antigravity: frontmatter
12681
12638
  })
12682
12639
  };
@@ -12688,7 +12645,7 @@ var alwaysOnStrategy = {
12688
12645
  }),
12689
12646
  exportRulesyncData: ({ description, ...frontmatter }) => ({
12690
12647
  globs: ["**/*"],
12691
- description: description || "",
12648
+ description,
12692
12649
  antigravity: frontmatter
12693
12650
  })
12694
12651
  };
@@ -12701,7 +12658,7 @@ var modelDecisionStrategy = {
12701
12658
  }),
12702
12659
  exportRulesyncData: ({ description, ...frontmatter }) => ({
12703
12660
  globs: [],
12704
- description: description || "",
12661
+ description,
12705
12662
  antigravity: frontmatter
12706
12663
  })
12707
12664
  };
@@ -12716,7 +12673,7 @@ var unknownStrategy = {
12716
12673
  },
12717
12674
  exportRulesyncData: ({ description, ...frontmatter }) => ({
12718
12675
  globs: frontmatter.globs ? parseGlobsString(frontmatter.globs) : ["**/*"],
12719
- description: description || "",
12676
+ description,
12720
12677
  antigravity: frontmatter
12721
12678
  })
12722
12679
  };
@@ -12738,7 +12695,7 @@ var inferenceStrategy = {
12738
12695
  },
12739
12696
  exportRulesyncData: ({ description, ...frontmatter }) => ({
12740
12697
  globs: frontmatter.globs ? parseGlobsString(frontmatter.globs) : ["**/*"],
12741
- description: description || "",
12698
+ description,
12742
12699
  antigravity: frontmatter
12743
12700
  })
12744
12701
  };
@@ -12868,7 +12825,6 @@ var AntigravityRule = class _AntigravityRule extends ToolRule {
12868
12825
  const strategy = STRATEGIES.find((s) => s.canHandle(this.frontmatter.trigger));
12869
12826
  let rulesyncData = {
12870
12827
  globs: [],
12871
- description: "",
12872
12828
  antigravity: this.frontmatter
12873
12829
  };
12874
12830
  if (strategy) {
@@ -12938,7 +12894,6 @@ var AugmentcodeLegacyRule = class _AugmentcodeLegacyRule extends ToolRule {
12938
12894
  const rulesyncFrontmatter = {
12939
12895
  root: this.isRoot(),
12940
12896
  targets: ["*"],
12941
- description: "",
12942
12897
  globs: this.isRoot() ? ["**/*"] : []
12943
12898
  };
12944
12899
  return new RulesyncRule({
@@ -13113,6 +13068,12 @@ var ClaudecodeLegacyRule = class _ClaudecodeLegacyRule extends ToolRule {
13113
13068
  relativeDirPath: ".",
13114
13069
  relativeFilePath: "CLAUDE.md"
13115
13070
  },
13071
+ alternativeRoots: [
13072
+ {
13073
+ relativeDirPath: ".claude",
13074
+ relativeFilePath: "CLAUDE.md"
13075
+ }
13076
+ ],
13116
13077
  nonRoot: {
13117
13078
  relativeDirPath: buildToolPath(".claude", "memories", excludeToolDir)
13118
13079
  }
@@ -13122,18 +13083,19 @@ var ClaudecodeLegacyRule = class _ClaudecodeLegacyRule extends ToolRule {
13122
13083
  baseDir = process.cwd(),
13123
13084
  relativeFilePath,
13124
13085
  validate = true,
13125
- global = false
13086
+ global = false,
13087
+ relativeDirPath: overrideDirPath
13126
13088
  }) {
13127
13089
  const paths = this.getSettablePaths({ global });
13128
13090
  const isRoot = relativeFilePath === paths.root.relativeFilePath;
13129
13091
  if (isRoot) {
13130
- const relativePath2 = paths.root.relativeFilePath;
13092
+ const rootDirPath = overrideDirPath ?? paths.root.relativeDirPath;
13131
13093
  const fileContent2 = await readFileContent(
13132
- (0, import_node_path96.join)(baseDir, paths.root.relativeDirPath, relativePath2)
13094
+ (0, import_node_path96.join)(baseDir, rootDirPath, paths.root.relativeFilePath)
13133
13095
  );
13134
13096
  return new _ClaudecodeLegacyRule({
13135
13097
  baseDir,
13136
- relativeDirPath: paths.root.relativeDirPath,
13098
+ relativeDirPath: rootDirPath,
13137
13099
  relativeFilePath: paths.root.relativeFilePath,
13138
13100
  fileContent: fileContent2,
13139
13101
  validate,
@@ -13228,6 +13190,12 @@ var ClaudecodeRule = class _ClaudecodeRule extends ToolRule {
13228
13190
  relativeDirPath: ".",
13229
13191
  relativeFilePath: "CLAUDE.md"
13230
13192
  },
13193
+ alternativeRoots: [
13194
+ {
13195
+ relativeDirPath: ".claude",
13196
+ relativeFilePath: "CLAUDE.md"
13197
+ }
13198
+ ],
13231
13199
  nonRoot: {
13232
13200
  relativeDirPath: buildToolPath(".claude", "rules", excludeToolDir)
13233
13201
  }
@@ -13260,17 +13228,19 @@ var ClaudecodeRule = class _ClaudecodeRule extends ToolRule {
13260
13228
  baseDir = process.cwd(),
13261
13229
  relativeFilePath,
13262
13230
  validate = true,
13263
- global = false
13231
+ global = false,
13232
+ relativeDirPath: overrideDirPath
13264
13233
  }) {
13265
13234
  const paths = this.getSettablePaths({ global });
13266
13235
  const isRoot = relativeFilePath === paths.root.relativeFilePath;
13267
13236
  if (isRoot) {
13237
+ const rootDirPath = overrideDirPath ?? paths.root.relativeDirPath;
13268
13238
  const fileContent2 = await readFileContent(
13269
- (0, import_node_path97.join)(baseDir, paths.root.relativeDirPath, paths.root.relativeFilePath)
13239
+ (0, import_node_path97.join)(baseDir, rootDirPath, paths.root.relativeFilePath)
13270
13240
  );
13271
13241
  return new _ClaudecodeRule({
13272
13242
  baseDir,
13273
- relativeDirPath: paths.root.relativeDirPath,
13243
+ relativeDirPath: rootDirPath,
13274
13244
  relativeFilePath: paths.root.relativeFilePath,
13275
13245
  frontmatter: {},
13276
13246
  body: fileContent2.trim(),
@@ -13282,16 +13252,12 @@ var ClaudecodeRule = class _ClaudecodeRule extends ToolRule {
13282
13252
  throw new Error(`nonRoot path is not set for ${relativeFilePath}`);
13283
13253
  }
13284
13254
  const relativePath = (0, import_node_path97.join)(paths.nonRoot.relativeDirPath, relativeFilePath);
13285
- const fileContent = await readFileContent((0, import_node_path97.join)(baseDir, relativePath));
13286
- const { frontmatter, body: content } = parseFrontmatter(
13287
- fileContent,
13288
- (0, import_node_path97.join)(baseDir, relativePath)
13289
- );
13255
+ const filePath = (0, import_node_path97.join)(baseDir, relativePath);
13256
+ const fileContent = await readFileContent(filePath);
13257
+ const { frontmatter, body: content } = parseFrontmatter(fileContent, filePath);
13290
13258
  const result = ClaudecodeRuleFrontmatterSchema.safeParse(frontmatter);
13291
13259
  if (!result.success) {
13292
- throw new Error(
13293
- `Invalid frontmatter in ${(0, import_node_path97.join)(baseDir, relativePath)}: ${formatError(result.error)}`
13294
- );
13260
+ throw new Error(`Invalid frontmatter in ${filePath}: ${formatError(result.error)}`);
13295
13261
  }
13296
13262
  return new _ClaudecodeRule({
13297
13263
  baseDir,
@@ -13723,7 +13689,8 @@ var CopilotRule = class _CopilotRule extends ToolRule {
13723
13689
  const isRoot = relativeFilePath === paths.root.relativeFilePath;
13724
13690
  if (isRoot) {
13725
13691
  const relativePath2 = (0, import_node_path100.join)(paths.root.relativeDirPath, paths.root.relativeFilePath);
13726
- const fileContent2 = await readFileContent((0, import_node_path100.join)(baseDir, relativePath2));
13692
+ const filePath2 = (0, import_node_path100.join)(baseDir, relativePath2);
13693
+ const fileContent2 = await readFileContent(filePath2);
13727
13694
  return new _CopilotRule({
13728
13695
  baseDir,
13729
13696
  relativeDirPath: paths.root.relativeDirPath,
@@ -13738,16 +13705,12 @@ var CopilotRule = class _CopilotRule extends ToolRule {
13738
13705
  throw new Error(`nonRoot path is not set for ${relativeFilePath}`);
13739
13706
  }
13740
13707
  const relativePath = (0, import_node_path100.join)(paths.nonRoot.relativeDirPath, relativeFilePath);
13741
- const fileContent = await readFileContent((0, import_node_path100.join)(baseDir, relativePath));
13742
- const { frontmatter, body: content } = parseFrontmatter(
13743
- fileContent,
13744
- (0, import_node_path100.join)(baseDir, relativePath)
13745
- );
13708
+ const filePath = (0, import_node_path100.join)(baseDir, relativePath);
13709
+ const fileContent = await readFileContent(filePath);
13710
+ const { frontmatter, body: content } = parseFrontmatter(fileContent, filePath);
13746
13711
  const result = CopilotRuleFrontmatterSchema.safeParse(frontmatter);
13747
13712
  if (!result.success) {
13748
- throw new Error(
13749
- `Invalid frontmatter in ${(0, import_node_path100.join)(baseDir, relativeFilePath)}: ${formatError(result.error)}`
13750
- );
13713
+ throw new Error(`Invalid frontmatter in ${filePath}: ${formatError(result.error)}`);
13751
13714
  }
13752
13715
  return new _CopilotRule({
13753
13716
  baseDir,
@@ -15634,35 +15597,62 @@ var RulesProcessor = class extends FeatureProcessor {
15634
15597
  const settablePaths = factory.class.getSettablePaths({
15635
15598
  global: this.global
15636
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
+ };
15637
15614
  const rootToolRules = await (async () => {
15638
15615
  if (!settablePaths.root) {
15639
15616
  return [];
15640
15617
  }
15641
- const rootFilePaths = await findFilesByGlobs(
15618
+ const uniqueRootFilePaths = await findFilesWithFallback(
15642
15619
  (0, import_node_path114.join)(
15643
15620
  this.baseDir,
15644
15621
  settablePaths.root.relativeDirPath ?? ".",
15645
15622
  settablePaths.root.relativeFilePath
15646
- )
15623
+ ),
15624
+ settablePaths.alternativeRoots,
15625
+ (alt) => (0, import_node_path114.join)(this.baseDir, alt.relativeDirPath, alt.relativeFilePath)
15647
15626
  );
15648
15627
  if (forDeletion) {
15649
- return rootFilePaths.map(
15650
- (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({
15651
15635
  baseDir: this.baseDir,
15652
- relativeDirPath: settablePaths.root?.relativeDirPath ?? ".",
15636
+ relativeDirPath,
15653
15637
  relativeFilePath: (0, import_node_path114.basename)(filePath),
15654
15638
  global: this.global
15655
- })
15656
- ).filter((rule) => rule.isDeletable());
15639
+ });
15640
+ }).filter((rule) => rule.isDeletable());
15657
15641
  }
15658
15642
  return await Promise.all(
15659
- rootFilePaths.map(
15660
- (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({
15661
15650
  baseDir: this.baseDir,
15662
15651
  relativeFilePath: (0, import_node_path114.basename)(filePath),
15652
+ relativeDirPath,
15663
15653
  global: this.global
15664
- })
15665
- )
15654
+ });
15655
+ })
15666
15656
  );
15667
15657
  })();
15668
15658
  logger.debug(`Found ${rootToolRules.length} root tool rule files`);
@@ -15676,17 +15666,24 @@ var RulesProcessor = class extends FeatureProcessor {
15676
15666
  if (!settablePaths.root) {
15677
15667
  return [];
15678
15668
  }
15679
- const localRootFilePaths = await findFilesByGlobs(
15680
- (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")
15681
15673
  );
15682
- return localRootFilePaths.map(
15683
- (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({
15684
15681
  baseDir: this.baseDir,
15685
- relativeDirPath: settablePaths.root?.relativeDirPath ?? ".",
15682
+ relativeDirPath,
15686
15683
  relativeFilePath: (0, import_node_path114.basename)(filePath),
15687
15684
  global: this.global
15688
- })
15689
- ).filter((rule) => rule.isDeletable());
15685
+ });
15686
+ }).filter((rule) => rule.isDeletable());
15690
15687
  })();
15691
15688
  logger.debug(`Found ${localRootToolRules.length} local root tool rule files for deletion`);
15692
15689
  const nonRootToolRules = await (async () => {
@@ -15905,6 +15902,14 @@ async function processEmptyFeatureGeneration(params) {
15905
15902
  }
15906
15903
  return { count: totalCount, paths: [], hasDiff };
15907
15904
  }
15905
+ function warnUnsupportedTargets(params) {
15906
+ const { config, supportedTargets, featureName } = params;
15907
+ for (const target of config.getTargets()) {
15908
+ if (!supportedTargets.includes(target) && config.getFeatures(target).includes(featureName)) {
15909
+ logger.warn(`Target '${target}' does not support the feature '${featureName}'. Skipping.`);
15910
+ }
15911
+ }
15912
+ }
15908
15913
  async function checkRulesyncDirExists(params) {
15909
15914
  return fileExists((0, import_node_path115.join)(params.baseDir, RULESYNC_RELATIVE_DIR_PATH));
15910
15915
  }
@@ -15942,10 +15947,9 @@ async function generateRulesCore(params) {
15942
15947
  let totalCount = 0;
15943
15948
  const allPaths = [];
15944
15949
  let hasDiff = false;
15945
- const toolTargets = (0, import_es_toolkit4.intersection)(
15946
- config.getTargets(),
15947
- RulesProcessor.getToolTargets({ global: config.getGlobal() })
15948
- );
15950
+ const supportedTargets = RulesProcessor.getToolTargets({ global: config.getGlobal() });
15951
+ const toolTargets = (0, import_es_toolkit4.intersection)(config.getTargets(), supportedTargets);
15952
+ warnUnsupportedTargets({ config, supportedTargets, featureName: "rules" });
15949
15953
  for (const baseDir of config.getBaseDirs()) {
15950
15954
  for (const toolTarget of toolTargets) {
15951
15955
  if (!config.getFeatures(toolTarget).includes("rules")) {
@@ -15977,13 +15981,19 @@ async function generateRulesCore(params) {
15977
15981
  }
15978
15982
  async function generateIgnoreCore(params) {
15979
15983
  const { config } = params;
15984
+ const supportedIgnoreTargets = IgnoreProcessor.getToolTargets();
15985
+ warnUnsupportedTargets({
15986
+ config,
15987
+ supportedTargets: supportedIgnoreTargets,
15988
+ featureName: "ignore"
15989
+ });
15980
15990
  if (config.getGlobal()) {
15981
15991
  return { count: 0, paths: [], hasDiff: false };
15982
15992
  }
15983
15993
  let totalCount = 0;
15984
15994
  const allPaths = [];
15985
15995
  let hasDiff = false;
15986
- for (const toolTarget of (0, import_es_toolkit4.intersection)(config.getTargets(), IgnoreProcessor.getToolTargets())) {
15996
+ for (const toolTarget of (0, import_es_toolkit4.intersection)(config.getTargets(), supportedIgnoreTargets)) {
15987
15997
  if (!config.getFeatures(toolTarget).includes("ignore")) {
15988
15998
  continue;
15989
15999
  }
@@ -16027,10 +16037,9 @@ async function generateMcpCore(params) {
16027
16037
  let totalCount = 0;
16028
16038
  const allPaths = [];
16029
16039
  let hasDiff = false;
16030
- const toolTargets = (0, import_es_toolkit4.intersection)(
16031
- config.getTargets(),
16032
- McpProcessor.getToolTargets({ global: config.getGlobal() })
16033
- );
16040
+ const supportedMcpTargets = McpProcessor.getToolTargets({ global: config.getGlobal() });
16041
+ const toolTargets = (0, import_es_toolkit4.intersection)(config.getTargets(), supportedMcpTargets);
16042
+ warnUnsupportedTargets({ config, supportedTargets: supportedMcpTargets, featureName: "mcp" });
16034
16043
  for (const baseDir of config.getBaseDirs()) {
16035
16044
  for (const toolTarget of toolTargets) {
16036
16045
  if (!config.getFeatures(toolTarget).includes("mcp")) {
@@ -16061,13 +16070,16 @@ async function generateCommandsCore(params) {
16061
16070
  let totalCount = 0;
16062
16071
  const allPaths = [];
16063
16072
  let hasDiff = false;
16064
- const toolTargets = (0, import_es_toolkit4.intersection)(
16065
- config.getTargets(),
16066
- CommandsProcessor.getToolTargets({
16067
- global: config.getGlobal(),
16068
- includeSimulated: config.getSimulateCommands()
16069
- })
16070
- );
16073
+ const supportedCommandsTargets = CommandsProcessor.getToolTargets({
16074
+ global: config.getGlobal(),
16075
+ includeSimulated: config.getSimulateCommands()
16076
+ });
16077
+ const toolTargets = (0, import_es_toolkit4.intersection)(config.getTargets(), supportedCommandsTargets);
16078
+ warnUnsupportedTargets({
16079
+ config,
16080
+ supportedTargets: supportedCommandsTargets,
16081
+ featureName: "commands"
16082
+ });
16071
16083
  for (const baseDir of config.getBaseDirs()) {
16072
16084
  for (const toolTarget of toolTargets) {
16073
16085
  if (!config.getFeatures(toolTarget).includes("commands")) {
@@ -16098,13 +16110,16 @@ async function generateSubagentsCore(params) {
16098
16110
  let totalCount = 0;
16099
16111
  const allPaths = [];
16100
16112
  let hasDiff = false;
16101
- const toolTargets = (0, import_es_toolkit4.intersection)(
16102
- config.getTargets(),
16103
- SubagentsProcessor.getToolTargets({
16104
- global: config.getGlobal(),
16105
- includeSimulated: config.getSimulateSubagents()
16106
- })
16107
- );
16113
+ const supportedSubagentsTargets = SubagentsProcessor.getToolTargets({
16114
+ global: config.getGlobal(),
16115
+ includeSimulated: config.getSimulateSubagents()
16116
+ });
16117
+ const toolTargets = (0, import_es_toolkit4.intersection)(config.getTargets(), supportedSubagentsTargets);
16118
+ warnUnsupportedTargets({
16119
+ config,
16120
+ supportedTargets: supportedSubagentsTargets,
16121
+ featureName: "subagents"
16122
+ });
16108
16123
  for (const baseDir of config.getBaseDirs()) {
16109
16124
  for (const toolTarget of toolTargets) {
16110
16125
  if (!config.getFeatures(toolTarget).includes("subagents")) {
@@ -16136,13 +16151,16 @@ async function generateSkillsCore(params) {
16136
16151
  const allPaths = [];
16137
16152
  let hasDiff = false;
16138
16153
  const allSkills = [];
16139
- const toolTargets = (0, import_es_toolkit4.intersection)(
16140
- config.getTargets(),
16141
- SkillsProcessor.getToolTargets({
16142
- global: config.getGlobal(),
16143
- includeSimulated: config.getSimulateSkills()
16144
- })
16145
- );
16154
+ const supportedSkillsTargets = SkillsProcessor.getToolTargets({
16155
+ global: config.getGlobal(),
16156
+ includeSimulated: config.getSimulateSkills()
16157
+ });
16158
+ const toolTargets = (0, import_es_toolkit4.intersection)(config.getTargets(), supportedSkillsTargets);
16159
+ warnUnsupportedTargets({
16160
+ config,
16161
+ supportedTargets: supportedSkillsTargets,
16162
+ featureName: "skills"
16163
+ });
16146
16164
  for (const baseDir of config.getBaseDirs()) {
16147
16165
  for (const toolTarget of toolTargets) {
16148
16166
  if (!config.getFeatures(toolTarget).includes("skills")) {
@@ -16178,10 +16196,9 @@ async function generateHooksCore(params) {
16178
16196
  let totalCount = 0;
16179
16197
  const allPaths = [];
16180
16198
  let hasDiff = false;
16181
- const toolTargets = (0, import_es_toolkit4.intersection)(
16182
- config.getTargets(),
16183
- HooksProcessor.getToolTargets({ global: config.getGlobal() })
16184
- );
16199
+ const supportedHooksTargets = HooksProcessor.getToolTargets({ global: config.getGlobal() });
16200
+ const toolTargets = (0, import_es_toolkit4.intersection)(config.getTargets(), supportedHooksTargets);
16201
+ warnUnsupportedTargets({ config, supportedTargets: supportedHooksTargets, featureName: "hooks" });
16185
16202
  for (const baseDir of config.getBaseDirs()) {
16186
16203
  for (const toolTarget of toolTargets) {
16187
16204
  if (!config.getFeatures(toolTarget).includes("hooks")) {
@@ -16253,6 +16270,7 @@ async function importRulesCore(params) {
16253
16270
  });
16254
16271
  const toolFiles = await rulesProcessor.loadToolFiles();
16255
16272
  if (toolFiles.length === 0) {
16273
+ logger.warn(`No rule files found for ${tool}. Skipping import.`);
16256
16274
  return 0;
16257
16275
  }
16258
16276
  const rulesyncFiles = await rulesProcessor.convertToolFilesToRulesyncFiles(toolFiles);
@@ -16280,6 +16298,7 @@ async function importIgnoreCore(params) {
16280
16298
  });
16281
16299
  const toolFiles = await ignoreProcessor.loadToolFiles();
16282
16300
  if (toolFiles.length === 0) {
16301
+ logger.warn(`No ignore files found for ${tool}. Skipping import.`);
16283
16302
  return 0;
16284
16303
  }
16285
16304
  const rulesyncFiles = await ignoreProcessor.convertToolFilesToRulesyncFiles(toolFiles);
@@ -16309,6 +16328,7 @@ async function importMcpCore(params) {
16309
16328
  });
16310
16329
  const toolFiles = await mcpProcessor.loadToolFiles();
16311
16330
  if (toolFiles.length === 0) {
16331
+ logger.warn(`No MCP files found for ${tool}. Skipping import.`);
16312
16332
  return 0;
16313
16333
  }
16314
16334
  const rulesyncFiles = await mcpProcessor.convertToolFilesToRulesyncFiles(toolFiles);
@@ -16335,6 +16355,7 @@ async function importCommandsCore(params) {
16335
16355
  });
16336
16356
  const toolFiles = await commandsProcessor.loadToolFiles();
16337
16357
  if (toolFiles.length === 0) {
16358
+ logger.warn(`No command files found for ${tool}. Skipping import.`);
16338
16359
  return 0;
16339
16360
  }
16340
16361
  const rulesyncFiles = await commandsProcessor.convertToolFilesToRulesyncFiles(toolFiles);
@@ -16361,6 +16382,7 @@ async function importSubagentsCore(params) {
16361
16382
  });
16362
16383
  const toolFiles = await subagentsProcessor.loadToolFiles();
16363
16384
  if (toolFiles.length === 0) {
16385
+ logger.warn(`No subagent files found for ${tool}. Skipping import.`);
16364
16386
  return 0;
16365
16387
  }
16366
16388
  const rulesyncFiles = await subagentsProcessor.convertToolFilesToRulesyncFiles(toolFiles);
@@ -16387,6 +16409,7 @@ async function importSkillsCore(params) {
16387
16409
  });
16388
16410
  const toolDirs = await skillsProcessor.loadToolDirs();
16389
16411
  if (toolDirs.length === 0) {
16412
+ logger.warn(`No skill directories found for ${tool}. Skipping import.`);
16390
16413
  return 0;
16391
16414
  }
16392
16415
  const rulesyncDirs = await skillsProcessor.convertToolDirsToRulesyncDirs(toolDirs);
@@ -16418,6 +16441,7 @@ async function importHooksCore(params) {
16418
16441
  });
16419
16442
  const toolFiles = await hooksProcessor.loadToolFiles();
16420
16443
  if (toolFiles.length === 0) {
16444
+ logger.warn(`No hooks files found for ${tool}. Skipping import.`);
16421
16445
  return 0;
16422
16446
  }
16423
16447
  const rulesyncFiles = await hooksProcessor.convertToolFilesToRulesyncFiles(toolFiles);