rulesync 22.0.0 → 23.0.0

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.
@@ -444,36 +444,36 @@ const isFeatureValueEnabled = (value) => {
444
444
  const parseCommaSeparatedList = (value) => value.split(",").map((s) => s.trim()).filter(Boolean);
445
445
  //#endregion
446
446
  //#region src/constants/rulesync-paths.ts
447
- const { join: join$351 } = node_path.posix;
447
+ const { join: join$352 } = node_path.posix;
448
448
  const RULESYNC_CONFIG_RELATIVE_FILE_PATH = "rulesync.jsonc";
449
449
  const RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH = "rulesync.local.jsonc";
450
450
  const RULESYNC_USER_CONFIG_DIR_NAME = "rulesync";
451
451
  const RULESYNC_XDG_CONFIG_HOME_DEFAULT_DIR_NAME = ".config";
452
452
  const RULESYNC_RELATIVE_DIR_PATH = ".rulesync";
453
453
  const RULES_FEATURE_SUBDIR = "rules";
454
- const CURATED_RULES_FEATURE_SUBDIR = join$351(RULES_FEATURE_SUBDIR, ".curated");
454
+ const CURATED_RULES_FEATURE_SUBDIR = join$352(RULES_FEATURE_SUBDIR, ".curated");
455
455
  const COMMANDS_FEATURE_SUBDIR = "commands";
456
456
  const SUBAGENTS_FEATURE_SUBDIR = "subagents";
457
457
  const CHECKS_FEATURE_SUBDIR = "checks";
458
458
  const SKILLS_FEATURE_SUBDIR = "skills";
459
- const CURATED_SKILLS_FEATURE_SUBDIR = join$351(SKILLS_FEATURE_SUBDIR, ".curated");
460
- const RULESYNC_RULES_RELATIVE_DIR_PATH = join$351(RULESYNC_RELATIVE_DIR_PATH, RULES_FEATURE_SUBDIR);
461
- const RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH = join$351(RULESYNC_RELATIVE_DIR_PATH, CURATED_RULES_FEATURE_SUBDIR);
462
- const RULESYNC_COMMANDS_RELATIVE_DIR_PATH = join$351(RULESYNC_RELATIVE_DIR_PATH, COMMANDS_FEATURE_SUBDIR);
463
- const RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH = join$351(RULESYNC_RELATIVE_DIR_PATH, SUBAGENTS_FEATURE_SUBDIR);
464
- const RULESYNC_CHECKS_RELATIVE_DIR_PATH = join$351(RULESYNC_RELATIVE_DIR_PATH, CHECKS_FEATURE_SUBDIR);
465
- const RULESYNC_MCP_RELATIVE_FILE_PATH = join$351(RULESYNC_RELATIVE_DIR_PATH, "mcp.jsonc");
466
- const RULESYNC_HOOKS_RELATIVE_FILE_PATH = join$351(RULESYNC_RELATIVE_DIR_PATH, "hooks.jsonc");
467
- const RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH = join$351(RULESYNC_RELATIVE_DIR_PATH, "permissions.jsonc");
468
- join$351(RULESYNC_RELATIVE_DIR_PATH, "mcp.json");
469
- const RULESYNC_HOOKS_LEGACY_RELATIVE_FILE_PATH = join$351(RULESYNC_RELATIVE_DIR_PATH, "hooks.json");
470
- const RULESYNC_PERMISSIONS_LEGACY_RELATIVE_FILE_PATH = join$351(RULESYNC_RELATIVE_DIR_PATH, "permissions.json");
459
+ const CURATED_SKILLS_FEATURE_SUBDIR = join$352(SKILLS_FEATURE_SUBDIR, ".curated");
460
+ const RULESYNC_RULES_RELATIVE_DIR_PATH = join$352(RULESYNC_RELATIVE_DIR_PATH, RULES_FEATURE_SUBDIR);
461
+ const RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH = join$352(RULESYNC_RELATIVE_DIR_PATH, CURATED_RULES_FEATURE_SUBDIR);
462
+ const RULESYNC_COMMANDS_RELATIVE_DIR_PATH = join$352(RULESYNC_RELATIVE_DIR_PATH, COMMANDS_FEATURE_SUBDIR);
463
+ const RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH = join$352(RULESYNC_RELATIVE_DIR_PATH, SUBAGENTS_FEATURE_SUBDIR);
464
+ const RULESYNC_CHECKS_RELATIVE_DIR_PATH = join$352(RULESYNC_RELATIVE_DIR_PATH, CHECKS_FEATURE_SUBDIR);
465
+ const RULESYNC_MCP_RELATIVE_FILE_PATH = join$352(RULESYNC_RELATIVE_DIR_PATH, "mcp.jsonc");
466
+ const RULESYNC_HOOKS_RELATIVE_FILE_PATH = join$352(RULESYNC_RELATIVE_DIR_PATH, "hooks.jsonc");
467
+ const RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH = join$352(RULESYNC_RELATIVE_DIR_PATH, "permissions.jsonc");
468
+ join$352(RULESYNC_RELATIVE_DIR_PATH, "mcp.json");
469
+ const RULESYNC_HOOKS_LEGACY_RELATIVE_FILE_PATH = join$352(RULESYNC_RELATIVE_DIR_PATH, "hooks.json");
470
+ const RULESYNC_PERMISSIONS_LEGACY_RELATIVE_FILE_PATH = join$352(RULESYNC_RELATIVE_DIR_PATH, "permissions.json");
471
471
  const RULESYNC_AIIGNORE_FILE_NAME = ".aiignore";
472
- const RULESYNC_AIIGNORE_RELATIVE_FILE_PATH = join$351(RULESYNC_RELATIVE_DIR_PATH, ".aiignore");
472
+ const RULESYNC_AIIGNORE_RELATIVE_FILE_PATH = join$352(RULESYNC_RELATIVE_DIR_PATH, ".aiignore");
473
473
  const RULESYNC_IGNORE_RELATIVE_FILE_PATH = ".rulesyncignore";
474
474
  const RULESYNC_OVERVIEW_FILE_NAME = "overview.md";
475
- const RULESYNC_SKILLS_RELATIVE_DIR_PATH = join$351(RULESYNC_RELATIVE_DIR_PATH, SKILLS_FEATURE_SUBDIR);
476
- const RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH = join$351(RULESYNC_RELATIVE_DIR_PATH, CURATED_SKILLS_FEATURE_SUBDIR);
475
+ const RULESYNC_SKILLS_RELATIVE_DIR_PATH = join$352(RULESYNC_RELATIVE_DIR_PATH, SKILLS_FEATURE_SUBDIR);
476
+ const RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH = join$352(RULESYNC_RELATIVE_DIR_PATH, CURATED_SKILLS_FEATURE_SUBDIR);
477
477
  const RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH = "rulesync.lock";
478
478
  const RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH = "rulesync-npm.lock.json";
479
479
  const RULESYNC_MCP_FILE_NAME = "mcp.jsonc";
@@ -2728,6 +2728,7 @@ var Config = class Config {
2728
2728
  */
2729
2729
  objectFormTargetKeys;
2730
2730
  configFileTargets;
2731
+ configFileSelection;
2731
2732
  verbose;
2732
2733
  delete;
2733
2734
  global;
@@ -2762,7 +2763,7 @@ var Config = class Config {
2762
2763
  inputRoots;
2763
2764
  configFilePath;
2764
2765
  sources;
2765
- constructor({ outputRoots, targets, features, verbose, delete: isDelete, global, silent, simulateCommands, simulateSubagents, simulateSkills, preserveUnownedHooks, deriveSubprojectPathFromGlobs, flattenedCommandNaming, language, gitignoreTargetsOnly, gitignoreDestination, dryRun, check, inputRoot, inputRoots, configFilePath, sources, configFileTargets }) {
2766
+ constructor({ outputRoots, targets, features, verbose, delete: isDelete, global, silent, simulateCommands, simulateSubagents, simulateSkills, preserveUnownedHooks, deriveSubprojectPathFromGlobs, flattenedCommandNaming, language, gitignoreTargetsOnly, gitignoreDestination, dryRun, check, inputRoot, inputRoots, configFilePath, sources, configFileTargets, configFileSelection }) {
2766
2767
  assertTargetsFeaturesExclusive({
2767
2768
  targets,
2768
2769
  features
@@ -2787,6 +2788,7 @@ var Config = class Config {
2787
2788
  this.features = resolvedFeatures;
2788
2789
  this.objectFormTargetKeys = isRulesyncConfigTargetsObject(resolvedTargets) ? Config.filterValidToolTargets(Object.keys(resolvedTargets)) : void 0;
2789
2790
  this.configFileTargets = configFileTargets;
2791
+ this.configFileSelection = configFileSelection;
2790
2792
  this.verbose = verbose;
2791
2793
  this.delete = isDelete;
2792
2794
  this.global = global ?? false;
@@ -2877,17 +2879,54 @@ var Config = class Config {
2877
2879
  getConfigFileTargets() {
2878
2880
  return this.configFileTargets ?? this.getTargets();
2879
2881
  }
2882
+ /**
2883
+ * The features `target` has for a run over the configuration file's
2884
+ * targets, i.e. what `rulesync generate` without `--targets` would produce
2885
+ * for it (a CLI `--features` still applies to array-form targets). Falls
2886
+ * back to {@link getFeatures} when no configuration file declared `targets`.
2887
+ */
2888
+ getConfigFileFeatures(target) {
2889
+ const selection = this.configFileSelection;
2890
+ if (selection === void 0) return this.getFeatures(target);
2891
+ return Config.resolveTargetFeatures({
2892
+ targets: selection.targets,
2893
+ features: selection.features ?? [],
2894
+ target
2895
+ });
2896
+ }
2897
+ /**
2898
+ * The options `target` sets for `feature` in the configuration file, for
2899
+ * the same run as {@link getConfigFileFeatures}. Falls back to
2900
+ * {@link getFeatureOptions} when no configuration file declared `targets`.
2901
+ */
2902
+ getConfigFileFeatureOptions(target, feature) {
2903
+ const selection = this.configFileSelection;
2904
+ if (selection === void 0) return this.getFeatureOptions(target, feature);
2905
+ return Config.resolveFeatureOptions({
2906
+ targets: selection.targets,
2907
+ target,
2908
+ feature
2909
+ });
2910
+ }
2880
2911
  getFeatures(target) {
2881
- if (isRulesyncConfigTargetsObject(this.targets)) {
2882
- if (target) {
2883
- const value = this.targets[target];
2884
- if (!value) return [];
2885
- return Config.normalizeTargetFeatures(value);
2886
- }
2887
- return Config.collectAllFeatures(Object.values(this.targets));
2912
+ if (target) return Config.resolveTargetFeatures({
2913
+ targets: this.targets,
2914
+ features: this.features,
2915
+ target
2916
+ });
2917
+ if (isRulesyncConfigTargetsObject(this.targets)) return Config.collectAllFeatures(Object.values(this.targets));
2918
+ return Config.normalizeFeatureList(this.features);
2919
+ }
2920
+ static resolveTargetFeatures({ targets, features, target }) {
2921
+ if (isRulesyncConfigTargetsObject(targets)) {
2922
+ const value = targets[target];
2923
+ return value ? Config.normalizeTargetFeatures(value) : [];
2888
2924
  }
2889
- if (this.features.includes("*")) return [...ALL_FEATURES];
2890
- return this.features.filter((feature) => feature !== "*");
2925
+ return Config.normalizeFeatureList(features);
2926
+ }
2927
+ static normalizeFeatureList(features) {
2928
+ if (features.includes("*")) return [...ALL_FEATURES];
2929
+ return features.filter((feature) => feature !== "*");
2891
2930
  }
2892
2931
  /**
2893
2932
  * Normalize a per-target features value (array or per-feature object) into
@@ -2928,7 +2967,14 @@ var Config = class Config {
2928
2967
  * feature is not enabled for the given target.
2929
2968
  */
2930
2969
  getFeatureOptions(target, feature) {
2931
- const value = isRulesyncConfigTargetsObject(this.targets) ? this.targets[target] : void 0;
2970
+ return Config.resolveFeatureOptions({
2971
+ targets: this.targets,
2972
+ target,
2973
+ feature
2974
+ });
2975
+ }
2976
+ static resolveFeatureOptions({ targets, target, feature }) {
2977
+ const value = isRulesyncConfigTargetsObject(targets) ? targets[target] : void 0;
2932
2978
  if (!value || Array.isArray(value)) return;
2933
2979
  const featureValue = value[feature];
2934
2980
  if (featureValue && typeof featureValue === "object" && isFeatureValueEnabled(featureValue)) return featureValue;
@@ -3363,7 +3409,15 @@ var ConfigResolver = class {
3363
3409
  sources: configByFile.sources ?? getDefaults().sources,
3364
3410
  flattenedCommandNaming: configByFile.flattenedCommandNaming ?? getDefaults().flattenedCommandNaming,
3365
3411
  language: configByFile.language,
3366
- configFileTargets: extractConfigFileTargets(configByFile.targets)
3412
+ configFileTargets: extractConfigFileTargets(configByFile.targets),
3413
+ configFileSelection: configByFile.targets === void 0 ? void 0 : {
3414
+ targets: configByFile.targets,
3415
+ features: resolveFeaturesAndTargets({
3416
+ features,
3417
+ targets: void 0,
3418
+ configByFile
3419
+ }).resolvedFeatures
3420
+ }
3367
3421
  });
3368
3422
  }
3369
3423
  };
@@ -14965,6 +15019,7 @@ const ANTIGRAVITY_GLOBAL_AGENTS_DIR_PATH = (0, node_path.join)(ANTIGRAVITY_GLOBA
14965
15019
  const ANTIGRAVITY_AGENTS_DIR = ANTIGRAVITY_DIR;
14966
15020
  const ANTIGRAVITY_RULE_FILE_NAME = "AGENTS.md";
14967
15021
  const ANTIGRAVITY_GLOBAL_RULE_FILE_NAME = "GEMINI.md";
15022
+ const ANTIGRAVITY_GLOBAL_RULES_SUBDIR = (0, node_path.join)(ANTIGRAVITY_GLOBAL_CONFIG_SUBDIR, "rules");
14968
15023
  //#endregion
14969
15024
  //#region src/features/commands/antigravity-command.ts
14970
15025
  const AntigravityWorkflowFrontmatterSchema = zod_mini.z.looseObject({
@@ -73162,97 +73217,6 @@ var AmpRule = class AmpRule extends ToolRule {
73162
73217
  }
73163
73218
  };
73164
73219
  //#endregion
73165
- //#region src/features/rules/antigravity-cli-rule.ts
73166
- /**
73167
- * Rule generator for the Google Antigravity CLI (`agy`, the Gemini-CLI
73168
- * successor in Antigravity 2.0).
73169
- *
73170
- * The CLI reads the same plain-markdown context files as Gemini CLI — a root
73171
- * context file plus non-root memory files in `.agents/rules/` — so this class
73172
- * follows that same plain-markdown approach but points at the new `.agents/` tree.
73173
- *
73174
- * - Project scope: root `AGENTS.md` (the cross-tool standard, matching
73175
- * `antigravity-ide`); non-root `.agents/rules/*.md`.
73176
- * - Global scope: a single plain `~/.gemini/GEMINI.md` (shared with the IDE).
73177
- */
73178
- var AntigravityCliRule = class AntigravityCliRule extends ToolRule {
73179
- static getSettablePaths({ global, excludeToolDir } = {}) {
73180
- if (global) return { root: {
73181
- relativeDirPath: buildToolPath(ANTIGRAVITY_GEMINI_DIR, ".", excludeToolDir),
73182
- relativeFilePath: ANTIGRAVITY_GLOBAL_RULE_FILE_NAME
73183
- } };
73184
- return {
73185
- root: {
73186
- relativeDirPath: ".",
73187
- relativeFilePath: ANTIGRAVITY_RULE_FILE_NAME
73188
- },
73189
- nonRoot: { relativeDirPath: buildToolPath(ANTIGRAVITY_AGENTS_DIR, "rules", excludeToolDir) }
73190
- };
73191
- }
73192
- static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
73193
- const paths = this.getSettablePaths({ global });
73194
- if (relativeFilePath === paths.root.relativeFilePath) {
73195
- const relativePath = paths.root.relativeFilePath;
73196
- const fileContent = await readFileContent((0, node_path.join)(outputRoot, paths.root.relativeDirPath, relativePath));
73197
- return new AntigravityCliRule({
73198
- outputRoot,
73199
- relativeDirPath: paths.root.relativeDirPath,
73200
- relativeFilePath: paths.root.relativeFilePath,
73201
- fileContent,
73202
- validate,
73203
- root: true
73204
- });
73205
- }
73206
- if (!paths.nonRoot) throw new Error(`nonRoot path is not set for ${relativeFilePath}`);
73207
- const relativePath = (0, node_path.join)(paths.nonRoot.relativeDirPath, relativeFilePath);
73208
- const fileContent = await readFileContent((0, node_path.join)(outputRoot, relativePath));
73209
- return new AntigravityCliRule({
73210
- outputRoot,
73211
- relativeDirPath: paths.nonRoot.relativeDirPath,
73212
- relativeFilePath,
73213
- fileContent,
73214
- validate,
73215
- root: false
73216
- });
73217
- }
73218
- static fromRulesyncRule({ outputRoot = process.cwd(), rulesyncRule, validate = true, global = false }) {
73219
- const paths = this.getSettablePaths({ global });
73220
- return new AntigravityCliRule(this.buildToolRuleParamsDefault({
73221
- outputRoot,
73222
- rulesyncRule,
73223
- validate,
73224
- rootPath: paths.root,
73225
- nonRootPath: paths.nonRoot
73226
- }));
73227
- }
73228
- toRulesyncRule() {
73229
- return this.toRulesyncRuleDefault();
73230
- }
73231
- validate() {
73232
- return {
73233
- success: true,
73234
- error: null
73235
- };
73236
- }
73237
- static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
73238
- const isRoot = relativeFilePath === this.getSettablePaths({ global }).root.relativeFilePath;
73239
- return new AntigravityCliRule({
73240
- outputRoot,
73241
- relativeDirPath,
73242
- relativeFilePath,
73243
- fileContent: "",
73244
- validate: false,
73245
- root: isRoot
73246
- });
73247
- }
73248
- static isTargetedByRulesyncRule(rulesyncRule) {
73249
- return this.isTargetedByRulesyncRuleDefault({
73250
- rulesyncRule,
73251
- toolTarget: "antigravity-cli"
73252
- });
73253
- }
73254
- };
73255
- //#endregion
73256
73220
  //#region src/features/rules/antigravity-rule.ts
73257
73221
  const AntigravityRuleFrontmatterSchema = zod_mini.z.looseObject({
73258
73222
  trigger: zod_mini.z.optional(zod_mini.z.union([
@@ -73399,6 +73363,185 @@ const STRATEGIES$1 = [
73399
73363
  }
73400
73364
  ];
73401
73365
  //#endregion
73366
+ //#region src/features/rules/antigravity-cli-rule.ts
73367
+ /**
73368
+ * Rule generator for the Google Antigravity CLI (`agy`, the Gemini-CLI
73369
+ * successor in Antigravity 2.0).
73370
+ *
73371
+ * The CLI reads the same plain-markdown context files as Gemini CLI — a root
73372
+ * context file plus non-root memory files in `.agents/rules/` — so this class
73373
+ * follows that same plain-markdown approach but points at the new `.agents/` tree.
73374
+ *
73375
+ * - Project scope: root `AGENTS.md` (the cross-tool standard, matching
73376
+ * `antigravity-ide`); non-root `.agents/rules/*.md`.
73377
+ * - Global scope: root plain `~/.gemini/GEMINI.md` (shared with the IDE);
73378
+ * non-root `~/.gemini/config/rules/*.md`. The CLI truncates each rule file at
73379
+ * 24,000 bytes, so keeping non-root rules as separate files stops a large rule
73380
+ * set from being cut off. Every file in that directory must carry a valid
73381
+ * `trigger` frontmatter or the CLI discards it, so these files get the same
73382
+ * trigger-strategy frontmatter as the IDE's non-root rules.
73383
+ *
73384
+ * @see https://antigravity.google/docs/rules
73385
+ */
73386
+ var AntigravityCliRule = class AntigravityCliRule extends ToolRule {
73387
+ static getSettablePaths({ global, excludeToolDir } = {}) {
73388
+ if (global) return {
73389
+ root: {
73390
+ relativeDirPath: buildToolPath(ANTIGRAVITY_GEMINI_DIR, ".", excludeToolDir),
73391
+ relativeFilePath: ANTIGRAVITY_GLOBAL_RULE_FILE_NAME
73392
+ },
73393
+ nonRoot: { relativeDirPath: buildToolPath(ANTIGRAVITY_GEMINI_DIR, ANTIGRAVITY_GLOBAL_RULES_SUBDIR, excludeToolDir) }
73394
+ };
73395
+ return {
73396
+ root: {
73397
+ relativeDirPath: ".",
73398
+ relativeFilePath: ANTIGRAVITY_RULE_FILE_NAME
73399
+ },
73400
+ nonRoot: { relativeDirPath: buildToolPath(ANTIGRAVITY_AGENTS_DIR, "rules", excludeToolDir) }
73401
+ };
73402
+ }
73403
+ static async fromFile({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, validate = true, global = false }) {
73404
+ const paths = this.getSettablePaths({ global });
73405
+ if (relativeFilePath === paths.root.relativeFilePath && (relativeDirPath === void 0 || relativeDirPath === paths.root.relativeDirPath)) {
73406
+ const relativePath = paths.root.relativeFilePath;
73407
+ const fileContent = await readFileContent((0, node_path.join)(outputRoot, paths.root.relativeDirPath, relativePath));
73408
+ return new AntigravityCliRule({
73409
+ outputRoot,
73410
+ relativeDirPath: paths.root.relativeDirPath,
73411
+ relativeFilePath: paths.root.relativeFilePath,
73412
+ fileContent,
73413
+ validate,
73414
+ root: true,
73415
+ global
73416
+ });
73417
+ }
73418
+ if (!paths.nonRoot) throw new Error(`nonRoot path is not set for ${relativeFilePath}`);
73419
+ const relativePath = (0, node_path.join)(paths.nonRoot.relativeDirPath, relativeFilePath);
73420
+ const fileContent = await readFileContent((0, node_path.join)(outputRoot, relativePath));
73421
+ const rule = new AntigravityCliRule({
73422
+ outputRoot,
73423
+ relativeDirPath: paths.nonRoot.relativeDirPath,
73424
+ relativeFilePath,
73425
+ fileContent,
73426
+ validate,
73427
+ root: false,
73428
+ global
73429
+ });
73430
+ if (validate) {
73431
+ const result = rule.validate();
73432
+ if (!result.success) throw new Error(`Invalid frontmatter in ${relativePath}: ${result.error.message}`);
73433
+ }
73434
+ return rule;
73435
+ }
73436
+ static fromRulesyncRule({ outputRoot = process.cwd(), rulesyncRule, validate = true, global = false }) {
73437
+ const paths = this.getSettablePaths({ global });
73438
+ const params = this.buildToolRuleParamsDefault({
73439
+ outputRoot,
73440
+ rulesyncRule,
73441
+ validate,
73442
+ rootPath: paths.root,
73443
+ nonRootPath: paths.nonRoot
73444
+ });
73445
+ if (!global || params.root) return new AntigravityCliRule({
73446
+ ...params,
73447
+ global
73448
+ });
73449
+ const rulesyncFrontmatter = rulesyncRule.getFrontmatter();
73450
+ const storedAntigravity = rulesyncFrontmatter.antigravity;
73451
+ const storedTrigger = storedAntigravity?.trigger;
73452
+ const strategy = STRATEGIES$1.find((s) => s.canHandle(storedTrigger));
73453
+ if (!strategy) throw new Error(`No strategy found for trigger: ${storedTrigger}`);
73454
+ const frontmatter = strategy.generateFrontmatter(normalizeStoredAntigravity(storedAntigravity), rulesyncFrontmatter);
73455
+ return new AntigravityCliRule({
73456
+ ...params,
73457
+ relativeFilePath: params.relativeFilePath.split(/[\\/]/).join("-"),
73458
+ fileContent: stringifyFrontmatter(rulesyncRule.getBody(), frontmatter),
73459
+ global
73460
+ });
73461
+ }
73462
+ toRulesyncRule() {
73463
+ if (!this.global || this.root) return this.toRulesyncRuleDefault();
73464
+ const { frontmatter, body } = this.parseGlobalNonRootContent();
73465
+ const strategy = STRATEGIES$1.find((s) => s.canHandle(frontmatter.trigger));
73466
+ const rulesyncData = strategy ? strategy.exportRulesyncData(frontmatter) : {
73467
+ globs: [],
73468
+ antigravity: frontmatter
73469
+ };
73470
+ return new RulesyncRule({
73471
+ outputRoot: process.cwd(),
73472
+ relativeDirPath: RulesyncRule.getSettablePaths().recommended.relativeDirPath,
73473
+ relativeFilePath: this.getRelativeFilePath(),
73474
+ frontmatter: {
73475
+ root: false,
73476
+ targets: ["*"],
73477
+ ...rulesyncData,
73478
+ antigravity: {
73479
+ ...rulesyncData.antigravity,
73480
+ globs: frontmatter.globs ? parseGlobsString$1(frontmatter.globs) : void 0
73481
+ }
73482
+ },
73483
+ body
73484
+ });
73485
+ }
73486
+ validate() {
73487
+ if (!this.global || this.root) return {
73488
+ success: true,
73489
+ error: null
73490
+ };
73491
+ try {
73492
+ const { frontmatter } = this.parseGlobalNonRootContent();
73493
+ const result = AntigravityRuleFrontmatterSchema.safeParse(frontmatter);
73494
+ if (!result.success) return {
73495
+ success: false,
73496
+ error: new Error(formatError(result.error))
73497
+ };
73498
+ } catch (error) {
73499
+ return {
73500
+ success: false,
73501
+ error: error instanceof Error ? error : new Error(String(error))
73502
+ };
73503
+ }
73504
+ return {
73505
+ success: true,
73506
+ error: null
73507
+ };
73508
+ }
73509
+ /**
73510
+ * `~/.gemini/config/rules/` is shared with the Antigravity IDE and holds
73511
+ * global rules the user created outside rulesync, which cannot be told apart
73512
+ * from generated ones, so the orphan sweep never removes files there.
73513
+ */
73514
+ isDeletable() {
73515
+ return !this.global || this.root;
73516
+ }
73517
+ parseGlobalNonRootContent() {
73518
+ const { frontmatter, body } = parseFrontmatter(this.getFileContent(), this.getFilePath());
73519
+ return {
73520
+ frontmatter,
73521
+ body
73522
+ };
73523
+ }
73524
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
73525
+ const paths = this.getSettablePaths({ global });
73526
+ const isRoot = relativeFilePath === paths.root.relativeFilePath && relativeDirPath === paths.root.relativeDirPath;
73527
+ return new AntigravityCliRule({
73528
+ outputRoot,
73529
+ relativeDirPath,
73530
+ relativeFilePath,
73531
+ fileContent: "",
73532
+ validate: false,
73533
+ root: isRoot,
73534
+ global
73535
+ });
73536
+ }
73537
+ static isTargetedByRulesyncRule(rulesyncRule) {
73538
+ return this.isTargetedByRulesyncRuleDefault({
73539
+ rulesyncRule,
73540
+ toolTarget: "antigravity-cli"
73541
+ });
73542
+ }
73543
+ };
73544
+ //#endregion
73402
73545
  //#region src/features/rules/antigravity-ide-rule.ts
73403
73546
  /**
73404
73547
  * Rule generator for the Google Antigravity IDE (Antigravity 2.0).
@@ -79025,7 +79168,9 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
79025
79168
  meta: {
79026
79169
  extension: "md",
79027
79170
  supportsGlobal: true,
79028
- ruleDiscoveryMode: "toon"
79171
+ ruleDiscoveryMode: "toon",
79172
+ ruleDiscoveryModeGlobal: "auto",
79173
+ sharedGlobalNonRootDir: true
79029
79174
  }
79030
79175
  }],
79031
79176
  ["antigravity-ide", {
@@ -80104,7 +80249,7 @@ var RulesProcessor = class extends FeatureProcessor {
80104
80249
  */
80105
80250
  generateReferenceSectionFromMeta(meta, toolRules) {
80106
80251
  switch (resolveRuleDiscoveryMode({
80107
- defaultMode: meta.ruleDiscoveryMode,
80252
+ defaultMode: this.global && meta.ruleDiscoveryModeGlobal ? meta.ruleDiscoveryModeGlobal : meta.ruleDiscoveryMode,
80108
80253
  options: this.featureOptions
80109
80254
  })) {
80110
80255
  case "toon": return this.generateToonReferencesSection(toolRules);
@@ -80623,7 +80768,8 @@ As this project's AI coding tool, you must follow the additional conventions bel
80623
80768
  const nonRootToolRules = await (async () => {
80624
80769
  if (!settablePaths.nonRoot) return [];
80625
80770
  const nonRootOutputRoot = (0, node_path.join)(this.outputRoot, settablePaths.nonRoot.relativeDirPath);
80626
- const nonRootFilePaths = await findFilesByGlobs(`**/*.${factory.meta.extension}`, { cwd: nonRootOutputRoot });
80771
+ const isSharedGlobalNonRootDir = this.global && factory.meta.sharedGlobalNonRootDir === true;
80772
+ const nonRootFilePaths = await findFilesByGlobs(`${isSharedGlobalNonRootDir ? "" : "**/"}*.${factory.meta.extension}`, { cwd: nonRootOutputRoot });
80627
80773
  if (forDeletion) return buildDeletionRulesFromPaths(nonRootFilePaths, {
80628
80774
  outputRootOverride: nonRootOutputRoot,
80629
80775
  relativeDirPathOverride: settablePaths.nonRoot.relativeDirPath
@@ -80636,19 +80782,25 @@ As this project's AI coding tool, you must follow the additional conventions bel
80636
80782
  if (!ok) this.logger.warn(`Skipping reserved Rovodev path under modular-rules (import): ${(0, node_path.join)(modularRootRelative, relativeFilePath)}`);
80637
80783
  return ok;
80638
80784
  }) : nonRootFilePaths).filter((filePath) => rootFileNameInSameDir === void 0 || (0, node_path.relative)(nonRootOutputRoot, filePath) !== rootFileNameInSameDir);
80639
- return await Promise.all(nonRootPathsForImport.map((filePath) => {
80785
+ return (await Promise.all(nonRootPathsForImport.map(async (filePath) => {
80640
80786
  const relativeFilePath = (0, node_path.relative)(nonRootOutputRoot, filePath);
80641
80787
  checkPathTraversal({
80642
80788
  relativePath: relativeFilePath,
80643
80789
  intendedRootDir: nonRootOutputRoot
80644
80790
  });
80645
- return factory.class.fromFile({
80646
- outputRoot: this.outputRoot,
80647
- relativeDirPath: modularRootRelative,
80648
- relativeFilePath,
80649
- global: this.global
80650
- });
80651
- }));
80791
+ try {
80792
+ return await factory.class.fromFile({
80793
+ outputRoot: this.outputRoot,
80794
+ relativeDirPath: modularRootRelative,
80795
+ relativeFilePath,
80796
+ global: this.global
80797
+ });
80798
+ } catch (error) {
80799
+ if (!isSharedGlobalNonRootDir) throw error;
80800
+ this.logger.warn(`Skipping ${stripControlCharacters((0, node_path.join)(modularRootRelative, relativeFilePath))} for ${this.toolTarget}: ${formatError(error)}`);
80801
+ return;
80802
+ }
80803
+ }))).filter((rule) => rule !== void 0);
80652
80804
  })();
80653
80805
  this.logger.debug(`Found ${nonRootToolRules.length} non-root tool rule files`);
80654
80806
  return [
@@ -82042,6 +82194,115 @@ function computeRootFileOwnership(params) {
82042
82194
  }
82043
82195
  return ownerByPath;
82044
82196
  }
82197
+ function createSkippedRootFileCheck({ rootFileOwner }) {
82198
+ const skippedByRoot = /* @__PURE__ */ new Map();
82199
+ const emittedByRoot = /* @__PURE__ */ new Map();
82200
+ const emittedKey = (toolTarget, relativePath) => `${toolTarget}\0${relativePath}`;
82201
+ const loadOwnerToolFiles = async (processor) => {
82202
+ const rulesyncFiles = await processor.loadRulesyncFiles();
82203
+ return {
82204
+ toolFiles: rulesyncFiles.length > 0 ? await processor.convertRulesyncFilesToToolFiles(rulesyncFiles) : [],
82205
+ sourceLoadFailed: processor.hasRulesyncSourceLoadFailure()
82206
+ };
82207
+ };
82208
+ return {
82209
+ observe: ({ toolTarget, outputRoot, processor, toolFiles, skipFilePaths }) => {
82210
+ if (rootFileOwner.size === 0) return;
82211
+ for (const toolFile of toolFiles) {
82212
+ const relativePath = toolFile.getRelativePathFromCwd();
82213
+ if (!rootFileOwner.has(relativePath)) continue;
82214
+ const emitted = emittedByRoot.get(outputRoot) ?? /* @__PURE__ */ new Set();
82215
+ emittedByRoot.set(outputRoot, emitted);
82216
+ emitted.add(emittedKey(toolTarget, relativePath));
82217
+ if (!skipFilePaths.has(relativePath)) continue;
82218
+ const skipped = skippedByRoot.get(outputRoot) ?? /* @__PURE__ */ new Map();
82219
+ skippedByRoot.set(outputRoot, skipped);
82220
+ skipped.set(relativePath, {
82221
+ processor,
82222
+ toolFile
82223
+ });
82224
+ }
82225
+ },
82226
+ verify: async ({ isOwnerProcessed, createOwnerProcessor }) => {
82227
+ let count = 0;
82228
+ const paths = [];
82229
+ let sourceLoadFailed = false;
82230
+ for (const [outputRoot, skipped] of skippedByRoot) {
82231
+ const ownerOutputs = /* @__PURE__ */ new Map();
82232
+ for (const [relativePath, fallback] of skipped) {
82233
+ const owner = rootFileOwner.get(relativePath);
82234
+ if (owner === void 0) continue;
82235
+ if (emittedByRoot.get(outputRoot)?.has(emittedKey(owner, relativePath))) continue;
82236
+ let expected = fallback;
82237
+ if (!isOwnerProcessed(owner)) {
82238
+ if (!ownerOutputs.has(owner)) {
82239
+ const processor = await createOwnerProcessor({
82240
+ toolTarget: owner,
82241
+ outputRoot
82242
+ });
82243
+ if (processor === void 0) ownerOutputs.set(owner, void 0);
82244
+ else {
82245
+ const loaded = await loadOwnerToolFiles(processor);
82246
+ if (loaded.sourceLoadFailed) sourceLoadFailed = true;
82247
+ ownerOutputs.set(owner, {
82248
+ processor,
82249
+ toolFiles: loaded.toolFiles
82250
+ });
82251
+ }
82252
+ }
82253
+ const ownerOutput = ownerOutputs.get(owner);
82254
+ const ownerFile = ownerOutput?.toolFiles.find((file) => file.getRelativePathFromCwd() === relativePath && file.getFilePath() === fallback.toolFile.getFilePath());
82255
+ if (ownerOutput !== void 0 && ownerFile !== void 0) expected = {
82256
+ processor: ownerOutput.processor,
82257
+ toolFile: ownerFile
82258
+ };
82259
+ }
82260
+ const result = await expected.processor.writeAiFiles([expected.toolFile]);
82261
+ count += result.count;
82262
+ paths.push(...result.paths);
82263
+ }
82264
+ }
82265
+ return {
82266
+ count,
82267
+ paths,
82268
+ hasDiff: count > 0,
82269
+ sourceLoadFailed
82270
+ };
82271
+ }
82272
+ };
82273
+ }
82274
+ /**
82275
+ * The skills a full `rulesync generate` hands to the rules step: what the
82276
+ * skills step collects for every config-file target that generates skills, in
82277
+ * the same order. Simulated skill sections in a root file list these, so a
82278
+ * narrowed check needs them to rebuild another target's root file.
82279
+ */
82280
+ async function loadConfigFileSkills({ config, logger }) {
82281
+ const supportedSkillsTargets = SkillsProcessor.getToolTargets({
82282
+ global: config.getGlobal(),
82283
+ includeSimulated: config.getSimulateSkills()
82284
+ });
82285
+ const skills = [];
82286
+ for (const toolTarget of (0, es_toolkit.intersection)(config.getConfigFileTargets(), supportedSkillsTargets)) {
82287
+ if (!config.getConfigFileFeatures(toolTarget).includes("skills")) continue;
82288
+ for (const outputRoot of config.getOutputRoots(toolTarget)) {
82289
+ const processor = new SkillsProcessor({
82290
+ outputRoot: resolveToolOutputRoot({
82291
+ outputRoot,
82292
+ toolTarget,
82293
+ global: config.getGlobal()
82294
+ }),
82295
+ inputRoots: config.getInputRoots(),
82296
+ toolTarget,
82297
+ global: config.getGlobal(),
82298
+ dryRun: true,
82299
+ logger
82300
+ });
82301
+ for (const rulesyncDir of await processor.loadRulesyncDirs()) if (rulesyncDir instanceof RulesyncSkill) skills.push(rulesyncDir);
82302
+ }
82303
+ }
82304
+ return skills;
82305
+ }
82045
82306
  async function generateRulesCore(params) {
82046
82307
  const { config, logger, sweepPlan, skills } = params;
82047
82308
  let totalCount = 0;
@@ -82057,32 +82318,47 @@ async function generateRulesCore(params) {
82057
82318
  logger
82058
82319
  });
82059
82320
  const foldRootOverwriteWatch = createFoldRootOverwriteWatch({ logger });
82321
+ let fullRunSkills;
82322
+ const loadFullRunSkills = () => {
82323
+ fullRunSkills ??= loadConfigFileSkills({
82324
+ config,
82325
+ logger
82326
+ });
82327
+ return fullRunSkills;
82328
+ };
82329
+ const createProcessor = ({ toolTarget, outputRoot, featureOptions, skills: processorSkills }) => new RulesProcessor({
82330
+ outputRoot: resolveToolOutputRoot({
82331
+ outputRoot,
82332
+ toolTarget,
82333
+ global: config.getGlobal()
82334
+ }),
82335
+ inputRoots: config.getInputRoots(),
82336
+ toolTarget,
82337
+ global: config.getGlobal(),
82338
+ simulateCommands: config.getSimulateCommands(),
82339
+ simulateSubagents: config.getSimulateSubagents(),
82340
+ simulateSkills: config.getSimulateSkills(),
82341
+ language: config.getLanguage(),
82342
+ deriveSubprojectPathFromGlobs: config.getDeriveSubprojectPathFromGlobs(),
82343
+ skills: processorSkills,
82344
+ featureOptions,
82345
+ delete: config.getDelete(),
82346
+ dryRun: config.isPreviewMode(),
82347
+ logger
82348
+ });
82060
82349
  const isCheck = config.getCheck();
82061
82350
  const rootFileOwner = isCheck ? computeRootFileOwnership({
82062
- targets: config.getConfigFileTargets(),
82351
+ targets: config.getConfigFileTargets().filter((target) => (toolTargets.includes(target) ? config.getFeatures(target) : config.getConfigFileFeatures(target)).includes("rules")),
82063
82352
  global: config.getGlobal()
82064
82353
  }) : /* @__PURE__ */ new Map();
82354
+ const skippedRootFileCheck = createSkippedRootFileCheck({ rootFileOwner });
82065
82355
  for (const toolTarget of toolTargets) for (const outputRoot of config.getOutputRoots(toolTarget)) {
82066
82356
  if (!config.getFeatures(toolTarget).includes("rules")) continue;
82067
- const processor = new RulesProcessor({
82068
- outputRoot: resolveToolOutputRoot({
82069
- outputRoot,
82070
- toolTarget,
82071
- global: config.getGlobal()
82072
- }),
82073
- inputRoots: config.getInputRoots(),
82357
+ const processor = createProcessor({
82074
82358
  toolTarget,
82075
- global: config.getGlobal(),
82076
- simulateCommands: config.getSimulateCommands(),
82077
- simulateSubagents: config.getSimulateSubagents(),
82078
- simulateSkills: config.getSimulateSkills(),
82079
- language: config.getLanguage(),
82080
- deriveSubprojectPathFromGlobs: config.getDeriveSubprojectPathFromGlobs(),
82081
- skills,
82359
+ outputRoot,
82082
82360
  featureOptions: config.getFeatureOptions(toolTarget, "rules"),
82083
- delete: config.getDelete(),
82084
- dryRun: config.isPreviewMode(),
82085
- logger
82361
+ skills
82086
82362
  });
82087
82363
  const rulesyncFiles = await processor.loadRulesyncFiles();
82088
82364
  const skipFilePaths = /* @__PURE__ */ new Set();
@@ -82095,16 +82371,39 @@ async function generateRulesCore(params) {
82095
82371
  rulesyncFiles,
82096
82372
  sweepPlan,
82097
82373
  skipFilePaths: skipFilePaths.size > 0 ? skipFilePaths : void 0,
82098
- onToolFiles: (toolFiles) => foldRootOverwriteWatch.observe({
82099
- toolTarget,
82100
- toolFiles
82101
- })
82374
+ onToolFiles: (toolFiles) => {
82375
+ foldRootOverwriteWatch.observe({
82376
+ toolTarget,
82377
+ toolFiles
82378
+ });
82379
+ skippedRootFileCheck.observe({
82380
+ toolTarget,
82381
+ outputRoot,
82382
+ processor,
82383
+ toolFiles,
82384
+ skipFilePaths
82385
+ });
82386
+ }
82102
82387
  });
82103
82388
  totalCount += result.count;
82104
82389
  allPaths.push(...result.paths);
82105
82390
  if (result.hasDiff) hasDiff = true;
82106
82391
  if (result.sourceLoadFailed) sourceLoadFailed = true;
82107
82392
  }
82393
+ if (isCheck) {
82394
+ const skippedResult = await skippedRootFileCheck.verify({
82395
+ isOwnerProcessed: (owner) => toolTargets.includes(owner) && config.getFeatures(owner).includes("rules"),
82396
+ createOwnerProcessor: async (ownerParams) => supportedTargets.includes(ownerParams.toolTarget) && config.getOutputRoots(ownerParams.toolTarget).includes(ownerParams.outputRoot) ? createProcessor({
82397
+ ...ownerParams,
82398
+ featureOptions: config.getConfigFileFeatureOptions(ownerParams.toolTarget, "rules"),
82399
+ skills: config.getSimulateSkills() && SkillsProcessor.getToolTargetsSimulated().includes(ownerParams.toolTarget) ? await loadFullRunSkills() : void 0
82400
+ }) : void 0
82401
+ });
82402
+ totalCount += skippedResult.count;
82403
+ allPaths.push(...skippedResult.paths);
82404
+ if (skippedResult.hasDiff) hasDiff = true;
82405
+ if (skippedResult.sourceLoadFailed) sourceLoadFailed = true;
82406
+ }
82108
82407
  foldRootOverwriteWatch.report();
82109
82408
  return {
82110
82409
  count: totalCount,