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.
@@ -731,10 +731,9 @@ function parseFrontmatter(content, filePath) {
731
731
  body = result.content;
732
732
  } catch (error) {
733
733
  if (filePath) {
734
- throw new Error(
735
- `Failed to parse frontmatter in ${filePath}: ${error instanceof Error ? error.message : String(error)}`,
736
- { cause: error }
737
- );
734
+ throw new Error(`Failed to parse frontmatter in ${filePath}: ${formatError(error)}`, {
735
+ cause: error
736
+ });
738
737
  }
739
738
  throw error;
740
739
  }
@@ -907,7 +906,7 @@ var ToolCommand = class extends AiFile {
907
906
 
908
907
  // src/features/commands/simulated-command.ts
909
908
  var SimulatedCommandFrontmatterSchema = z4.object({
910
- description: z4.string()
909
+ description: z4.optional(z4.string())
911
910
  });
912
911
  var SimulatedCommand = class _SimulatedCommand extends ToolCommand {
913
912
  frontmatter;
@@ -1094,7 +1093,7 @@ var RulesyncFile = class extends AiFile {
1094
1093
  // src/features/commands/rulesync-command.ts
1095
1094
  var RulesyncCommandFrontmatterSchema = z5.looseObject({
1096
1095
  targets: z5._default(RulesyncTargetsSchema, ["*"]),
1097
- description: z5.string()
1096
+ description: z5.optional(z5.string())
1098
1097
  });
1099
1098
  var RulesyncCommand = class _RulesyncCommand extends RulesyncFile {
1100
1099
  frontmatter;
@@ -1182,7 +1181,7 @@ var AntigravityWorkflowFrontmatterSchema = z6.looseObject({
1182
1181
  turbo: z6.optional(z6.boolean())
1183
1182
  });
1184
1183
  var AntigravityCommandFrontmatterSchema = z6.looseObject({
1185
- description: z6.string(),
1184
+ description: z6.optional(z6.string()),
1186
1185
  // Support for workflow-specific configuration
1187
1186
  ...AntigravityWorkflowFrontmatterSchema.shape
1188
1187
  });
@@ -1355,7 +1354,7 @@ ${body}${turboDirective}`;
1355
1354
  import { join as join8 } from "path";
1356
1355
  import { z as z7 } from "zod/mini";
1357
1356
  var ClaudecodeCommandFrontmatterSchema = z7.looseObject({
1358
- description: z7.string(),
1357
+ description: z7.optional(z7.string()),
1359
1358
  "allowed-tools": z7.optional(z7.union([z7.string(), z7.array(z7.string())])),
1360
1359
  "argument-hint": z7.optional(z7.string()),
1361
1360
  model: z7.optional(z7.string()),
@@ -1510,8 +1509,7 @@ var ClineCommand = class _ClineCommand extends ToolCommand {
1510
1509
  }
1511
1510
  toRulesyncCommand() {
1512
1511
  const rulesyncFrontmatter = {
1513
- targets: ["*"],
1514
- description: ""
1512
+ targets: ["*"]
1515
1513
  };
1516
1514
  return new RulesyncCommand({
1517
1515
  baseDir: process.cwd(),
@@ -1596,8 +1594,7 @@ var CodexcliCommand = class _CodexcliCommand extends ToolCommand {
1596
1594
  }
1597
1595
  toRulesyncCommand() {
1598
1596
  const rulesyncFrontmatter = {
1599
- targets: ["*"],
1600
- description: ""
1597
+ targets: ["*"]
1601
1598
  };
1602
1599
  return new RulesyncCommand({
1603
1600
  baseDir: ".",
@@ -1675,7 +1672,7 @@ import { join as join11 } from "path";
1675
1672
  import { z as z8 } from "zod/mini";
1676
1673
  var CopilotCommandFrontmatterSchema = z8.looseObject({
1677
1674
  mode: z8.optional(z8.string()),
1678
- description: z8.string()
1675
+ description: z8.optional(z8.string())
1679
1676
  });
1680
1677
  var CopilotCommand = class _CopilotCommand extends ToolCommand {
1681
1678
  frontmatter;
@@ -1858,7 +1855,7 @@ var CursorCommand = class _CursorCommand extends ToolCommand {
1858
1855
  return this.frontmatter;
1859
1856
  }
1860
1857
  toRulesyncCommand() {
1861
- const { description = "", ...restFields } = this.frontmatter;
1858
+ const { description, ...restFields } = this.frontmatter;
1862
1859
  const rulesyncFrontmatter = {
1863
1860
  targets: ["*"],
1864
1861
  description,
@@ -2052,7 +2049,7 @@ var GeminiCliCommand = class _GeminiCliCommand extends ToolCommand {
2052
2049
  }
2053
2050
  return {
2054
2051
  ...result.data,
2055
- description: result.data.description || ""
2052
+ description: result.data.description
2056
2053
  };
2057
2054
  } catch (error) {
2058
2055
  throw new Error(
@@ -2074,7 +2071,7 @@ var GeminiCliCommand = class _GeminiCliCommand extends ToolCommand {
2074
2071
  const { description, prompt: _prompt, ...restFields } = this.frontmatter;
2075
2072
  const rulesyncFrontmatter = {
2076
2073
  targets: ["geminicli"],
2077
- description: description ?? "",
2074
+ description,
2078
2075
  // Preserve extra fields in geminicli section (excluding prompt which is the body)
2079
2076
  ...Object.keys(restFields).length > 0 && { geminicli: restFields }
2080
2077
  };
@@ -2103,8 +2100,9 @@ var GeminiCliCommand = class _GeminiCliCommand extends ToolCommand {
2103
2100
  prompt: rulesyncCommand.getBody(),
2104
2101
  ...geminicliFields
2105
2102
  };
2106
- const tomlContent = `description = "${geminiFrontmatter.description}"
2107
- prompt = """
2103
+ const descriptionLine = geminiFrontmatter.description !== void 0 ? `description = "${geminiFrontmatter.description}"
2104
+ ` : "";
2105
+ const tomlContent = `${descriptionLine}prompt = """
2108
2106
  ${geminiFrontmatter.prompt}
2109
2107
  """`;
2110
2108
  const paths = this.getSettablePaths({ global });
@@ -2174,8 +2172,7 @@ var KiloCommand = class _KiloCommand extends ToolCommand {
2174
2172
  }
2175
2173
  toRulesyncCommand() {
2176
2174
  const rulesyncFrontmatter = {
2177
- targets: ["*"],
2178
- description: ""
2175
+ targets: ["*"]
2179
2176
  };
2180
2177
  return new RulesyncCommand({
2181
2178
  baseDir: process.cwd(),
@@ -2255,8 +2252,7 @@ var KiroCommand = class _KiroCommand extends ToolCommand {
2255
2252
  }
2256
2253
  toRulesyncCommand() {
2257
2254
  const rulesyncFrontmatter = {
2258
- targets: ["*"],
2259
- description: ""
2255
+ targets: ["*"]
2260
2256
  };
2261
2257
  return new RulesyncCommand({
2262
2258
  baseDir: process.cwd(),
@@ -2330,7 +2326,7 @@ var KiroCommand = class _KiroCommand extends ToolCommand {
2330
2326
  import { join as join17 } from "path";
2331
2327
  import { optional as optional2, z as z11 } from "zod/mini";
2332
2328
  var OpenCodeCommandFrontmatterSchema = z11.looseObject({
2333
- description: z11.string(),
2329
+ description: z11.optional(z11.string()),
2334
2330
  agent: optional2(z11.string()),
2335
2331
  subtask: optional2(z11.boolean()),
2336
2332
  model: optional2(z11.string())
@@ -2470,7 +2466,7 @@ var OpenCodeCommand = class _OpenCodeCommand extends ToolCommand {
2470
2466
  import { join as join18 } from "path";
2471
2467
  import { optional as optional3, z as z12 } from "zod/mini";
2472
2468
  var RooCommandFrontmatterSchema = z12.looseObject({
2473
- description: z12.string(),
2469
+ description: z12.optional(z12.string()),
2474
2470
  "argument-hint": optional3(z12.string())
2475
2471
  });
2476
2472
  var RooCommand = class _RooCommand extends ToolCommand {
@@ -3009,7 +3005,7 @@ var HookDefinitionSchema = z14.looseObject({
3009
3005
  type: z14.optional(z14.enum(["command", "prompt"])),
3010
3006
  timeout: z14.optional(z14.number()),
3011
3007
  matcher: z14.optional(safeString),
3012
- prompt: z14.optional(z14.string()),
3008
+ prompt: z14.optional(safeString),
3013
3009
  loop_limit: z14.optional(z14.nullable(z14.number())),
3014
3010
  name: z14.optional(safeString),
3015
3011
  description: z14.optional(safeString)
@@ -3061,7 +3057,7 @@ var OPENCODE_HOOK_EVENTS = [
3061
3057
  var COPILOT_HOOK_EVENTS = [
3062
3058
  "sessionStart",
3063
3059
  "sessionEnd",
3064
- "afterSubmitPrompt",
3060
+ "beforeSubmitPrompt",
3065
3061
  "preToolUse",
3066
3062
  "postToolUse",
3067
3063
  "afterError"
@@ -3172,7 +3168,7 @@ var CANONICAL_TO_OPENCODE_EVENT_NAMES = {
3172
3168
  var CANONICAL_TO_COPILOT_EVENT_NAMES = {
3173
3169
  sessionStart: "sessionStart",
3174
3170
  sessionEnd: "sessionEnd",
3175
- afterSubmitPrompt: "userPromptSubmitted",
3171
+ beforeSubmitPrompt: "userPromptSubmitted",
3176
3172
  preToolUse: "preToolUse",
3177
3173
  postToolUse: "postToolUse",
3178
3174
  afterError: "errorOccurred"
@@ -3200,6 +3196,104 @@ var GEMINICLI_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(
3200
3196
  // src/features/hooks/claudecode-hooks.ts
3201
3197
  import { join as join21 } from "path";
3202
3198
 
3199
+ // src/features/hooks/tool-hooks-converter.ts
3200
+ function isToolMatcherEntry(x) {
3201
+ if (x === null || typeof x !== "object") {
3202
+ return false;
3203
+ }
3204
+ if ("matcher" in x && typeof x.matcher !== "string") {
3205
+ return false;
3206
+ }
3207
+ if ("hooks" in x && !Array.isArray(x.hooks)) {
3208
+ return false;
3209
+ }
3210
+ return true;
3211
+ }
3212
+ function canonicalToToolHooks({
3213
+ config,
3214
+ toolOverrideHooks,
3215
+ converterConfig
3216
+ }) {
3217
+ const supported = new Set(converterConfig.supportedEvents);
3218
+ const sharedHooks = {};
3219
+ for (const [event, defs] of Object.entries(config.hooks)) {
3220
+ if (supported.has(event)) {
3221
+ sharedHooks[event] = defs;
3222
+ }
3223
+ }
3224
+ const effectiveHooks = {
3225
+ ...sharedHooks,
3226
+ ...toolOverrideHooks
3227
+ };
3228
+ const result = {};
3229
+ for (const [eventName, definitions] of Object.entries(effectiveHooks)) {
3230
+ const toolEventName = converterConfig.canonicalToToolEventNames[eventName] ?? eventName;
3231
+ const byMatcher = /* @__PURE__ */ new Map();
3232
+ for (const def of definitions) {
3233
+ const key = def.matcher ?? "";
3234
+ const list = byMatcher.get(key);
3235
+ if (list) list.push(def);
3236
+ else byMatcher.set(key, [def]);
3237
+ }
3238
+ const entries = [];
3239
+ for (const [matcherKey, defs] of byMatcher) {
3240
+ const hooks = defs.map((def) => {
3241
+ const command = def.command !== void 0 && def.command !== null && !def.command.startsWith("$") ? `${converterConfig.projectDirVar}/${def.command.replace(/^\.\//, "")}` : def.command;
3242
+ return {
3243
+ type: def.type ?? "command",
3244
+ ...command !== void 0 && command !== null && { command },
3245
+ ...def.timeout !== void 0 && def.timeout !== null && { timeout: def.timeout },
3246
+ ...def.prompt !== void 0 && def.prompt !== null && { prompt: def.prompt }
3247
+ };
3248
+ });
3249
+ entries.push(matcherKey ? { matcher: matcherKey, hooks } : { hooks });
3250
+ }
3251
+ result[toolEventName] = entries;
3252
+ }
3253
+ return result;
3254
+ }
3255
+ function toolHooksToCanonical({
3256
+ hooks,
3257
+ converterConfig
3258
+ }) {
3259
+ if (hooks === null || hooks === void 0 || typeof hooks !== "object") {
3260
+ return {};
3261
+ }
3262
+ const canonical = {};
3263
+ for (const [toolEventName, matcherEntries] of Object.entries(hooks)) {
3264
+ const eventName = converterConfig.toolToCanonicalEventNames[toolEventName] ?? toolEventName;
3265
+ if (!Array.isArray(matcherEntries)) continue;
3266
+ const defs = [];
3267
+ for (const rawEntry of matcherEntries) {
3268
+ if (!isToolMatcherEntry(rawEntry)) continue;
3269
+ const hookDefs = rawEntry.hooks ?? [];
3270
+ for (const h of hookDefs) {
3271
+ const cmd = typeof h.command === "string" ? h.command : void 0;
3272
+ const command = typeof cmd === "string" && cmd.includes(`${converterConfig.projectDirVar}/`) ? cmd.replace(
3273
+ new RegExp(
3274
+ `^${converterConfig.projectDirVar.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\/?`
3275
+ ),
3276
+ "./"
3277
+ ) : cmd;
3278
+ const hookType = h.type === "command" || h.type === "prompt" ? h.type : "command";
3279
+ const timeout = typeof h.timeout === "number" ? h.timeout : void 0;
3280
+ const prompt = typeof h.prompt === "string" ? h.prompt : void 0;
3281
+ defs.push({
3282
+ type: hookType,
3283
+ ...command !== void 0 && command !== null && { command },
3284
+ ...timeout !== void 0 && timeout !== null && { timeout },
3285
+ ...prompt !== void 0 && prompt !== null && { prompt },
3286
+ ...rawEntry.matcher !== void 0 && rawEntry.matcher !== null && rawEntry.matcher !== "" && { matcher: rawEntry.matcher }
3287
+ });
3288
+ }
3289
+ }
3290
+ if (defs.length > 0) {
3291
+ canonical[eventName] = defs;
3292
+ }
3293
+ }
3294
+ return canonical;
3295
+ }
3296
+
3203
3297
  // src/types/tool-file.ts
3204
3298
  var ToolFile = class extends AiFile {
3205
3299
  };
@@ -3290,91 +3384,12 @@ var ToolHooks = class extends ToolFile {
3290
3384
  };
3291
3385
 
3292
3386
  // src/features/hooks/claudecode-hooks.ts
3293
- function canonicalToClaudeHooks(config) {
3294
- const claudeSupported = new Set(CLAUDE_HOOK_EVENTS);
3295
- const sharedHooks = {};
3296
- for (const [event, defs] of Object.entries(config.hooks)) {
3297
- if (claudeSupported.has(event)) {
3298
- sharedHooks[event] = defs;
3299
- }
3300
- }
3301
- const effectiveHooks = {
3302
- ...sharedHooks,
3303
- ...config.claudecode?.hooks
3304
- };
3305
- const claude = {};
3306
- for (const [eventName, definitions] of Object.entries(effectiveHooks)) {
3307
- const claudeEventName = CANONICAL_TO_CLAUDE_EVENT_NAMES[eventName] ?? eventName;
3308
- const byMatcher = /* @__PURE__ */ new Map();
3309
- for (const def of definitions) {
3310
- const key = def.matcher ?? "";
3311
- const list = byMatcher.get(key);
3312
- if (list) list.push(def);
3313
- else byMatcher.set(key, [def]);
3314
- }
3315
- const entries = [];
3316
- for (const [matcherKey, defs] of byMatcher) {
3317
- const hooks = defs.map((def) => {
3318
- const command = def.command !== void 0 && def.command !== null && !def.command.startsWith("$") ? `$CLAUDE_PROJECT_DIR/${def.command.replace(/^\.\//, "")}` : def.command;
3319
- return {
3320
- type: def.type ?? "command",
3321
- ...command !== void 0 && command !== null && { command },
3322
- ...def.timeout !== void 0 && def.timeout !== null && { timeout: def.timeout },
3323
- ...def.prompt !== void 0 && def.prompt !== null && { prompt: def.prompt }
3324
- };
3325
- });
3326
- entries.push(matcherKey ? { matcher: matcherKey, hooks } : { hooks });
3327
- }
3328
- claude[claudeEventName] = entries;
3329
- }
3330
- return claude;
3331
- }
3332
- function isClaudeMatcherEntry(x) {
3333
- if (x === null || typeof x !== "object") {
3334
- return false;
3335
- }
3336
- if ("matcher" in x && typeof x.matcher !== "string") {
3337
- return false;
3338
- }
3339
- if ("hooks" in x && !Array.isArray(x.hooks)) {
3340
- return false;
3341
- }
3342
- return true;
3343
- }
3344
- function claudeHooksToCanonical(claudeHooks) {
3345
- if (claudeHooks === null || claudeHooks === void 0 || typeof claudeHooks !== "object") {
3346
- return {};
3347
- }
3348
- const canonical = {};
3349
- for (const [claudeEventName, matcherEntries] of Object.entries(claudeHooks)) {
3350
- const eventName = CLAUDE_TO_CANONICAL_EVENT_NAMES[claudeEventName] ?? claudeEventName;
3351
- if (!Array.isArray(matcherEntries)) continue;
3352
- const defs = [];
3353
- for (const rawEntry of matcherEntries) {
3354
- if (!isClaudeMatcherEntry(rawEntry)) continue;
3355
- const entry = rawEntry;
3356
- const hooks = entry.hooks ?? [];
3357
- for (const h of hooks) {
3358
- const cmd = typeof h.command === "string" ? h.command : void 0;
3359
- const command = typeof cmd === "string" && cmd.includes("$CLAUDE_PROJECT_DIR/") ? cmd.replace(/^\$CLAUDE_PROJECT_DIR\/?/, "./") : cmd;
3360
- const hookType = h.type === "command" || h.type === "prompt" ? h.type : "command";
3361
- const timeout = typeof h.timeout === "number" ? h.timeout : void 0;
3362
- const prompt = typeof h.prompt === "string" ? h.prompt : void 0;
3363
- defs.push({
3364
- type: hookType,
3365
- ...command !== void 0 && command !== null && { command },
3366
- ...timeout !== void 0 && timeout !== null && { timeout },
3367
- ...prompt !== void 0 && prompt !== null && { prompt },
3368
- ...entry.matcher !== void 0 && entry.matcher !== null && entry.matcher !== "" && { matcher: entry.matcher }
3369
- });
3370
- }
3371
- }
3372
- if (defs.length > 0) {
3373
- canonical[eventName] = defs;
3374
- }
3375
- }
3376
- return canonical;
3377
- }
3387
+ var CLAUDE_CONVERTER_CONFIG = {
3388
+ supportedEvents: CLAUDE_HOOK_EVENTS,
3389
+ canonicalToToolEventNames: CANONICAL_TO_CLAUDE_EVENT_NAMES,
3390
+ toolToCanonicalEventNames: CLAUDE_TO_CANONICAL_EVENT_NAMES,
3391
+ projectDirVar: "$CLAUDE_PROJECT_DIR"
3392
+ };
3378
3393
  var ClaudecodeHooks = class _ClaudecodeHooks extends ToolHooks {
3379
3394
  constructor(params) {
3380
3395
  super({
@@ -3426,7 +3441,11 @@ var ClaudecodeHooks = class _ClaudecodeHooks extends ToolHooks {
3426
3441
  );
3427
3442
  }
3428
3443
  const config = rulesyncHooks.getJson();
3429
- const claudeHooks = canonicalToClaudeHooks(config);
3444
+ const claudeHooks = canonicalToToolHooks({
3445
+ config,
3446
+ toolOverrideHooks: config.claudecode?.hooks,
3447
+ converterConfig: CLAUDE_CONVERTER_CONFIG
3448
+ });
3430
3449
  const merged = { ...settings, hooks: claudeHooks };
3431
3450
  const fileContent = JSON.stringify(merged, null, 2);
3432
3451
  return new _ClaudecodeHooks({
@@ -3449,7 +3468,10 @@ var ClaudecodeHooks = class _ClaudecodeHooks extends ToolHooks {
3449
3468
  }
3450
3469
  );
3451
3470
  }
3452
- const hooks = claudeHooksToCanonical(settings.hooks);
3471
+ const hooks = toolHooksToCanonical({
3472
+ hooks: settings.hooks,
3473
+ converterConfig: CLAUDE_CONVERTER_CONFIG
3474
+ });
3453
3475
  return this.toRulesyncHooksDefault({
3454
3476
  fileContent: JSON.stringify({ version: 1, hooks }, null, 2)
3455
3477
  });
@@ -3758,91 +3780,12 @@ var CursorHooks = class _CursorHooks extends ToolHooks {
3758
3780
 
3759
3781
  // src/features/hooks/factorydroid-hooks.ts
3760
3782
  import { join as join24 } from "path";
3761
- function canonicalToFactorydroidHooks(config) {
3762
- const supported = new Set(FACTORYDROID_HOOK_EVENTS);
3763
- const sharedHooks = {};
3764
- for (const [event, defs] of Object.entries(config.hooks)) {
3765
- if (supported.has(event)) {
3766
- sharedHooks[event] = defs;
3767
- }
3768
- }
3769
- const effectiveHooks = {
3770
- ...sharedHooks,
3771
- ...config.factorydroid?.hooks
3772
- };
3773
- const result = {};
3774
- for (const [eventName, definitions] of Object.entries(effectiveHooks)) {
3775
- const pascalEventName = CANONICAL_TO_FACTORYDROID_EVENT_NAMES[eventName] ?? eventName;
3776
- const byMatcher = /* @__PURE__ */ new Map();
3777
- for (const def of definitions) {
3778
- const key = def.matcher ?? "";
3779
- const list = byMatcher.get(key);
3780
- if (list) list.push(def);
3781
- else byMatcher.set(key, [def]);
3782
- }
3783
- const entries = [];
3784
- for (const [matcherKey, defs] of byMatcher) {
3785
- const hooks = defs.map((def) => {
3786
- const command = def.command !== void 0 && def.command !== null && !def.command.startsWith("$") ? `$FACTORY_PROJECT_DIR/${def.command.replace(/^\.\//, "")}` : def.command;
3787
- return {
3788
- type: def.type ?? "command",
3789
- ...command !== void 0 && command !== null && { command },
3790
- ...def.timeout !== void 0 && def.timeout !== null && { timeout: def.timeout },
3791
- ...def.prompt !== void 0 && def.prompt !== null && { prompt: def.prompt }
3792
- };
3793
- });
3794
- entries.push(matcherKey ? { matcher: matcherKey, hooks } : { hooks });
3795
- }
3796
- result[pascalEventName] = entries;
3797
- }
3798
- return result;
3799
- }
3800
- function isFactorydroidMatcherEntry(x) {
3801
- if (x === null || typeof x !== "object") {
3802
- return false;
3803
- }
3804
- if ("matcher" in x && typeof x.matcher !== "string") {
3805
- return false;
3806
- }
3807
- if ("hooks" in x && !Array.isArray(x.hooks)) {
3808
- return false;
3809
- }
3810
- return true;
3811
- }
3812
- function factorydroidHooksToCanonical(hooks) {
3813
- if (hooks === null || hooks === void 0 || typeof hooks !== "object") {
3814
- return {};
3815
- }
3816
- const canonical = {};
3817
- for (const [pascalEventName, matcherEntries] of Object.entries(hooks)) {
3818
- const eventName = FACTORYDROID_TO_CANONICAL_EVENT_NAMES[pascalEventName] ?? pascalEventName;
3819
- if (!Array.isArray(matcherEntries)) continue;
3820
- const defs = [];
3821
- for (const rawEntry of matcherEntries) {
3822
- if (!isFactorydroidMatcherEntry(rawEntry)) continue;
3823
- const entry = rawEntry;
3824
- const hookDefs = entry.hooks ?? [];
3825
- for (const h of hookDefs) {
3826
- const cmd = typeof h.command === "string" ? h.command : void 0;
3827
- const command = typeof cmd === "string" && cmd.includes("$FACTORY_PROJECT_DIR/") ? cmd.replace(/^\$FACTORY_PROJECT_DIR\/?/, "./") : cmd;
3828
- const hookType = h.type === "command" || h.type === "prompt" ? h.type : "command";
3829
- const timeout = typeof h.timeout === "number" ? h.timeout : void 0;
3830
- const prompt = typeof h.prompt === "string" ? h.prompt : void 0;
3831
- defs.push({
3832
- type: hookType,
3833
- ...command !== void 0 && command !== null && { command },
3834
- ...timeout !== void 0 && timeout !== null && { timeout },
3835
- ...prompt !== void 0 && prompt !== null && { prompt },
3836
- ...entry.matcher !== void 0 && entry.matcher !== null && entry.matcher !== "" && { matcher: entry.matcher }
3837
- });
3838
- }
3839
- }
3840
- if (defs.length > 0) {
3841
- canonical[eventName] = defs;
3842
- }
3843
- }
3844
- return canonical;
3845
- }
3783
+ var FACTORYDROID_CONVERTER_CONFIG = {
3784
+ supportedEvents: FACTORYDROID_HOOK_EVENTS,
3785
+ canonicalToToolEventNames: CANONICAL_TO_FACTORYDROID_EVENT_NAMES,
3786
+ toolToCanonicalEventNames: FACTORYDROID_TO_CANONICAL_EVENT_NAMES,
3787
+ projectDirVar: "$FACTORY_PROJECT_DIR"
3788
+ };
3846
3789
  var FactorydroidHooks = class _FactorydroidHooks extends ToolHooks {
3847
3790
  constructor(params) {
3848
3791
  super({
@@ -3894,7 +3837,11 @@ var FactorydroidHooks = class _FactorydroidHooks extends ToolHooks {
3894
3837
  );
3895
3838
  }
3896
3839
  const config = rulesyncHooks.getJson();
3897
- const factorydroidHooks = canonicalToFactorydroidHooks(config);
3840
+ const factorydroidHooks = canonicalToToolHooks({
3841
+ config,
3842
+ toolOverrideHooks: config.factorydroid?.hooks,
3843
+ converterConfig: FACTORYDROID_CONVERTER_CONFIG
3844
+ });
3898
3845
  const merged = { ...settings, hooks: factorydroidHooks };
3899
3846
  const fileContent = JSON.stringify(merged, null, 2);
3900
3847
  return new _FactorydroidHooks({
@@ -3917,7 +3864,10 @@ var FactorydroidHooks = class _FactorydroidHooks extends ToolHooks {
3917
3864
  }
3918
3865
  );
3919
3866
  }
3920
- const hooks = factorydroidHooksToCanonical(settings.hooks);
3867
+ const hooks = toolHooksToCanonical({
3868
+ hooks: settings.hooks,
3869
+ converterConfig: FACTORYDROID_CONVERTER_CONFIG
3870
+ });
3921
3871
  return this.toRulesyncHooksDefault({
3922
3872
  fileContent: JSON.stringify({ version: 1, hooks }, null, 2)
3923
3873
  });
@@ -7445,7 +7395,7 @@ var McpProcessor = class extends FeatureProcessor {
7445
7395
  };
7446
7396
 
7447
7397
  // src/features/rules/rules-processor.ts
7448
- import { basename as basename10, join as join113, relative as relative5 } from "path";
7398
+ import { basename as basename10, dirname as dirname3, join as join113, relative as relative5 } from "path";
7449
7399
  import { encode } from "@toon-format/toon";
7450
7400
  import { z as z54 } from "zod/mini";
7451
7401
 
@@ -8045,7 +7995,8 @@ var RulesyncSkillFrontmatterSchemaInternal = z24.looseObject({
8045
7995
  targets: z24._default(RulesyncTargetsSchema, ["*"]),
8046
7996
  claudecode: z24.optional(
8047
7997
  z24.looseObject({
8048
- "allowed-tools": z24.optional(z24.array(z24.string()))
7998
+ "allowed-tools": z24.optional(z24.array(z24.string())),
7999
+ model: z24.optional(z24.string())
8049
8000
  })
8050
8001
  ),
8051
8002
  codexcli: z24.optional(
@@ -8481,7 +8432,8 @@ import { z as z27 } from "zod/mini";
8481
8432
  var ClaudecodeSkillFrontmatterSchema = z27.looseObject({
8482
8433
  name: z27.string(),
8483
8434
  description: z27.string(),
8484
- "allowed-tools": z27.optional(z27.array(z27.string()))
8435
+ "allowed-tools": z27.optional(z27.array(z27.string())),
8436
+ model: z27.optional(z27.string())
8485
8437
  });
8486
8438
  var ClaudecodeSkill = class _ClaudecodeSkill extends ToolSkill {
8487
8439
  constructor({
@@ -8547,15 +8499,15 @@ var ClaudecodeSkill = class _ClaudecodeSkill extends ToolSkill {
8547
8499
  }
8548
8500
  toRulesyncSkill() {
8549
8501
  const frontmatter = this.getFrontmatter();
8502
+ const claudecodeSection = {
8503
+ ...frontmatter["allowed-tools"] && { "allowed-tools": frontmatter["allowed-tools"] },
8504
+ ...frontmatter.model && { model: frontmatter.model }
8505
+ };
8550
8506
  const rulesyncFrontmatter = {
8551
8507
  name: frontmatter.name,
8552
8508
  description: frontmatter.description,
8553
8509
  targets: ["*"],
8554
- ...frontmatter["allowed-tools"] && {
8555
- claudecode: {
8556
- "allowed-tools": frontmatter["allowed-tools"]
8557
- }
8558
- }
8510
+ ...Object.keys(claudecodeSection).length > 0 && { claudecode: claudecodeSection }
8559
8511
  };
8560
8512
  return new RulesyncSkill({
8561
8513
  baseDir: this.baseDir,
@@ -8577,7 +8529,12 @@ var ClaudecodeSkill = class _ClaudecodeSkill extends ToolSkill {
8577
8529
  const claudecodeFrontmatter = {
8578
8530
  name: rulesyncFrontmatter.name,
8579
8531
  description: rulesyncFrontmatter.description,
8580
- "allowed-tools": rulesyncFrontmatter.claudecode?.["allowed-tools"]
8532
+ ...rulesyncFrontmatter.claudecode?.["allowed-tools"] && {
8533
+ "allowed-tools": rulesyncFrontmatter.claudecode["allowed-tools"]
8534
+ },
8535
+ ...rulesyncFrontmatter.claudecode?.model && {
8536
+ model: rulesyncFrontmatter.claudecode.model
8537
+ }
8581
8538
  };
8582
8539
  const settablePaths = _ClaudecodeSkill.getSettablePaths({ global });
8583
8540
  return new _ClaudecodeSkill({
@@ -10707,7 +10664,7 @@ var ToolSubagent = class extends ToolFile {
10707
10664
  // src/features/subagents/simulated-subagent.ts
10708
10665
  var SimulatedSubagentFrontmatterSchema = z39.object({
10709
10666
  name: z39.string(),
10710
- description: z39.string()
10667
+ description: z39.optional(z39.string())
10711
10668
  });
10712
10669
  var SimulatedSubagent = class extends ToolSubagent {
10713
10670
  frontmatter;
@@ -10931,7 +10888,7 @@ import { z as z40 } from "zod/mini";
10931
10888
  var RulesyncSubagentFrontmatterSchema = z40.looseObject({
10932
10889
  targets: z40._default(RulesyncTargetsSchema, ["*"]),
10933
10890
  name: z40.string(),
10934
- description: z40.string()
10891
+ description: z40.optional(z40.string())
10935
10892
  });
10936
10893
  var RulesyncSubagent = class _RulesyncSubagent extends RulesyncFile {
10937
10894
  frontmatter;
@@ -11002,7 +10959,7 @@ var RulesyncSubagent = class _RulesyncSubagent extends RulesyncFile {
11002
10959
  // src/features/subagents/claudecode-subagent.ts
11003
10960
  var ClaudecodeSubagentFrontmatterSchema = z41.looseObject({
11004
10961
  name: z41.string(),
11005
- description: z41.string(),
10962
+ description: z41.optional(z41.string()),
11006
10963
  model: z41.optional(z41.string()),
11007
10964
  tools: z41.optional(z41.union([z41.string(), z41.array(z41.string())])),
11008
10965
  permissionMode: z41.optional(z41.string()),
@@ -11212,7 +11169,7 @@ var CodexCliSubagent = class _CodexCliSubagent extends ToolSubagent {
11212
11169
  const rulesyncFrontmatter = {
11213
11170
  targets: ["codexcli"],
11214
11171
  name,
11215
- description: description ?? "",
11172
+ description,
11216
11173
  // Only include codexcli section if there are fields
11217
11174
  ...Object.keys(codexcliSection).length > 0 && { codexcli: codexcliSection }
11218
11175
  };
@@ -11325,7 +11282,7 @@ import { z as z43 } from "zod/mini";
11325
11282
  var REQUIRED_TOOL = "agent/runSubagent";
11326
11283
  var CopilotSubagentFrontmatterSchema = z43.looseObject({
11327
11284
  name: z43.string(),
11328
- description: z43.string(),
11285
+ description: z43.optional(z43.string()),
11329
11286
  tools: z43.optional(z43.union([z43.string(), z43.array(z43.string())]))
11330
11287
  });
11331
11288
  var normalizeTools = (tools) => {
@@ -11490,7 +11447,7 @@ import { join as join85 } from "path";
11490
11447
  import { z as z44 } from "zod/mini";
11491
11448
  var CursorSubagentFrontmatterSchema = z44.looseObject({
11492
11449
  name: z44.string(),
11493
- description: z44.string()
11450
+ description: z44.optional(z44.string())
11494
11451
  });
11495
11452
  var CursorSubagent = class _CursorSubagent extends ToolSubagent {
11496
11453
  frontmatter;
@@ -11695,7 +11652,7 @@ var KiroSubagent = class _KiroSubagent extends ToolSubagent {
11695
11652
  const rulesyncFrontmatter = {
11696
11653
  targets: ["kiro"],
11697
11654
  name,
11698
- description: description ?? "",
11655
+ description: description ?? void 0,
11699
11656
  // Only include kiro section if there are fields
11700
11657
  ...Object.keys(kiroSection).length > 0 && { kiro: kiroSection }
11701
11658
  };
@@ -11806,7 +11763,7 @@ var KiroSubagent = class _KiroSubagent extends ToolSubagent {
11806
11763
  import { basename as basename8, join as join87 } from "path";
11807
11764
  import { z as z46 } from "zod/mini";
11808
11765
  var OpenCodeSubagentFrontmatterSchema = z46.looseObject({
11809
- description: z46.string(),
11766
+ description: z46.optional(z46.string()),
11810
11767
  mode: z46._default(z46.string(), "subagent"),
11811
11768
  name: z46.optional(z46.string())
11812
11769
  });
@@ -12636,7 +12593,7 @@ var globStrategy = {
12636
12593
  },
12637
12594
  exportRulesyncData: ({ description, ...frontmatter }) => ({
12638
12595
  globs: parseGlobsString(frontmatter.globs),
12639
- description: description || "",
12596
+ description,
12640
12597
  antigravity: frontmatter
12641
12598
  })
12642
12599
  };
@@ -12648,7 +12605,7 @@ var manualStrategy = {
12648
12605
  }),
12649
12606
  exportRulesyncData: ({ description, ...frontmatter }) => ({
12650
12607
  globs: [],
12651
- description: description || "",
12608
+ description,
12652
12609
  antigravity: frontmatter
12653
12610
  })
12654
12611
  };
@@ -12660,7 +12617,7 @@ var alwaysOnStrategy = {
12660
12617
  }),
12661
12618
  exportRulesyncData: ({ description, ...frontmatter }) => ({
12662
12619
  globs: ["**/*"],
12663
- description: description || "",
12620
+ description,
12664
12621
  antigravity: frontmatter
12665
12622
  })
12666
12623
  };
@@ -12673,7 +12630,7 @@ var modelDecisionStrategy = {
12673
12630
  }),
12674
12631
  exportRulesyncData: ({ description, ...frontmatter }) => ({
12675
12632
  globs: [],
12676
- description: description || "",
12633
+ description,
12677
12634
  antigravity: frontmatter
12678
12635
  })
12679
12636
  };
@@ -12688,7 +12645,7 @@ var unknownStrategy = {
12688
12645
  },
12689
12646
  exportRulesyncData: ({ description, ...frontmatter }) => ({
12690
12647
  globs: frontmatter.globs ? parseGlobsString(frontmatter.globs) : ["**/*"],
12691
- description: description || "",
12648
+ description,
12692
12649
  antigravity: frontmatter
12693
12650
  })
12694
12651
  };
@@ -12710,7 +12667,7 @@ var inferenceStrategy = {
12710
12667
  },
12711
12668
  exportRulesyncData: ({ description, ...frontmatter }) => ({
12712
12669
  globs: frontmatter.globs ? parseGlobsString(frontmatter.globs) : ["**/*"],
12713
- description: description || "",
12670
+ description,
12714
12671
  antigravity: frontmatter
12715
12672
  })
12716
12673
  };
@@ -12840,7 +12797,6 @@ var AntigravityRule = class _AntigravityRule extends ToolRule {
12840
12797
  const strategy = STRATEGIES.find((s) => s.canHandle(this.frontmatter.trigger));
12841
12798
  let rulesyncData = {
12842
12799
  globs: [],
12843
- description: "",
12844
12800
  antigravity: this.frontmatter
12845
12801
  };
12846
12802
  if (strategy) {
@@ -12910,7 +12866,6 @@ var AugmentcodeLegacyRule = class _AugmentcodeLegacyRule extends ToolRule {
12910
12866
  const rulesyncFrontmatter = {
12911
12867
  root: this.isRoot(),
12912
12868
  targets: ["*"],
12913
- description: "",
12914
12869
  globs: this.isRoot() ? ["**/*"] : []
12915
12870
  };
12916
12871
  return new RulesyncRule({
@@ -13085,6 +13040,12 @@ var ClaudecodeLegacyRule = class _ClaudecodeLegacyRule extends ToolRule {
13085
13040
  relativeDirPath: ".",
13086
13041
  relativeFilePath: "CLAUDE.md"
13087
13042
  },
13043
+ alternativeRoots: [
13044
+ {
13045
+ relativeDirPath: ".claude",
13046
+ relativeFilePath: "CLAUDE.md"
13047
+ }
13048
+ ],
13088
13049
  nonRoot: {
13089
13050
  relativeDirPath: buildToolPath(".claude", "memories", excludeToolDir)
13090
13051
  }
@@ -13094,18 +13055,19 @@ var ClaudecodeLegacyRule = class _ClaudecodeLegacyRule extends ToolRule {
13094
13055
  baseDir = process.cwd(),
13095
13056
  relativeFilePath,
13096
13057
  validate = true,
13097
- global = false
13058
+ global = false,
13059
+ relativeDirPath: overrideDirPath
13098
13060
  }) {
13099
13061
  const paths = this.getSettablePaths({ global });
13100
13062
  const isRoot = relativeFilePath === paths.root.relativeFilePath;
13101
13063
  if (isRoot) {
13102
- const relativePath2 = paths.root.relativeFilePath;
13064
+ const rootDirPath = overrideDirPath ?? paths.root.relativeDirPath;
13103
13065
  const fileContent2 = await readFileContent(
13104
- join95(baseDir, paths.root.relativeDirPath, relativePath2)
13066
+ join95(baseDir, rootDirPath, paths.root.relativeFilePath)
13105
13067
  );
13106
13068
  return new _ClaudecodeLegacyRule({
13107
13069
  baseDir,
13108
- relativeDirPath: paths.root.relativeDirPath,
13070
+ relativeDirPath: rootDirPath,
13109
13071
  relativeFilePath: paths.root.relativeFilePath,
13110
13072
  fileContent: fileContent2,
13111
13073
  validate,
@@ -13200,6 +13162,12 @@ var ClaudecodeRule = class _ClaudecodeRule extends ToolRule {
13200
13162
  relativeDirPath: ".",
13201
13163
  relativeFilePath: "CLAUDE.md"
13202
13164
  },
13165
+ alternativeRoots: [
13166
+ {
13167
+ relativeDirPath: ".claude",
13168
+ relativeFilePath: "CLAUDE.md"
13169
+ }
13170
+ ],
13203
13171
  nonRoot: {
13204
13172
  relativeDirPath: buildToolPath(".claude", "rules", excludeToolDir)
13205
13173
  }
@@ -13232,17 +13200,19 @@ var ClaudecodeRule = class _ClaudecodeRule extends ToolRule {
13232
13200
  baseDir = process.cwd(),
13233
13201
  relativeFilePath,
13234
13202
  validate = true,
13235
- global = false
13203
+ global = false,
13204
+ relativeDirPath: overrideDirPath
13236
13205
  }) {
13237
13206
  const paths = this.getSettablePaths({ global });
13238
13207
  const isRoot = relativeFilePath === paths.root.relativeFilePath;
13239
13208
  if (isRoot) {
13209
+ const rootDirPath = overrideDirPath ?? paths.root.relativeDirPath;
13240
13210
  const fileContent2 = await readFileContent(
13241
- join96(baseDir, paths.root.relativeDirPath, paths.root.relativeFilePath)
13211
+ join96(baseDir, rootDirPath, paths.root.relativeFilePath)
13242
13212
  );
13243
13213
  return new _ClaudecodeRule({
13244
13214
  baseDir,
13245
- relativeDirPath: paths.root.relativeDirPath,
13215
+ relativeDirPath: rootDirPath,
13246
13216
  relativeFilePath: paths.root.relativeFilePath,
13247
13217
  frontmatter: {},
13248
13218
  body: fileContent2.trim(),
@@ -13254,16 +13224,12 @@ var ClaudecodeRule = class _ClaudecodeRule extends ToolRule {
13254
13224
  throw new Error(`nonRoot path is not set for ${relativeFilePath}`);
13255
13225
  }
13256
13226
  const relativePath = join96(paths.nonRoot.relativeDirPath, relativeFilePath);
13257
- const fileContent = await readFileContent(join96(baseDir, relativePath));
13258
- const { frontmatter, body: content } = parseFrontmatter(
13259
- fileContent,
13260
- join96(baseDir, relativePath)
13261
- );
13227
+ const filePath = join96(baseDir, relativePath);
13228
+ const fileContent = await readFileContent(filePath);
13229
+ const { frontmatter, body: content } = parseFrontmatter(fileContent, filePath);
13262
13230
  const result = ClaudecodeRuleFrontmatterSchema.safeParse(frontmatter);
13263
13231
  if (!result.success) {
13264
- throw new Error(
13265
- `Invalid frontmatter in ${join96(baseDir, relativePath)}: ${formatError(result.error)}`
13266
- );
13232
+ throw new Error(`Invalid frontmatter in ${filePath}: ${formatError(result.error)}`);
13267
13233
  }
13268
13234
  return new _ClaudecodeRule({
13269
13235
  baseDir,
@@ -13695,7 +13661,8 @@ var CopilotRule = class _CopilotRule extends ToolRule {
13695
13661
  const isRoot = relativeFilePath === paths.root.relativeFilePath;
13696
13662
  if (isRoot) {
13697
13663
  const relativePath2 = join99(paths.root.relativeDirPath, paths.root.relativeFilePath);
13698
- const fileContent2 = await readFileContent(join99(baseDir, relativePath2));
13664
+ const filePath2 = join99(baseDir, relativePath2);
13665
+ const fileContent2 = await readFileContent(filePath2);
13699
13666
  return new _CopilotRule({
13700
13667
  baseDir,
13701
13668
  relativeDirPath: paths.root.relativeDirPath,
@@ -13710,16 +13677,12 @@ var CopilotRule = class _CopilotRule extends ToolRule {
13710
13677
  throw new Error(`nonRoot path is not set for ${relativeFilePath}`);
13711
13678
  }
13712
13679
  const relativePath = join99(paths.nonRoot.relativeDirPath, relativeFilePath);
13713
- const fileContent = await readFileContent(join99(baseDir, relativePath));
13714
- const { frontmatter, body: content } = parseFrontmatter(
13715
- fileContent,
13716
- join99(baseDir, relativePath)
13717
- );
13680
+ const filePath = join99(baseDir, relativePath);
13681
+ const fileContent = await readFileContent(filePath);
13682
+ const { frontmatter, body: content } = parseFrontmatter(fileContent, filePath);
13718
13683
  const result = CopilotRuleFrontmatterSchema.safeParse(frontmatter);
13719
13684
  if (!result.success) {
13720
- throw new Error(
13721
- `Invalid frontmatter in ${join99(baseDir, relativeFilePath)}: ${formatError(result.error)}`
13722
- );
13685
+ throw new Error(`Invalid frontmatter in ${filePath}: ${formatError(result.error)}`);
13723
13686
  }
13724
13687
  return new _CopilotRule({
13725
13688
  baseDir,
@@ -15606,35 +15569,62 @@ var RulesProcessor = class extends FeatureProcessor {
15606
15569
  const settablePaths = factory.class.getSettablePaths({
15607
15570
  global: this.global
15608
15571
  });
15572
+ const resolveRelativeDirPath = (filePath) => {
15573
+ const dirName = dirname3(relative5(this.baseDir, filePath));
15574
+ return dirName === "" ? "." : dirName;
15575
+ };
15576
+ const findFilesWithFallback = async (primaryGlob, alternativeRoots, buildAltGlob) => {
15577
+ const primaryFilePaths = await findFilesByGlobs(primaryGlob);
15578
+ if (primaryFilePaths.length > 0) {
15579
+ return primaryFilePaths;
15580
+ }
15581
+ if (alternativeRoots) {
15582
+ return findFilesByGlobs(alternativeRoots.map(buildAltGlob));
15583
+ }
15584
+ return [];
15585
+ };
15609
15586
  const rootToolRules = await (async () => {
15610
15587
  if (!settablePaths.root) {
15611
15588
  return [];
15612
15589
  }
15613
- const rootFilePaths = await findFilesByGlobs(
15590
+ const uniqueRootFilePaths = await findFilesWithFallback(
15614
15591
  join113(
15615
15592
  this.baseDir,
15616
15593
  settablePaths.root.relativeDirPath ?? ".",
15617
15594
  settablePaths.root.relativeFilePath
15618
- )
15595
+ ),
15596
+ settablePaths.alternativeRoots,
15597
+ (alt) => join113(this.baseDir, alt.relativeDirPath, alt.relativeFilePath)
15619
15598
  );
15620
15599
  if (forDeletion) {
15621
- return rootFilePaths.map(
15622
- (filePath) => factory.class.forDeletion({
15600
+ return uniqueRootFilePaths.map((filePath) => {
15601
+ const relativeDirPath = resolveRelativeDirPath(filePath);
15602
+ checkPathTraversal({
15603
+ relativePath: relativeDirPath,
15604
+ intendedRootDir: this.baseDir
15605
+ });
15606
+ return factory.class.forDeletion({
15623
15607
  baseDir: this.baseDir,
15624
- relativeDirPath: settablePaths.root?.relativeDirPath ?? ".",
15608
+ relativeDirPath,
15625
15609
  relativeFilePath: basename10(filePath),
15626
15610
  global: this.global
15627
- })
15628
- ).filter((rule) => rule.isDeletable());
15611
+ });
15612
+ }).filter((rule) => rule.isDeletable());
15629
15613
  }
15630
15614
  return await Promise.all(
15631
- rootFilePaths.map(
15632
- (filePath) => factory.class.fromFile({
15615
+ uniqueRootFilePaths.map((filePath) => {
15616
+ const relativeDirPath = resolveRelativeDirPath(filePath);
15617
+ checkPathTraversal({
15618
+ relativePath: relativeDirPath,
15619
+ intendedRootDir: this.baseDir
15620
+ });
15621
+ return factory.class.fromFile({
15633
15622
  baseDir: this.baseDir,
15634
15623
  relativeFilePath: basename10(filePath),
15624
+ relativeDirPath,
15635
15625
  global: this.global
15636
- })
15637
- )
15626
+ });
15627
+ })
15638
15628
  );
15639
15629
  })();
15640
15630
  logger.debug(`Found ${rootToolRules.length} root tool rule files`);
@@ -15648,17 +15638,24 @@ var RulesProcessor = class extends FeatureProcessor {
15648
15638
  if (!settablePaths.root) {
15649
15639
  return [];
15650
15640
  }
15651
- const localRootFilePaths = await findFilesByGlobs(
15652
- join113(this.baseDir, settablePaths.root.relativeDirPath ?? ".", "CLAUDE.local.md")
15641
+ const uniqueLocalRootFilePaths = await findFilesWithFallback(
15642
+ join113(this.baseDir, settablePaths.root.relativeDirPath ?? ".", "CLAUDE.local.md"),
15643
+ settablePaths.alternativeRoots,
15644
+ (alt) => join113(this.baseDir, alt.relativeDirPath, "CLAUDE.local.md")
15653
15645
  );
15654
- return localRootFilePaths.map(
15655
- (filePath) => factory.class.forDeletion({
15646
+ return uniqueLocalRootFilePaths.map((filePath) => {
15647
+ const relativeDirPath = resolveRelativeDirPath(filePath);
15648
+ checkPathTraversal({
15649
+ relativePath: relativeDirPath,
15650
+ intendedRootDir: this.baseDir
15651
+ });
15652
+ return factory.class.forDeletion({
15656
15653
  baseDir: this.baseDir,
15657
- relativeDirPath: settablePaths.root?.relativeDirPath ?? ".",
15654
+ relativeDirPath,
15658
15655
  relativeFilePath: basename10(filePath),
15659
15656
  global: this.global
15660
- })
15661
- ).filter((rule) => rule.isDeletable());
15657
+ });
15658
+ }).filter((rule) => rule.isDeletable());
15662
15659
  })();
15663
15660
  logger.debug(`Found ${localRootToolRules.length} local root tool rule files for deletion`);
15664
15661
  const nonRootToolRules = await (async () => {
@@ -15877,6 +15874,14 @@ async function processEmptyFeatureGeneration(params) {
15877
15874
  }
15878
15875
  return { count: totalCount, paths: [], hasDiff };
15879
15876
  }
15877
+ function warnUnsupportedTargets(params) {
15878
+ const { config, supportedTargets, featureName } = params;
15879
+ for (const target of config.getTargets()) {
15880
+ if (!supportedTargets.includes(target) && config.getFeatures(target).includes(featureName)) {
15881
+ logger.warn(`Target '${target}' does not support the feature '${featureName}'. Skipping.`);
15882
+ }
15883
+ }
15884
+ }
15880
15885
  async function checkRulesyncDirExists(params) {
15881
15886
  return fileExists(join114(params.baseDir, RULESYNC_RELATIVE_DIR_PATH));
15882
15887
  }
@@ -15914,10 +15919,9 @@ async function generateRulesCore(params) {
15914
15919
  let totalCount = 0;
15915
15920
  const allPaths = [];
15916
15921
  let hasDiff = false;
15917
- const toolTargets = intersection(
15918
- config.getTargets(),
15919
- RulesProcessor.getToolTargets({ global: config.getGlobal() })
15920
- );
15922
+ const supportedTargets = RulesProcessor.getToolTargets({ global: config.getGlobal() });
15923
+ const toolTargets = intersection(config.getTargets(), supportedTargets);
15924
+ warnUnsupportedTargets({ config, supportedTargets, featureName: "rules" });
15921
15925
  for (const baseDir of config.getBaseDirs()) {
15922
15926
  for (const toolTarget of toolTargets) {
15923
15927
  if (!config.getFeatures(toolTarget).includes("rules")) {
@@ -15949,13 +15953,19 @@ async function generateRulesCore(params) {
15949
15953
  }
15950
15954
  async function generateIgnoreCore(params) {
15951
15955
  const { config } = params;
15956
+ const supportedIgnoreTargets = IgnoreProcessor.getToolTargets();
15957
+ warnUnsupportedTargets({
15958
+ config,
15959
+ supportedTargets: supportedIgnoreTargets,
15960
+ featureName: "ignore"
15961
+ });
15952
15962
  if (config.getGlobal()) {
15953
15963
  return { count: 0, paths: [], hasDiff: false };
15954
15964
  }
15955
15965
  let totalCount = 0;
15956
15966
  const allPaths = [];
15957
15967
  let hasDiff = false;
15958
- for (const toolTarget of intersection(config.getTargets(), IgnoreProcessor.getToolTargets())) {
15968
+ for (const toolTarget of intersection(config.getTargets(), supportedIgnoreTargets)) {
15959
15969
  if (!config.getFeatures(toolTarget).includes("ignore")) {
15960
15970
  continue;
15961
15971
  }
@@ -15999,10 +16009,9 @@ async function generateMcpCore(params) {
15999
16009
  let totalCount = 0;
16000
16010
  const allPaths = [];
16001
16011
  let hasDiff = false;
16002
- const toolTargets = intersection(
16003
- config.getTargets(),
16004
- McpProcessor.getToolTargets({ global: config.getGlobal() })
16005
- );
16012
+ const supportedMcpTargets = McpProcessor.getToolTargets({ global: config.getGlobal() });
16013
+ const toolTargets = intersection(config.getTargets(), supportedMcpTargets);
16014
+ warnUnsupportedTargets({ config, supportedTargets: supportedMcpTargets, featureName: "mcp" });
16006
16015
  for (const baseDir of config.getBaseDirs()) {
16007
16016
  for (const toolTarget of toolTargets) {
16008
16017
  if (!config.getFeatures(toolTarget).includes("mcp")) {
@@ -16033,13 +16042,16 @@ async function generateCommandsCore(params) {
16033
16042
  let totalCount = 0;
16034
16043
  const allPaths = [];
16035
16044
  let hasDiff = false;
16036
- const toolTargets = intersection(
16037
- config.getTargets(),
16038
- CommandsProcessor.getToolTargets({
16039
- global: config.getGlobal(),
16040
- includeSimulated: config.getSimulateCommands()
16041
- })
16042
- );
16045
+ const supportedCommandsTargets = CommandsProcessor.getToolTargets({
16046
+ global: config.getGlobal(),
16047
+ includeSimulated: config.getSimulateCommands()
16048
+ });
16049
+ const toolTargets = intersection(config.getTargets(), supportedCommandsTargets);
16050
+ warnUnsupportedTargets({
16051
+ config,
16052
+ supportedTargets: supportedCommandsTargets,
16053
+ featureName: "commands"
16054
+ });
16043
16055
  for (const baseDir of config.getBaseDirs()) {
16044
16056
  for (const toolTarget of toolTargets) {
16045
16057
  if (!config.getFeatures(toolTarget).includes("commands")) {
@@ -16070,13 +16082,16 @@ async function generateSubagentsCore(params) {
16070
16082
  let totalCount = 0;
16071
16083
  const allPaths = [];
16072
16084
  let hasDiff = false;
16073
- const toolTargets = intersection(
16074
- config.getTargets(),
16075
- SubagentsProcessor.getToolTargets({
16076
- global: config.getGlobal(),
16077
- includeSimulated: config.getSimulateSubagents()
16078
- })
16079
- );
16085
+ const supportedSubagentsTargets = SubagentsProcessor.getToolTargets({
16086
+ global: config.getGlobal(),
16087
+ includeSimulated: config.getSimulateSubagents()
16088
+ });
16089
+ const toolTargets = intersection(config.getTargets(), supportedSubagentsTargets);
16090
+ warnUnsupportedTargets({
16091
+ config,
16092
+ supportedTargets: supportedSubagentsTargets,
16093
+ featureName: "subagents"
16094
+ });
16080
16095
  for (const baseDir of config.getBaseDirs()) {
16081
16096
  for (const toolTarget of toolTargets) {
16082
16097
  if (!config.getFeatures(toolTarget).includes("subagents")) {
@@ -16108,13 +16123,16 @@ async function generateSkillsCore(params) {
16108
16123
  const allPaths = [];
16109
16124
  let hasDiff = false;
16110
16125
  const allSkills = [];
16111
- const toolTargets = intersection(
16112
- config.getTargets(),
16113
- SkillsProcessor.getToolTargets({
16114
- global: config.getGlobal(),
16115
- includeSimulated: config.getSimulateSkills()
16116
- })
16117
- );
16126
+ const supportedSkillsTargets = SkillsProcessor.getToolTargets({
16127
+ global: config.getGlobal(),
16128
+ includeSimulated: config.getSimulateSkills()
16129
+ });
16130
+ const toolTargets = intersection(config.getTargets(), supportedSkillsTargets);
16131
+ warnUnsupportedTargets({
16132
+ config,
16133
+ supportedTargets: supportedSkillsTargets,
16134
+ featureName: "skills"
16135
+ });
16118
16136
  for (const baseDir of config.getBaseDirs()) {
16119
16137
  for (const toolTarget of toolTargets) {
16120
16138
  if (!config.getFeatures(toolTarget).includes("skills")) {
@@ -16150,10 +16168,9 @@ async function generateHooksCore(params) {
16150
16168
  let totalCount = 0;
16151
16169
  const allPaths = [];
16152
16170
  let hasDiff = false;
16153
- const toolTargets = intersection(
16154
- config.getTargets(),
16155
- HooksProcessor.getToolTargets({ global: config.getGlobal() })
16156
- );
16171
+ const supportedHooksTargets = HooksProcessor.getToolTargets({ global: config.getGlobal() });
16172
+ const toolTargets = intersection(config.getTargets(), supportedHooksTargets);
16173
+ warnUnsupportedTargets({ config, supportedTargets: supportedHooksTargets, featureName: "hooks" });
16157
16174
  for (const baseDir of config.getBaseDirs()) {
16158
16175
  for (const toolTarget of toolTargets) {
16159
16176
  if (!config.getFeatures(toolTarget).includes("hooks")) {
@@ -16225,6 +16242,7 @@ async function importRulesCore(params) {
16225
16242
  });
16226
16243
  const toolFiles = await rulesProcessor.loadToolFiles();
16227
16244
  if (toolFiles.length === 0) {
16245
+ logger.warn(`No rule files found for ${tool}. Skipping import.`);
16228
16246
  return 0;
16229
16247
  }
16230
16248
  const rulesyncFiles = await rulesProcessor.convertToolFilesToRulesyncFiles(toolFiles);
@@ -16252,6 +16270,7 @@ async function importIgnoreCore(params) {
16252
16270
  });
16253
16271
  const toolFiles = await ignoreProcessor.loadToolFiles();
16254
16272
  if (toolFiles.length === 0) {
16273
+ logger.warn(`No ignore files found for ${tool}. Skipping import.`);
16255
16274
  return 0;
16256
16275
  }
16257
16276
  const rulesyncFiles = await ignoreProcessor.convertToolFilesToRulesyncFiles(toolFiles);
@@ -16281,6 +16300,7 @@ async function importMcpCore(params) {
16281
16300
  });
16282
16301
  const toolFiles = await mcpProcessor.loadToolFiles();
16283
16302
  if (toolFiles.length === 0) {
16303
+ logger.warn(`No MCP files found for ${tool}. Skipping import.`);
16284
16304
  return 0;
16285
16305
  }
16286
16306
  const rulesyncFiles = await mcpProcessor.convertToolFilesToRulesyncFiles(toolFiles);
@@ -16307,6 +16327,7 @@ async function importCommandsCore(params) {
16307
16327
  });
16308
16328
  const toolFiles = await commandsProcessor.loadToolFiles();
16309
16329
  if (toolFiles.length === 0) {
16330
+ logger.warn(`No command files found for ${tool}. Skipping import.`);
16310
16331
  return 0;
16311
16332
  }
16312
16333
  const rulesyncFiles = await commandsProcessor.convertToolFilesToRulesyncFiles(toolFiles);
@@ -16333,6 +16354,7 @@ async function importSubagentsCore(params) {
16333
16354
  });
16334
16355
  const toolFiles = await subagentsProcessor.loadToolFiles();
16335
16356
  if (toolFiles.length === 0) {
16357
+ logger.warn(`No subagent files found for ${tool}. Skipping import.`);
16336
16358
  return 0;
16337
16359
  }
16338
16360
  const rulesyncFiles = await subagentsProcessor.convertToolFilesToRulesyncFiles(toolFiles);
@@ -16359,6 +16381,7 @@ async function importSkillsCore(params) {
16359
16381
  });
16360
16382
  const toolDirs = await skillsProcessor.loadToolDirs();
16361
16383
  if (toolDirs.length === 0) {
16384
+ logger.warn(`No skill directories found for ${tool}. Skipping import.`);
16362
16385
  return 0;
16363
16386
  }
16364
16387
  const rulesyncDirs = await skillsProcessor.convertToolDirsToRulesyncDirs(toolDirs);
@@ -16390,6 +16413,7 @@ async function importHooksCore(params) {
16390
16413
  });
16391
16414
  const toolFiles = await hooksProcessor.loadToolFiles();
16392
16415
  if (toolFiles.length === 0) {
16416
+ logger.warn(`No hooks files found for ${tool}. Skipping import.`);
16393
16417
  return 0;
16394
16418
  }
16395
16419
  const rulesyncFiles = await hooksProcessor.convertToolFilesToRulesyncFiles(toolFiles);