rulesync 21.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.
@@ -2703,6 +2703,7 @@ var Config = class Config {
2703
2703
  */
2704
2704
  objectFormTargetKeys;
2705
2705
  configFileTargets;
2706
+ configFileSelection;
2706
2707
  verbose;
2707
2708
  delete;
2708
2709
  global;
@@ -2737,7 +2738,7 @@ var Config = class Config {
2737
2738
  inputRoots;
2738
2739
  configFilePath;
2739
2740
  sources;
2740
- 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 }) {
2741
+ 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 }) {
2741
2742
  assertTargetsFeaturesExclusive({
2742
2743
  targets,
2743
2744
  features
@@ -2762,6 +2763,7 @@ var Config = class Config {
2762
2763
  this.features = resolvedFeatures;
2763
2764
  this.objectFormTargetKeys = isRulesyncConfigTargetsObject(resolvedTargets) ? Config.filterValidToolTargets(Object.keys(resolvedTargets)) : void 0;
2764
2765
  this.configFileTargets = configFileTargets;
2766
+ this.configFileSelection = configFileSelection;
2765
2767
  this.verbose = verbose;
2766
2768
  this.delete = isDelete;
2767
2769
  this.global = global ?? false;
@@ -2852,17 +2854,54 @@ var Config = class Config {
2852
2854
  getConfigFileTargets() {
2853
2855
  return this.configFileTargets ?? this.getTargets();
2854
2856
  }
2857
+ /**
2858
+ * The features `target` has for a run over the configuration file's
2859
+ * targets, i.e. what `rulesync generate` without `--targets` would produce
2860
+ * for it (a CLI `--features` still applies to array-form targets). Falls
2861
+ * back to {@link getFeatures} when no configuration file declared `targets`.
2862
+ */
2863
+ getConfigFileFeatures(target) {
2864
+ const selection = this.configFileSelection;
2865
+ if (selection === void 0) return this.getFeatures(target);
2866
+ return Config.resolveTargetFeatures({
2867
+ targets: selection.targets,
2868
+ features: selection.features ?? [],
2869
+ target
2870
+ });
2871
+ }
2872
+ /**
2873
+ * The options `target` sets for `feature` in the configuration file, for
2874
+ * the same run as {@link getConfigFileFeatures}. Falls back to
2875
+ * {@link getFeatureOptions} when no configuration file declared `targets`.
2876
+ */
2877
+ getConfigFileFeatureOptions(target, feature) {
2878
+ const selection = this.configFileSelection;
2879
+ if (selection === void 0) return this.getFeatureOptions(target, feature);
2880
+ return Config.resolveFeatureOptions({
2881
+ targets: selection.targets,
2882
+ target,
2883
+ feature
2884
+ });
2885
+ }
2855
2886
  getFeatures(target) {
2856
- if (isRulesyncConfigTargetsObject(this.targets)) {
2857
- if (target) {
2858
- const value = this.targets[target];
2859
- if (!value) return [];
2860
- return Config.normalizeTargetFeatures(value);
2861
- }
2862
- return Config.collectAllFeatures(Object.values(this.targets));
2887
+ if (target) return Config.resolveTargetFeatures({
2888
+ targets: this.targets,
2889
+ features: this.features,
2890
+ target
2891
+ });
2892
+ if (isRulesyncConfigTargetsObject(this.targets)) return Config.collectAllFeatures(Object.values(this.targets));
2893
+ return Config.normalizeFeatureList(this.features);
2894
+ }
2895
+ static resolveTargetFeatures({ targets, features, target }) {
2896
+ if (isRulesyncConfigTargetsObject(targets)) {
2897
+ const value = targets[target];
2898
+ return value ? Config.normalizeTargetFeatures(value) : [];
2863
2899
  }
2864
- if (this.features.includes("*")) return [...ALL_FEATURES];
2865
- return this.features.filter((feature) => feature !== "*");
2900
+ return Config.normalizeFeatureList(features);
2901
+ }
2902
+ static normalizeFeatureList(features) {
2903
+ if (features.includes("*")) return [...ALL_FEATURES];
2904
+ return features.filter((feature) => feature !== "*");
2866
2905
  }
2867
2906
  /**
2868
2907
  * Normalize a per-target features value (array or per-feature object) into
@@ -2903,7 +2942,14 @@ var Config = class Config {
2903
2942
  * feature is not enabled for the given target.
2904
2943
  */
2905
2944
  getFeatureOptions(target, feature) {
2906
- const value = isRulesyncConfigTargetsObject(this.targets) ? this.targets[target] : void 0;
2945
+ return Config.resolveFeatureOptions({
2946
+ targets: this.targets,
2947
+ target,
2948
+ feature
2949
+ });
2950
+ }
2951
+ static resolveFeatureOptions({ targets, target, feature }) {
2952
+ const value = isRulesyncConfigTargetsObject(targets) ? targets[target] : void 0;
2907
2953
  if (!value || Array.isArray(value)) return;
2908
2954
  const featureValue = value[feature];
2909
2955
  if (featureValue && typeof featureValue === "object" && isFeatureValueEnabled(featureValue)) return featureValue;
@@ -3338,7 +3384,15 @@ var ConfigResolver = class {
3338
3384
  sources: configByFile.sources ?? getDefaults().sources,
3339
3385
  flattenedCommandNaming: configByFile.flattenedCommandNaming ?? getDefaults().flattenedCommandNaming,
3340
3386
  language: configByFile.language,
3341
- configFileTargets: extractConfigFileTargets(configByFile.targets)
3387
+ configFileTargets: extractConfigFileTargets(configByFile.targets),
3388
+ configFileSelection: configByFile.targets === void 0 ? void 0 : {
3389
+ targets: configByFile.targets,
3390
+ features: resolveFeaturesAndTargets({
3391
+ features,
3392
+ targets: void 0,
3393
+ configByFile
3394
+ }).resolvedFeatures
3395
+ }
3342
3396
  });
3343
3397
  }
3344
3398
  };
@@ -12270,7 +12324,7 @@ const CODEXCLI_AGENTS_DIR_PATH = join(CODEXCLI_DIR, "agents");
12270
12324
  const CODEXCLI_SKILLS_DIR_PATH = join(".agents", "skills");
12271
12325
  const CODEXCLI_HOOKS_FILE_NAME = "hooks.json";
12272
12326
  const CODEXCLI_MCP_FILE_NAME = "config.toml";
12273
- const CODEXCLI_RULE_FILE_NAME = "AGENTS.md";
12327
+ const CODEXCLI_PROJECT_DOC_MAX_BYTES = 32768;
12274
12328
  const CODEXCLI_BASH_RULES_FILE_NAME = "rulesync.rules";
12275
12329
  const CODEXCLI_OPENAI_YAML_RELATIVE_PATH = join("agents", "openai.yaml");
12276
12330
  const CODEXCLI_OVERRIDE_KEYS = [
@@ -14940,6 +14994,7 @@ const ANTIGRAVITY_GLOBAL_AGENTS_DIR_PATH = join(ANTIGRAVITY_GLOBAL_CONFIG_DIR_PA
14940
14994
  const ANTIGRAVITY_AGENTS_DIR = ANTIGRAVITY_DIR;
14941
14995
  const ANTIGRAVITY_RULE_FILE_NAME = "AGENTS.md";
14942
14996
  const ANTIGRAVITY_GLOBAL_RULE_FILE_NAME = "GEMINI.md";
14997
+ const ANTIGRAVITY_GLOBAL_RULES_SUBDIR = join(ANTIGRAVITY_GLOBAL_CONFIG_SUBDIR, "rules");
14943
14998
  //#endregion
14944
14999
  //#region src/features/commands/antigravity-command.ts
14945
15000
  const AntigravityWorkflowFrontmatterSchema = z.looseObject({
@@ -15455,12 +15510,50 @@ var BobCommand = class BobCommand extends ToolCommand {
15455
15510
  }
15456
15511
  };
15457
15512
  //#endregion
15513
+ //#region src/features/shared/claudecode-argument-hint.ts
15514
+ /**
15515
+ * Turns a YAML-list `argument-hint` back into the bracketed string form Claude
15516
+ * Code documents (`[issue-number]`, `[filename] [format]`), one bracketed
15517
+ * placeholder per list entry. The per-entry form is chosen over re-joining with
15518
+ * commas because it is the shape every Claude Code example uses. An empty list
15519
+ * carries no hint, so it yields `undefined` and the key is dropped.
15520
+ */
15521
+ function joinArgumentHint(value) {
15522
+ if (typeof value === "string") return value;
15523
+ if (value.length === 0) return;
15524
+ return value.map((entry) => `[${String(entry)}]`).join(" ");
15525
+ }
15526
+ /**
15527
+ * `argument-hint` as Claude Code reads it from skill and command frontmatter.
15528
+ *
15529
+ * The documented examples (`[issue-number]`) are written unquoted, and YAML
15530
+ * parses an unquoted `[issue-number]` as a one-element list. Claude Code
15531
+ * accepts that form, so it is accepted here too and normalized to the
15532
+ * bracketed string, keeping the parsed value (and everything downstream of it)
15533
+ * a plain string. A scalar entry YAML did not read as a string (`[1]`,
15534
+ * `[true]`, `[~]`) is stringified rather than rejected.
15535
+ * https://code.claude.com/docs/en/skills
15536
+ *
15537
+ * A list entry is stringified from the value YAML produced, so its original
15538
+ * spelling is not kept (`[1.0]` becomes `[1]`), and an entry YAML reads as a
15539
+ * date (`[2026-01-01]`) is rejected.
15540
+ *
15541
+ * The unquoted multi-placeholder form (`argument-hint: [filename] [format]`)
15542
+ * is not valid YAML at all, so it fails before this schema is reached.
15543
+ */
15544
+ const ClaudecodeArgumentHintSchema = z.pipe(z.union([z.string(), z.array(z.union([
15545
+ z.string(),
15546
+ z.number(),
15547
+ z.boolean(),
15548
+ z.null()
15549
+ ]))]), z.transform(joinArgumentHint));
15550
+ //#endregion
15458
15551
  //#region src/features/commands/claudecode-command.ts
15459
15552
  const ClaudecodeCommandFrontmatterSchema = z.looseObject({
15460
15553
  description: z.optional(z.string()),
15461
15554
  "allowed-tools": z.optional(z.union([z.string(), z.array(z.string())])),
15462
15555
  "disallowed-tools": z.optional(z.union([z.string(), z.array(z.string())])),
15463
- "argument-hint": z.optional(z.string()),
15556
+ "argument-hint": z.optional(ClaudecodeArgumentHintSchema),
15464
15557
  model: z.optional(z.string()),
15465
15558
  "disable-model-invocation": z.optional(z.boolean())
15466
15559
  });
@@ -15489,7 +15582,8 @@ var ClaudecodeCommand = class ClaudecodeCommand extends ToolCommand {
15489
15582
  return this.frontmatter;
15490
15583
  }
15491
15584
  toRulesyncCommand() {
15492
- const { description, ...restFields } = this.frontmatter;
15585
+ const { description, ...extraFields } = this.frontmatter;
15586
+ const restFields = Object.fromEntries(Object.entries(extraFields).filter(([, value]) => value !== void 0));
15493
15587
  const rulesyncFrontmatter = {
15494
15588
  targets: ["*"],
15495
15589
  description,
@@ -33122,6 +33216,74 @@ function translateCanonicalKeyToCodex({ key, value, isStdio, serverName }) {
33122
33216
  }
33123
33217
  }
33124
33218
  /**
33219
+ * A header value that is exactly one canonical `${VAR}` reference. Anchored and
33220
+ * without the `g` flag, so `.exec()` carries no `lastIndex` state between
33221
+ * calls. `${VAR:-default}` does not match (the name excludes `:`), because
33222
+ * Codex has no way to express the fallback.
33223
+ */
33224
+ const WHOLE_ENV_VAR_REF_PATTERN = /^\$\{([A-Za-z_][A-Za-z0-9_]*)\}$/;
33225
+ /**
33226
+ * An `Authorization` value that is exactly `Bearer ${VAR}`. The scheme is
33227
+ * matched case-insensitively, as HTTP authentication schemes are.
33228
+ */
33229
+ const BEARER_ENV_VAR_REF_PATTERN = /^Bearer \$\{([A-Za-z_][A-Za-z0-9_]*)\}$/i;
33230
+ const AUTHORIZATION_HEADER_NAME = "authorization";
33231
+ /**
33232
+ * Whether a string still contains an unexpanded `${…}` reference. Codex writes
33233
+ * `url` and `http_headers` values as-is and never expands them, so such a value
33234
+ * reaches the server literally.
33235
+ */
33236
+ function hasUnexpandedEnvVarRef(value) {
33237
+ return value.includes("${");
33238
+ }
33239
+ /**
33240
+ * Rewrite canonical `${VAR}` header references into the keys Codex reads them
33241
+ * from, since Codex sends `http_headers` values verbatim:
33242
+ * - `Authorization: Bearer ${VAR}` → `bearer_token_env_var = "VAR"`
33243
+ * - a header whose whole value is `${VAR}` → `env_http_headers.<name> = "VAR"`
33244
+ *
33245
+ * Runs after every key is translated, so a `bearer_token_env_var` or
33246
+ * `env_http_headers` the source already carries (they pass through under their
33247
+ * own names) is seen regardless of key order. Those explicit values win: a
33248
+ * derived bearer is not written over an existing one, and an existing
33249
+ * `env_http_headers` entry for the same header is kept. Any value the rewrite
33250
+ * cannot express — a partial reference, a `${VAR:-default}` fallback — stays in
33251
+ * `http_headers` verbatim with a warning, as does a `${…}` in `url`, for which
33252
+ * Codex has no environment-sourced counterpart at all.
33253
+ * @see https://learn.chatgpt.com/docs/config-file/config-reference
33254
+ */
33255
+ function convertEnvVarRefsForCodex({ converted, serverName }) {
33256
+ const url = converted["url"];
33257
+ if (typeof url === "string" && hasUnexpandedEnvVarRef(url)) warnWithFallback(void 0, `[CodexCliMcp] MCP server "${serverName}": 'url' contains an environment variable reference that Codex does not expand, so it is written verbatim and the server will likely fail to start.`);
33258
+ const httpHeaders = converted["http_headers"];
33259
+ if (!isHeadersRecord(httpHeaders)) return;
33260
+ const existingEnvHeaders = converted["env_http_headers"];
33261
+ const canWriteEnvHeaders = existingEnvHeaders === void 0 || isHeadersRecord(existingEnvHeaders);
33262
+ const envHeaders = {};
33263
+ const staticHeaders = {};
33264
+ let bearerTokenEnvVar;
33265
+ for (const [name, value] of Object.entries(httpHeaders)) {
33266
+ const bearerMatch = BEARER_ENV_VAR_REF_PATTERN.exec(value);
33267
+ if (bearerMatch?.[1] && name.toLowerCase() === AUTHORIZATION_HEADER_NAME) {
33268
+ if (converted["bearer_token_env_var"] === void 0 && bearerTokenEnvVar === void 0) bearerTokenEnvVar = bearerMatch[1];
33269
+ continue;
33270
+ }
33271
+ const wholeMatch = WHOLE_ENV_VAR_REF_PATTERN.exec(value);
33272
+ if (wholeMatch?.[1] && canWriteEnvHeaders) {
33273
+ envHeaders[name] = wholeMatch[1];
33274
+ continue;
33275
+ }
33276
+ if (hasUnexpandedEnvVarRef(value)) warnWithFallback(void 0, `[CodexCliMcp] MCP server "${serverName}": header '${name}' contains an environment variable reference that Codex cannot source from the environment, so it is written verbatim to 'http_headers' and sent literally. Only 'Authorization: Bearer \${VAR}' and a whole-value '\${VAR}' are converted.`);
33277
+ staticHeaders[name] = value;
33278
+ }
33279
+ converted["http_headers"] = staticHeaders;
33280
+ if (bearerTokenEnvVar !== void 0) converted["bearer_token_env_var"] = bearerTokenEnvVar;
33281
+ if (Object.keys(envHeaders).length > 0) converted["env_http_headers"] = {
33282
+ ...envHeaders,
33283
+ ...isHeadersRecord(existingEnvHeaders) ? existingEnvHeaders : {}
33284
+ };
33285
+ }
33286
+ /**
33125
33287
  * Codex states no transport of its own — it infers one from `command` versus
33126
33288
  * `url`, which is why generate drops the canonical `type`. Restate it for a url
33127
33289
  * server on the way back, so a config imported from Codex reaches the adapters
@@ -33208,6 +33370,10 @@ function convertToCodexFormat(mcpServers) {
33208
33370
  }
33209
33371
  } else converted[key] = value;
33210
33372
  }
33373
+ convertEnvVarRefsForCodex({
33374
+ converted,
33375
+ serverName: name
33376
+ });
33211
33377
  const previousName = originalNames.get(codexName);
33212
33378
  if (previousName !== void 0) warnWithFallback(void 0, `Codex MCP server name collision: "${previousName}" and "${name}" both normalize to "${codexName}"; "${name}" (processed last) overwrites "${previousName}".`);
33213
33379
  originalNames.set(codexName, name);
@@ -59207,7 +59373,7 @@ const ClaudecodeSkillFrontmatterSchema = z.looseObject({
59207
59373
  "disallowed-tools": z.optional(z.union([z.string(), z.array(z.string())])),
59208
59374
  model: z.optional(z.string()),
59209
59375
  effort: z.optional(z.string()),
59210
- "argument-hint": z.optional(z.string()),
59376
+ "argument-hint": z.optional(ClaudecodeArgumentHintSchema),
59211
59377
  arguments: z.optional(z.union([z.string(), z.array(z.string())])),
59212
59378
  context: z.optional(z.string()),
59213
59379
  agent: z.optional(z.string()),
@@ -65363,7 +65529,8 @@ const toolSkillFactories = /* @__PURE__ */ new Map([
65363
65529
  meta: {
65364
65530
  supportsProject: true,
65365
65531
  supportsSimulated: false,
65366
- supportsGlobal: true
65532
+ supportsGlobal: true,
65533
+ lenientImport: true
65367
65534
  }
65368
65535
  }],
65369
65536
  ["claudecode-plugin", {
@@ -72312,7 +72479,7 @@ var ToolRule = class extends ToolFile {
72312
72479
  static buildToolRuleParamsAgentsmd({ outputRoot = process.cwd(), rulesyncRule, validate = true, rootPath = {
72313
72480
  relativeDirPath: ".",
72314
72481
  relativeFilePath: "AGENTS.md"
72315
- }, nonRootPath = { relativeDirPath: AGENTSMD_MEMORIES_DIR_PATH } }) {
72482
+ }, nonRootPath = { relativeDirPath: AGENTSMD_MEMORIES_DIR_PATH }, global = false }) {
72316
72483
  const params = this.buildToolRuleParamsDefault({
72317
72484
  outputRoot,
72318
72485
  rulesyncRule,
@@ -72321,7 +72488,7 @@ var ToolRule = class extends ToolFile {
72321
72488
  nonRootPath
72322
72489
  });
72323
72490
  const rulesyncFrontmatter = rulesyncRule.getFrontmatter();
72324
- if (!rulesyncFrontmatter.root && rulesyncFrontmatter.agentsmd?.subprojectPath) {
72491
+ if (!global && !rulesyncFrontmatter.root && rulesyncFrontmatter.agentsmd?.subprojectPath) {
72325
72492
  params.relativeDirPath = join(rulesyncFrontmatter.agentsmd.subprojectPath);
72326
72493
  params.relativeFilePath = "AGENTS.md";
72327
72494
  }
@@ -72978,7 +73145,8 @@ var AmpRule = class AmpRule extends ToolRule {
72978
73145
  rulesyncRule,
72979
73146
  validate,
72980
73147
  rootPath: paths.root,
72981
- nonRootPath: paths.nonRoot
73148
+ nonRootPath: paths.nonRoot,
73149
+ global
72982
73150
  });
72983
73151
  if (!params.root && params.globs !== void 0 && params.globs.length > 0) params.fileContent = stringifyFrontmatter(params.fileContent, { globs: params.globs });
72984
73152
  return new AmpRule(params);
@@ -73024,97 +73192,6 @@ var AmpRule = class AmpRule extends ToolRule {
73024
73192
  }
73025
73193
  };
73026
73194
  //#endregion
73027
- //#region src/features/rules/antigravity-cli-rule.ts
73028
- /**
73029
- * Rule generator for the Google Antigravity CLI (`agy`, the Gemini-CLI
73030
- * successor in Antigravity 2.0).
73031
- *
73032
- * The CLI reads the same plain-markdown context files as Gemini CLI — a root
73033
- * context file plus non-root memory files in `.agents/rules/` — so this class
73034
- * follows that same plain-markdown approach but points at the new `.agents/` tree.
73035
- *
73036
- * - Project scope: root `AGENTS.md` (the cross-tool standard, matching
73037
- * `antigravity-ide`); non-root `.agents/rules/*.md`.
73038
- * - Global scope: a single plain `~/.gemini/GEMINI.md` (shared with the IDE).
73039
- */
73040
- var AntigravityCliRule = class AntigravityCliRule extends ToolRule {
73041
- static getSettablePaths({ global, excludeToolDir } = {}) {
73042
- if (global) return { root: {
73043
- relativeDirPath: buildToolPath(ANTIGRAVITY_GEMINI_DIR, ".", excludeToolDir),
73044
- relativeFilePath: ANTIGRAVITY_GLOBAL_RULE_FILE_NAME
73045
- } };
73046
- return {
73047
- root: {
73048
- relativeDirPath: ".",
73049
- relativeFilePath: ANTIGRAVITY_RULE_FILE_NAME
73050
- },
73051
- nonRoot: { relativeDirPath: buildToolPath(ANTIGRAVITY_AGENTS_DIR, "rules", excludeToolDir) }
73052
- };
73053
- }
73054
- static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
73055
- const paths = this.getSettablePaths({ global });
73056
- if (relativeFilePath === paths.root.relativeFilePath) {
73057
- const relativePath = paths.root.relativeFilePath;
73058
- const fileContent = await readFileContent(join(outputRoot, paths.root.relativeDirPath, relativePath));
73059
- return new AntigravityCliRule({
73060
- outputRoot,
73061
- relativeDirPath: paths.root.relativeDirPath,
73062
- relativeFilePath: paths.root.relativeFilePath,
73063
- fileContent,
73064
- validate,
73065
- root: true
73066
- });
73067
- }
73068
- if (!paths.nonRoot) throw new Error(`nonRoot path is not set for ${relativeFilePath}`);
73069
- const relativePath = join(paths.nonRoot.relativeDirPath, relativeFilePath);
73070
- const fileContent = await readFileContent(join(outputRoot, relativePath));
73071
- return new AntigravityCliRule({
73072
- outputRoot,
73073
- relativeDirPath: paths.nonRoot.relativeDirPath,
73074
- relativeFilePath,
73075
- fileContent,
73076
- validate,
73077
- root: false
73078
- });
73079
- }
73080
- static fromRulesyncRule({ outputRoot = process.cwd(), rulesyncRule, validate = true, global = false }) {
73081
- const paths = this.getSettablePaths({ global });
73082
- return new AntigravityCliRule(this.buildToolRuleParamsDefault({
73083
- outputRoot,
73084
- rulesyncRule,
73085
- validate,
73086
- rootPath: paths.root,
73087
- nonRootPath: paths.nonRoot
73088
- }));
73089
- }
73090
- toRulesyncRule() {
73091
- return this.toRulesyncRuleDefault();
73092
- }
73093
- validate() {
73094
- return {
73095
- success: true,
73096
- error: null
73097
- };
73098
- }
73099
- static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
73100
- const isRoot = relativeFilePath === this.getSettablePaths({ global }).root.relativeFilePath;
73101
- return new AntigravityCliRule({
73102
- outputRoot,
73103
- relativeDirPath,
73104
- relativeFilePath,
73105
- fileContent: "",
73106
- validate: false,
73107
- root: isRoot
73108
- });
73109
- }
73110
- static isTargetedByRulesyncRule(rulesyncRule) {
73111
- return this.isTargetedByRulesyncRuleDefault({
73112
- rulesyncRule,
73113
- toolTarget: "antigravity-cli"
73114
- });
73115
- }
73116
- };
73117
- //#endregion
73118
73195
  //#region src/features/rules/antigravity-rule.ts
73119
73196
  const AntigravityRuleFrontmatterSchema = z.looseObject({
73120
73197
  trigger: z.optional(z.union([
@@ -73261,6 +73338,185 @@ const STRATEGIES$1 = [
73261
73338
  }
73262
73339
  ];
73263
73340
  //#endregion
73341
+ //#region src/features/rules/antigravity-cli-rule.ts
73342
+ /**
73343
+ * Rule generator for the Google Antigravity CLI (`agy`, the Gemini-CLI
73344
+ * successor in Antigravity 2.0).
73345
+ *
73346
+ * The CLI reads the same plain-markdown context files as Gemini CLI — a root
73347
+ * context file plus non-root memory files in `.agents/rules/` — so this class
73348
+ * follows that same plain-markdown approach but points at the new `.agents/` tree.
73349
+ *
73350
+ * - Project scope: root `AGENTS.md` (the cross-tool standard, matching
73351
+ * `antigravity-ide`); non-root `.agents/rules/*.md`.
73352
+ * - Global scope: root plain `~/.gemini/GEMINI.md` (shared with the IDE);
73353
+ * non-root `~/.gemini/config/rules/*.md`. The CLI truncates each rule file at
73354
+ * 24,000 bytes, so keeping non-root rules as separate files stops a large rule
73355
+ * set from being cut off. Every file in that directory must carry a valid
73356
+ * `trigger` frontmatter or the CLI discards it, so these files get the same
73357
+ * trigger-strategy frontmatter as the IDE's non-root rules.
73358
+ *
73359
+ * @see https://antigravity.google/docs/rules
73360
+ */
73361
+ var AntigravityCliRule = class AntigravityCliRule extends ToolRule {
73362
+ static getSettablePaths({ global, excludeToolDir } = {}) {
73363
+ if (global) return {
73364
+ root: {
73365
+ relativeDirPath: buildToolPath(ANTIGRAVITY_GEMINI_DIR, ".", excludeToolDir),
73366
+ relativeFilePath: ANTIGRAVITY_GLOBAL_RULE_FILE_NAME
73367
+ },
73368
+ nonRoot: { relativeDirPath: buildToolPath(ANTIGRAVITY_GEMINI_DIR, ANTIGRAVITY_GLOBAL_RULES_SUBDIR, excludeToolDir) }
73369
+ };
73370
+ return {
73371
+ root: {
73372
+ relativeDirPath: ".",
73373
+ relativeFilePath: ANTIGRAVITY_RULE_FILE_NAME
73374
+ },
73375
+ nonRoot: { relativeDirPath: buildToolPath(ANTIGRAVITY_AGENTS_DIR, "rules", excludeToolDir) }
73376
+ };
73377
+ }
73378
+ static async fromFile({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, validate = true, global = false }) {
73379
+ const paths = this.getSettablePaths({ global });
73380
+ if (relativeFilePath === paths.root.relativeFilePath && (relativeDirPath === void 0 || relativeDirPath === paths.root.relativeDirPath)) {
73381
+ const relativePath = paths.root.relativeFilePath;
73382
+ const fileContent = await readFileContent(join(outputRoot, paths.root.relativeDirPath, relativePath));
73383
+ return new AntigravityCliRule({
73384
+ outputRoot,
73385
+ relativeDirPath: paths.root.relativeDirPath,
73386
+ relativeFilePath: paths.root.relativeFilePath,
73387
+ fileContent,
73388
+ validate,
73389
+ root: true,
73390
+ global
73391
+ });
73392
+ }
73393
+ if (!paths.nonRoot) throw new Error(`nonRoot path is not set for ${relativeFilePath}`);
73394
+ const relativePath = join(paths.nonRoot.relativeDirPath, relativeFilePath);
73395
+ const fileContent = await readFileContent(join(outputRoot, relativePath));
73396
+ const rule = new AntigravityCliRule({
73397
+ outputRoot,
73398
+ relativeDirPath: paths.nonRoot.relativeDirPath,
73399
+ relativeFilePath,
73400
+ fileContent,
73401
+ validate,
73402
+ root: false,
73403
+ global
73404
+ });
73405
+ if (validate) {
73406
+ const result = rule.validate();
73407
+ if (!result.success) throw new Error(`Invalid frontmatter in ${relativePath}: ${result.error.message}`);
73408
+ }
73409
+ return rule;
73410
+ }
73411
+ static fromRulesyncRule({ outputRoot = process.cwd(), rulesyncRule, validate = true, global = false }) {
73412
+ const paths = this.getSettablePaths({ global });
73413
+ const params = this.buildToolRuleParamsDefault({
73414
+ outputRoot,
73415
+ rulesyncRule,
73416
+ validate,
73417
+ rootPath: paths.root,
73418
+ nonRootPath: paths.nonRoot
73419
+ });
73420
+ if (!global || params.root) return new AntigravityCliRule({
73421
+ ...params,
73422
+ global
73423
+ });
73424
+ const rulesyncFrontmatter = rulesyncRule.getFrontmatter();
73425
+ const storedAntigravity = rulesyncFrontmatter.antigravity;
73426
+ const storedTrigger = storedAntigravity?.trigger;
73427
+ const strategy = STRATEGIES$1.find((s) => s.canHandle(storedTrigger));
73428
+ if (!strategy) throw new Error(`No strategy found for trigger: ${storedTrigger}`);
73429
+ const frontmatter = strategy.generateFrontmatter(normalizeStoredAntigravity(storedAntigravity), rulesyncFrontmatter);
73430
+ return new AntigravityCliRule({
73431
+ ...params,
73432
+ relativeFilePath: params.relativeFilePath.split(/[\\/]/).join("-"),
73433
+ fileContent: stringifyFrontmatter(rulesyncRule.getBody(), frontmatter),
73434
+ global
73435
+ });
73436
+ }
73437
+ toRulesyncRule() {
73438
+ if (!this.global || this.root) return this.toRulesyncRuleDefault();
73439
+ const { frontmatter, body } = this.parseGlobalNonRootContent();
73440
+ const strategy = STRATEGIES$1.find((s) => s.canHandle(frontmatter.trigger));
73441
+ const rulesyncData = strategy ? strategy.exportRulesyncData(frontmatter) : {
73442
+ globs: [],
73443
+ antigravity: frontmatter
73444
+ };
73445
+ return new RulesyncRule({
73446
+ outputRoot: process.cwd(),
73447
+ relativeDirPath: RulesyncRule.getSettablePaths().recommended.relativeDirPath,
73448
+ relativeFilePath: this.getRelativeFilePath(),
73449
+ frontmatter: {
73450
+ root: false,
73451
+ targets: ["*"],
73452
+ ...rulesyncData,
73453
+ antigravity: {
73454
+ ...rulesyncData.antigravity,
73455
+ globs: frontmatter.globs ? parseGlobsString$1(frontmatter.globs) : void 0
73456
+ }
73457
+ },
73458
+ body
73459
+ });
73460
+ }
73461
+ validate() {
73462
+ if (!this.global || this.root) return {
73463
+ success: true,
73464
+ error: null
73465
+ };
73466
+ try {
73467
+ const { frontmatter } = this.parseGlobalNonRootContent();
73468
+ const result = AntigravityRuleFrontmatterSchema.safeParse(frontmatter);
73469
+ if (!result.success) return {
73470
+ success: false,
73471
+ error: new Error(formatError(result.error))
73472
+ };
73473
+ } catch (error) {
73474
+ return {
73475
+ success: false,
73476
+ error: error instanceof Error ? error : new Error(String(error))
73477
+ };
73478
+ }
73479
+ return {
73480
+ success: true,
73481
+ error: null
73482
+ };
73483
+ }
73484
+ /**
73485
+ * `~/.gemini/config/rules/` is shared with the Antigravity IDE and holds
73486
+ * global rules the user created outside rulesync, which cannot be told apart
73487
+ * from generated ones, so the orphan sweep never removes files there.
73488
+ */
73489
+ isDeletable() {
73490
+ return !this.global || this.root;
73491
+ }
73492
+ parseGlobalNonRootContent() {
73493
+ const { frontmatter, body } = parseFrontmatter(this.getFileContent(), this.getFilePath());
73494
+ return {
73495
+ frontmatter,
73496
+ body
73497
+ };
73498
+ }
73499
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
73500
+ const paths = this.getSettablePaths({ global });
73501
+ const isRoot = relativeFilePath === paths.root.relativeFilePath && relativeDirPath === paths.root.relativeDirPath;
73502
+ return new AntigravityCliRule({
73503
+ outputRoot,
73504
+ relativeDirPath,
73505
+ relativeFilePath,
73506
+ fileContent: "",
73507
+ validate: false,
73508
+ root: isRoot,
73509
+ global
73510
+ });
73511
+ }
73512
+ static isTargetedByRulesyncRule(rulesyncRule) {
73513
+ return this.isTargetedByRulesyncRuleDefault({
73514
+ rulesyncRule,
73515
+ toolTarget: "antigravity-cli"
73516
+ });
73517
+ }
73518
+ };
73519
+ //#endregion
73264
73520
  //#region src/features/rules/antigravity-ide-rule.ts
73265
73521
  /**
73266
73522
  * Rule generator for the Google Antigravity IDE (Antigravity 2.0).
@@ -74273,7 +74529,8 @@ var ClineRule = class ClineRule extends ToolRule {
74273
74529
  outputRoot,
74274
74530
  rulesyncRule,
74275
74531
  validate,
74276
- rootPath: paths.root
74532
+ rootPath: paths.root,
74533
+ global
74277
74534
  }));
74278
74535
  }
74279
74536
  if (isRoot) return new ClineRule({
@@ -74693,26 +74950,62 @@ var CodebuddyRule = class CodebuddyRule extends ToolRule {
74693
74950
  }
74694
74951
  };
74695
74952
  //#endregion
74696
- //#region src/features/rules/codexcli-rule.ts
74697
- var CodexcliRule = class CodexcliRule extends ToolRule {
74698
- constructor({ fileContent, root, ...rest }) {
74699
- super({
74700
- ...rest,
74701
- fileContent,
74702
- root: root ?? false
74703
- });
74953
+ //#region src/features/rules/nested-agentsmd-rule.ts
74954
+ /**
74955
+ * Shared adapter for the targets that read instructions exactly the way the
74956
+ * AGENTS.md standard describes them and nothing more: a root `AGENTS.md` at the
74957
+ * project root, nested per-directory `AGENTS.md` files loaded only while working
74958
+ * under that directory, and a single personal root file under a tool-specific
74959
+ * home directory. Such a target has no modular non-root instruction directory,
74960
+ * so every other non-root rule folds into the root file.
74961
+ *
74962
+ * Concrete targets (Codex CLI, Pool, Vibe, DeepSeek Harness) only supply the global
74963
+ * directory and their tool target through {@link getFamily}; the vendor docs
74964
+ * describing each one's discovery walk live on the subclass.
74965
+ *
74966
+ * The nested-file scan mirrors the AGENTS.md standard's nested discovery —
74967
+ * same file name, same exclusions, import-only, project scope — because it
74968
+ * discovers literally the same files.
74969
+ */
74970
+ var NestedAgentsmdRule = class extends ToolRule {
74971
+ static getFamily() {
74972
+ throw new Error("Please implement this method in the subclass.");
74704
74973
  }
74705
74974
  static getSettablePaths({ global = false } = {}) {
74975
+ if (global) return { root: {
74976
+ relativeDirPath: this.getFamily().globalDir,
74977
+ relativeFilePath: AGENTSMD_RULE_FILE_NAME
74978
+ } };
74706
74979
  return { root: {
74707
- relativeDirPath: global ? CODEXCLI_DIR : ".",
74708
- relativeFilePath: CODEXCLI_RULE_FILE_NAME
74980
+ relativeDirPath: ".",
74981
+ relativeFilePath: AGENTSMD_RULE_FILE_NAME
74709
74982
  } };
74710
74983
  }
74711
- static async fromFile({ outputRoot = process.cwd(), relativeFilePath: _relativeFilePath, validate = true, global = false }) {
74984
+ static getNestedFilePatterns() {
74985
+ return this.buildNestedFilePatterns({ fileName: AGENTSMD_RULE_FILE_NAME });
74986
+ }
74987
+ /**
74988
+ * The subproject directory this rule scopes, or `undefined` for the root file
74989
+ * (project or global).
74990
+ */
74991
+ getSubprojectPath() {
74992
+ return this.getNestedSubprojectPath({ fileName: AGENTSMD_RULE_FILE_NAME });
74993
+ }
74994
+ static async fromFile({ outputRoot = process.cwd(), relativeFilePath: _relativeFilePath, relativeDirPath: overrideDirPath, validate = true, global = false }) {
74712
74995
  const { root } = this.getSettablePaths({ global });
74713
- const relativePath = join(root.relativeDirPath, root.relativeFilePath);
74714
- const fileContent = await readFileContent(join(outputRoot, relativePath));
74715
- return new CodexcliRule({
74996
+ if (overrideDirPath !== void 0 && overrideDirPath !== root.relativeDirPath && overrideDirPath !== ".") {
74997
+ const fileContent = await readFileContent(join(outputRoot, overrideDirPath, AGENTSMD_RULE_FILE_NAME));
74998
+ return new this({
74999
+ outputRoot,
75000
+ relativeDirPath: overrideDirPath,
75001
+ relativeFilePath: AGENTSMD_RULE_FILE_NAME,
75002
+ fileContent,
75003
+ validate,
75004
+ root: false
75005
+ });
75006
+ }
75007
+ const fileContent = await readFileContent(join(outputRoot, root.relativeDirPath, root.relativeFilePath));
75008
+ return new this({
74716
75009
  outputRoot,
74717
75010
  relativeDirPath: root.relativeDirPath,
74718
75011
  relativeFilePath: root.relativeFilePath,
@@ -74723,8 +75016,18 @@ var CodexcliRule = class CodexcliRule extends ToolRule {
74723
75016
  }
74724
75017
  static fromRulesyncRule({ outputRoot = process.cwd(), rulesyncRule, validate = true, global = false }) {
74725
75018
  const { root } = this.getSettablePaths({ global });
74726
- const isRoot = rulesyncRule.getFrontmatter().root ?? false;
74727
- return new CodexcliRule({
75019
+ const frontmatter = rulesyncRule.getFrontmatter();
75020
+ const isRoot = frontmatter.root ?? false;
75021
+ const subprojectPath = frontmatter.agentsmd?.subprojectPath;
75022
+ if (!global && !isRoot && subprojectPath) return new this({
75023
+ outputRoot,
75024
+ relativeDirPath: join(subprojectPath),
75025
+ relativeFilePath: AGENTSMD_RULE_FILE_NAME,
75026
+ fileContent: rulesyncRule.getBody(),
75027
+ validate,
75028
+ root: false
75029
+ });
75030
+ return new this({
74728
75031
  outputRoot,
74729
75032
  relativeDirPath: root.relativeDirPath,
74730
75033
  relativeFilePath: root.relativeFilePath,
@@ -74734,7 +75037,9 @@ var CodexcliRule = class CodexcliRule extends ToolRule {
74734
75037
  });
74735
75038
  }
74736
75039
  toRulesyncRule() {
74737
- return this.toRulesyncRuleDefault();
75040
+ const subprojectPath = this.getSubprojectPath();
75041
+ if (subprojectPath === void 0) return this.toRulesyncRuleDefault();
75042
+ return this.toRulesyncRuleNestedAgentsmd({ subprojectPath });
74738
75043
  }
74739
75044
  validate() {
74740
75045
  return {
@@ -74743,23 +75048,54 @@ var CodexcliRule = class CodexcliRule extends ToolRule {
74743
75048
  };
74744
75049
  }
74745
75050
  static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
74746
- return new CodexcliRule({
75051
+ const isRoot = relativeFilePath === "AGENTS.md" && (relativeDirPath === "." || relativeDirPath === this.getFamily().globalDir);
75052
+ return new this({
74747
75053
  outputRoot,
74748
75054
  relativeDirPath,
74749
75055
  relativeFilePath,
74750
75056
  fileContent: "",
74751
75057
  validate: false,
74752
- root: relativeFilePath === "AGENTS.md" && (relativeDirPath === "." || relativeDirPath === ".codex")
75058
+ root: isRoot
74753
75059
  });
74754
75060
  }
74755
75061
  static isTargetedByRulesyncRule(rulesyncRule) {
74756
75062
  return this.isTargetedByRulesyncRuleDefault({
74757
75063
  rulesyncRule,
74758
- toolTarget: "codexcli"
75064
+ toolTarget: this.getFamily().toolTarget
74759
75065
  });
74760
75066
  }
74761
75067
  };
74762
75068
  //#endregion
75069
+ //#region src/features/rules/codexcli-rule.ts
75070
+ /**
75071
+ * Rule generator for OpenAI Codex CLI.
75072
+ *
75073
+ * Codex CLI loads instructions only from the `AGENTS.md` family: the global
75074
+ * `~/.codex/AGENTS.md` (or `AGENTS.override.md`), then one file per directory
75075
+ * level from the project root down to the current working directory, joined
75076
+ * root-first so deeper files take precedence. Nested per-directory files are
75077
+ * therefore a real scoping surface, and Codex's docs recommend splitting large
75078
+ * instruction sets across nested directories. It does NOT scan a
75079
+ * `.codex/memories/` directory for instruction files — that directory belongs
75080
+ * to Codex's separate SQLite-backed auto-memory system.
75081
+ *
75082
+ * A non-root rule carrying `agentsmd.subprojectPath` is emitted as a nested
75083
+ * `<subprojectPath>/AGENTS.md` (project scope only); every other non-root rule
75084
+ * folds into the root `AGENTS.md`, since Codex has no modular rules directory.
75085
+ * The project files on the root-to-cwd chain share one `project_doc_max_bytes`
75086
+ * budget (32 KiB by default), which the RulesProcessor warns about.
75087
+ *
75088
+ * @see https://learn.chatgpt.com/docs/agent-configuration/agents-md
75089
+ */
75090
+ var CodexcliRule = class extends NestedAgentsmdRule {
75091
+ static getFamily() {
75092
+ return {
75093
+ globalDir: CODEXCLI_DIR,
75094
+ toolTarget: "codexcli"
75095
+ };
75096
+ }
75097
+ };
75098
+ //#endregion
74763
75099
  //#region src/features/rules/commandcode-rule.ts
74764
75100
  var CommandcodeRule = class CommandcodeRule extends ToolRule {
74765
75101
  constructor({ fileContent, root, ...rest }) {
@@ -76064,122 +76400,6 @@ var DevinRule = class DevinRule extends ToolRule {
76064
76400
  }
76065
76401
  };
76066
76402
  //#endregion
76067
- //#region src/features/rules/nested-agentsmd-rule.ts
76068
- /**
76069
- * Shared adapter for the targets that read instructions exactly the way the
76070
- * AGENTS.md standard describes them and nothing more: a root `AGENTS.md` at the
76071
- * project root, nested per-directory `AGENTS.md` files loaded only while working
76072
- * under that directory, and a single personal root file under a tool-specific
76073
- * home directory. Such a target has no modular non-root instruction directory,
76074
- * so every other non-root rule folds into the root file.
76075
- *
76076
- * Concrete targets (Pool, Vibe, DeepSeek Harness) only supply the global
76077
- * directory and their tool target through {@link getFamily}; the vendor docs
76078
- * describing each one's discovery walk live on the subclass.
76079
- *
76080
- * The nested-file scan mirrors the AGENTS.md standard's nested discovery —
76081
- * same file name, same exclusions, import-only, project scope — because it
76082
- * discovers literally the same files.
76083
- */
76084
- var NestedAgentsmdRule = class extends ToolRule {
76085
- static getFamily() {
76086
- throw new Error("Please implement this method in the subclass.");
76087
- }
76088
- static getSettablePaths({ global = false } = {}) {
76089
- if (global) return { root: {
76090
- relativeDirPath: this.getFamily().globalDir,
76091
- relativeFilePath: AGENTSMD_RULE_FILE_NAME
76092
- } };
76093
- return { root: {
76094
- relativeDirPath: ".",
76095
- relativeFilePath: AGENTSMD_RULE_FILE_NAME
76096
- } };
76097
- }
76098
- static getNestedFilePatterns() {
76099
- return this.buildNestedFilePatterns({ fileName: AGENTSMD_RULE_FILE_NAME });
76100
- }
76101
- /**
76102
- * The subproject directory this rule scopes, or `undefined` for the root file
76103
- * (project or global).
76104
- */
76105
- getSubprojectPath() {
76106
- return this.getNestedSubprojectPath({ fileName: AGENTSMD_RULE_FILE_NAME });
76107
- }
76108
- static async fromFile({ outputRoot = process.cwd(), relativeFilePath: _relativeFilePath, relativeDirPath: overrideDirPath, validate = true, global = false }) {
76109
- const { root } = this.getSettablePaths({ global });
76110
- if (overrideDirPath !== void 0 && overrideDirPath !== root.relativeDirPath && overrideDirPath !== ".") {
76111
- const fileContent = await readFileContent(join(outputRoot, overrideDirPath, AGENTSMD_RULE_FILE_NAME));
76112
- return new this({
76113
- outputRoot,
76114
- relativeDirPath: overrideDirPath,
76115
- relativeFilePath: AGENTSMD_RULE_FILE_NAME,
76116
- fileContent,
76117
- validate,
76118
- root: false
76119
- });
76120
- }
76121
- const fileContent = await readFileContent(join(outputRoot, root.relativeDirPath, root.relativeFilePath));
76122
- return new this({
76123
- outputRoot,
76124
- relativeDirPath: root.relativeDirPath,
76125
- relativeFilePath: root.relativeFilePath,
76126
- fileContent,
76127
- validate,
76128
- root: true
76129
- });
76130
- }
76131
- static fromRulesyncRule({ outputRoot = process.cwd(), rulesyncRule, validate = true, global = false }) {
76132
- const { root } = this.getSettablePaths({ global });
76133
- const frontmatter = rulesyncRule.getFrontmatter();
76134
- const isRoot = frontmatter.root ?? false;
76135
- const subprojectPath = frontmatter.agentsmd?.subprojectPath;
76136
- if (!global && !isRoot && subprojectPath) return new this({
76137
- outputRoot,
76138
- relativeDirPath: join(subprojectPath),
76139
- relativeFilePath: AGENTSMD_RULE_FILE_NAME,
76140
- fileContent: rulesyncRule.getBody(),
76141
- validate,
76142
- root: false
76143
- });
76144
- return new this({
76145
- outputRoot,
76146
- relativeDirPath: root.relativeDirPath,
76147
- relativeFilePath: root.relativeFilePath,
76148
- fileContent: rulesyncRule.getBody(),
76149
- validate,
76150
- root: isRoot
76151
- });
76152
- }
76153
- toRulesyncRule() {
76154
- const subprojectPath = this.getSubprojectPath();
76155
- if (subprojectPath === void 0) return this.toRulesyncRuleDefault();
76156
- return this.toRulesyncRuleNestedAgentsmd({ subprojectPath });
76157
- }
76158
- validate() {
76159
- return {
76160
- success: true,
76161
- error: null
76162
- };
76163
- }
76164
- static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
76165
- const isRoot = relativeFilePath === "AGENTS.md" && (relativeDirPath === "." || relativeDirPath === this.getFamily().globalDir);
76166
- return new this({
76167
- outputRoot,
76168
- relativeDirPath,
76169
- relativeFilePath,
76170
- fileContent: "",
76171
- validate: false,
76172
- root: isRoot
76173
- });
76174
- }
76175
- static isTargetedByRulesyncRule(rulesyncRule) {
76176
- return this.isTargetedByRulesyncRuleDefault({
76177
- rulesyncRule,
76178
- toolTarget: this.getFamily().toolTarget
76179
- });
76180
- }
76181
- };
76182
- //#endregion
76183
76403
  //#region src/features/rules/dsh-rule.ts
76184
76404
  /**
76185
76405
  * DeepSeek Harness (`dsh`) rules.
@@ -76391,7 +76611,8 @@ var FactorydroidRule = class FactorydroidRule extends ToolRule {
76391
76611
  rulesyncRule,
76392
76612
  validate,
76393
76613
  rootPath: paths.root,
76394
- nonRootPath: paths.nonRoot
76614
+ nonRootPath: paths.nonRoot,
76615
+ global
76395
76616
  }));
76396
76617
  }
76397
76618
  toRulesyncRule() {
@@ -76858,7 +77079,8 @@ var KiloRule = class KiloRule extends ToolRule {
76858
77079
  rulesyncRule,
76859
77080
  validate,
76860
77081
  rootPath: paths.root,
76861
- nonRootPath: paths.nonRoot
77082
+ nonRootPath: paths.nonRoot,
77083
+ global
76862
77084
  }));
76863
77085
  }
76864
77086
  toRulesyncRule() {
@@ -77362,7 +77584,8 @@ var OpenCodeRule = class OpenCodeRule extends ToolRule {
77362
77584
  rulesyncRule,
77363
77585
  validate,
77364
77586
  rootPath: paths.root,
77365
- nonRootPath: paths.nonRoot
77587
+ nonRootPath: paths.nonRoot,
77588
+ global
77366
77589
  }));
77367
77590
  }
77368
77591
  toRulesyncRule() {
@@ -78831,6 +79054,14 @@ var ZoocodeRule = class extends RooRule {
78831
79054
  //#endregion
78832
79055
  //#region src/features/rules/rules-processor.ts
78833
79056
  const RulesProcessorToolTargetSchema = z.enum(rulesProcessorToolTargetTuple);
79057
+ /**
79058
+ * Normalizes a relative directory path for comparison: POSIX separators, no
79059
+ * `./` segments and no trailing slash, with the current directory as `"."`
79060
+ * (so `"./"`, `"a/"` and `"a"` compare the way `join()` groups them).
79061
+ */
79062
+ const normalizeRelativeDir = (dir) => posix.normalize(toPosixPath(dir)).replace(/\/+$/, "") || ".";
79063
+ /** A tool rule's output path relative to its output root, for comparison. */
79064
+ const toolOutputPath = (toolRule) => posix.join(normalizeRelativeDir(toolRule.getRelativeDirPath()), toolRule.getRelativeFilePath());
78834
79065
  const formatRulePaths = (rules) => rules.map((r) => join(r.getRelativeDirPath(), r.getRelativeFilePath())).join(", ");
78835
79066
  const RulesFeatureOptionsSchema = z.looseObject({
78836
79067
  ruleDiscoveryMode: z.optional(z.enum(["none", "explicit"])),
@@ -78912,7 +79143,9 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
78912
79143
  meta: {
78913
79144
  extension: "md",
78914
79145
  supportsGlobal: true,
78915
- ruleDiscoveryMode: "toon"
79146
+ ruleDiscoveryMode: "toon",
79147
+ ruleDiscoveryModeGlobal: "auto",
79148
+ sharedGlobalNonRootDir: true
78916
79149
  }
78917
79150
  }],
78918
79151
  ["antigravity-ide", {
@@ -78999,7 +79232,11 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
78999
79232
  extension: "md",
79000
79233
  supportsGlobal: true,
79001
79234
  ruleDiscoveryMode: "auto",
79002
- collisionPolicy: "fold"
79235
+ collisionPolicy: "fold",
79236
+ projectInstructionBudget: {
79237
+ bytes: CODEXCLI_PROJECT_DOC_MAX_BYTES,
79238
+ remedy: "Codex CLI stops reading project `AGENTS.md` files once their combined size on the root-to-working-directory chain reaches `project_doc_max_bytes` (32 KiB by default) and silently drops the rest. Move directory-scoped rules into nested `AGENTS.md` files with `agentsmd.subprojectPath` (or `deriveSubprojectPathFromGlobs: true` in rulesync.jsonc), trim the rules, or raise `project_doc_max_bytes` in `.codex/config.toml` (ignore this warning if you already have)."
79239
+ }
79003
79240
  }
79004
79241
  }],
79005
79242
  ["commandcode", {
@@ -79457,9 +79694,61 @@ var RulesProcessor = class extends FeatureProcessor {
79457
79694
  toolRules,
79458
79695
  factory
79459
79696
  });
79697
+ this.warnForInstructionBudget({
79698
+ toolRules,
79699
+ meta
79700
+ });
79460
79701
  return outputFiles;
79461
79702
  }
79462
79703
  /**
79704
+ * Warn when a generated project instruction chain exceeds the tool's byte
79705
+ * budget (`meta.projectInstructionBudget`).
79706
+ *
79707
+ * Codex CLI concatenates the project `AGENTS.md` files from the project root
79708
+ * down to the cwd and stops once their combined size reaches
79709
+ * `project_doc_max_bytes`, truncating the file that crosses it — silently, so
79710
+ * without this warning the rules past the limit simply never reach the model.
79711
+ * Each generated file is measured together with the generated files in its
79712
+ * ancestor directories, since that is the chain Codex loads while working
79713
+ * there. When the root file alone is over the budget every chain is, so only
79714
+ * the root is reported; likewise each over-budget nested chain is reported
79715
+ * once, at its shallowest directory. With several output roots the warning
79716
+ * names the root it refers to. An output root below the git root is measured
79717
+ * without the parent directories' files Codex would also load, so the
79718
+ * estimate can come out low there. The budget checked is the tool's default: a value
79719
+ * raised by hand in the tool's own config is not read. Global scope is not
79720
+ * checked because Codex loads the personal `~/.codex/AGENTS.md` outside the
79721
+ * project budget.
79722
+ */
79723
+ warnForInstructionBudget({ toolRules, meta }) {
79724
+ const budget = meta.projectInstructionBudget;
79725
+ if (budget === void 0 || this.global) return;
79726
+ const files = toolRules.map((rule) => ({
79727
+ dir: normalizeRelativeDir(rule.getRelativeDirPath()),
79728
+ path: toPosixPath(join(rule.getRelativeDirPath(), rule.getRelativeFilePath())),
79729
+ bytes: Buffer.byteLength(addTrailingNewline(rule.getFileContent()), "utf8")
79730
+ }));
79731
+ const isAncestorOrSelf = (ancestor, dir) => ancestor === "." || ancestor === dir || dir.startsWith(`${ancestor}/`);
79732
+ const location = this.outputRoot === process.cwd() ? "" : ` under '${this.outputRoot}'`;
79733
+ const rootFiles = files.filter(({ dir }) => dir === ".");
79734
+ const rootBytes = rootFiles.reduce((sum, { bytes }) => sum + bytes, 0);
79735
+ if (rootBytes > budget.bytes) {
79736
+ this.logger.warn(`The generated ${this.toolTarget} root instruction file${location} (${rootFiles.map(({ path }) => path).join(", ")}) is ${rootBytes} bytes, over the ${budget.bytes}-byte limit. ${budget.remedy}`);
79737
+ return;
79738
+ }
79739
+ const reportedDirs = [];
79740
+ const nestedFiles = files.filter(({ dir }) => dir !== ".").toSorted((a, b) => a.dir.split("/").length - b.dir.split("/").length);
79741
+ for (const file of nestedFiles) {
79742
+ if (reportedDirs.some((dir) => isAncestorOrSelf(dir, file.dir))) continue;
79743
+ const chain = files.filter(({ dir }) => isAncestorOrSelf(dir, file.dir));
79744
+ const chainBytes = chain.reduce((sum, { bytes }) => sum + bytes, 0);
79745
+ if (chainBytes > budget.bytes) {
79746
+ reportedDirs.push(file.dir);
79747
+ this.logger.warn(`The generated ${this.toolTarget} instruction files${location} loaded while working in '${file.dir}' (${chain.map(({ path }) => path).join(", ")}) total ${chainBytes} bytes, over the ${budget.bytes}-byte limit (as is every directory below it). ${budget.remedy}`);
79748
+ }
79749
+ }
79750
+ }
79751
+ /**
79463
79752
  * Handle localRoot rules (only in non-global mode and when enabled). Mutates
79464
79753
  * `toolRules` in place.
79465
79754
  */
@@ -79935,7 +80224,7 @@ var RulesProcessor = class extends FeatureProcessor {
79935
80224
  */
79936
80225
  generateReferenceSectionFromMeta(meta, toolRules) {
79937
80226
  switch (resolveRuleDiscoveryMode({
79938
- defaultMode: meta.ruleDiscoveryMode,
80227
+ defaultMode: this.global && meta.ruleDiscoveryModeGlobal ? meta.ruleDiscoveryModeGlobal : meta.ruleDiscoveryMode,
79939
80228
  options: this.featureOptions
79940
80229
  })) {
79941
80230
  case "toon": return this.generateToonReferencesSection(toolRules);
@@ -80131,14 +80420,14 @@ As this project's AI coding tool, you must follow the additional conventions bel
80131
80420
  * decides for all of them: its `pi.contextFile` is copied onto the non-root
80132
80421
  * rules, and the flag set only on a non-root rule is dropped with a warning.
80133
80422
  */
80134
- alignPiContextFile(rules) {
80423
+ alignPiContextFile(rules, { warn = true } = {}) {
80135
80424
  if (this.toolTarget !== "pi") return rules;
80136
80425
  const factory = this.getFactory(this.toolTarget);
80137
80426
  const targeted = rules.filter((rule) => factory.class.isTargetedByRulesyncRule(rule));
80138
80427
  const rootContextFile = targeted.some((rule) => rule.getFrontmatter().root === true && rule.getFrontmatter().pi?.contextFile === "override") ? "override" : void 0;
80139
80428
  const mismatched = targeted.filter((rule) => rule.getFrontmatter().pi?.contextFile !== rootContextFile);
80140
80429
  if (mismatched.length === 0) return rules;
80141
- if (rootContextFile === void 0) this.logger.warn(`pi.contextFile is set on ${mismatched.length} non-root rule(s) but not on the root rule, so it is ignored: Pi folds every rule body into the root context file, and emitting AGENTS.override.md for some of them would hide the rest. Set it on the root rule instead: ${formatRulePaths(mismatched)}`);
80430
+ if (warn && rootContextFile === void 0) this.logger.warn(`pi.contextFile is set on ${mismatched.length} non-root rule(s) but not on the root rule, so it is ignored: Pi folds every rule body into the root context file, and emitting AGENTS.override.md for some of them would hide the rest. Set it on the root rule instead: ${formatRulePaths(mismatched)}`);
80142
80431
  const mismatchedSet = new Set(mismatched);
80143
80432
  return rules.map((rule) => {
80144
80433
  if (!mismatchedSet.has(rule)) return rule;
@@ -80192,14 +80481,72 @@ As this project's AI coding tool, you must follow the additional conventions bel
80192
80481
  * duplication check the same way `loadRulesyncFiles`'s global-mode branch
80193
80482
  * excludes it from `nonRootRules`: `generate` ignores `localRoot` entirely
80194
80483
  * in global mode, so such a rule is never actually folded into the global
80195
- * root output and warning about it here would be inaccurate.
80484
+ * root output and warning about it here would be inaccurate. The same goes
80485
+ * for a tool that writes `localRoot` to a dedicated local file.
80486
+ *
80487
+ * Non-root rules written to a file other than the root (nested
80488
+ * per-directory files, Pi's `APPEND_SYSTEM.md`) get a separate warning when
80489
+ * that file already exists and would import under a different name.
80196
80490
  */
80197
80491
  async warnForFoldImportDuplicationRisk() {
80198
80492
  const factory = this.getFactory(this.toolTarget);
80199
80493
  if (factory.meta.collisionPolicy !== "fold") return;
80200
- const nonRootRules = (await this.loadMergedRulesyncRules()).filter((rule) => !rule.getFrontmatter().root && (!this.global || !rule.getFrontmatter().localRoot) && factory.class.isTargetedByRulesyncRule(rule));
80201
- if (nonRootRules.length === 0) return;
80202
- this.logger.warn(`Importing ${this.toolTarget}'s root file will re-add content already folded from ${formatRulePaths(nonRootRules)}: ${this.toolTarget} concatenates every non-root rule into its single root output file, so the imported copy duplicates them the next time you run \`rulesync generate --targets ${this.toolTarget}\`. Review the imported rule and remove the duplicated content, or remove the original non-root rule files, before generating again.`);
80494
+ const targetedRules = this.alignPiContextFile(await this.loadMergedRulesyncRules(), { warn: false }).filter((rule) => factory.class.isTargetedByRulesyncRule(rule));
80495
+ let includeLocalRoot = !this.global;
80496
+ try {
80497
+ includeLocalRoot &&= resolveIncludeLocalRoot(this.featureOptions);
80498
+ } catch {}
80499
+ const targetedNonRootRules = targetedRules.filter((rule) => !rule.getFrontmatter().root && !(rule.getFrontmatter().localRoot && (!includeLocalRoot || factory.meta.localRootMode === "separate-local-file")));
80500
+ const rootPaths = /* @__PURE__ */ new Set();
80501
+ const { root } = factory.class.getSettablePaths({ global: this.global });
80502
+ if (root) rootPaths.add(posix.join(normalizeRelativeDir(root.relativeDirPath), root.relativeFilePath));
80503
+ for (const rulesyncRule of targetedRules.filter((rule) => rule.getFrontmatter().root)) {
80504
+ const toolRule = this.toToolRuleForImportCheck({
80505
+ factory,
80506
+ rulesyncRule
80507
+ });
80508
+ rootPaths.add(toolOutputPath(toolRule));
80509
+ }
80510
+ const separateRules = [];
80511
+ const foldedRules = [];
80512
+ let localRootCounted = false;
80513
+ for (const rulesyncRule of targetedNonRootRules) {
80514
+ if (rulesyncRule.getFrontmatter().localRoot) {
80515
+ if (!localRootCounted) {
80516
+ foldedRules.push(rulesyncRule);
80517
+ localRootCounted = true;
80518
+ }
80519
+ continue;
80520
+ }
80521
+ const toolRule = this.toToolRuleForImportCheck({
80522
+ factory,
80523
+ rulesyncRule
80524
+ });
80525
+ if (rootPaths.size === 0 || rootPaths.has(toolOutputPath(toolRule))) foldedRules.push(rulesyncRule);
80526
+ else separateRules.push({
80527
+ toolRule,
80528
+ rulesyncRule
80529
+ });
80530
+ }
80531
+ if (foldedRules.length > 0) this.logger.warn(`Importing ${this.toolTarget}'s root file will re-add content already folded from ${formatRulePaths(foldedRules)}: ${this.toolTarget} concatenates every non-root rule into its single root output file, so the imported copy duplicates them the next time you run \`rulesync generate --targets ${this.toolTarget}\`. Review the imported rule and remove the duplicated content, or remove the original non-root rule files, before generating again.`);
80532
+ const duplicated = [];
80533
+ for (const conversion of separateRules) {
80534
+ const { toolRule, rulesyncRule } = conversion;
80535
+ if (toolRule.toRulesyncRule().getRelativeFilePath().toLowerCase() === rulesyncRule.getRelativeFilePath().toLowerCase()) continue;
80536
+ if (await fileExists(toolRule.getFilePath())) duplicated.push(conversion);
80537
+ }
80538
+ if (duplicated.length > 0) {
80539
+ const fileNames = [...new Set(duplicated.map(({ toolRule }) => toolRule.getRelativeFilePath()))].join(", ");
80540
+ this.logger.warn(`Importing ${this.toolTarget}'s ${fileNames} files outside the root file will re-add content already written from ${formatRulePaths(duplicated.map(({ rulesyncRule }) => rulesyncRule))}: each such file is imported as its own rule under a name derived from its path, so the imported copy and the original rule resolve to the same file and are concatenated the next time you run \`rulesync generate --targets ${this.toolTarget}\`. Remove one of the two copies before generating again.`);
80541
+ }
80542
+ }
80543
+ toToolRuleForImportCheck({ factory, rulesyncRule }) {
80544
+ return factory.class.fromRulesyncRule({
80545
+ outputRoot: this.outputRoot,
80546
+ rulesyncRule,
80547
+ validate: false,
80548
+ global: this.global
80549
+ });
80203
80550
  }
80204
80551
  /**
80205
80552
  * Implementation of abstract method from FeatureProcessor
@@ -80396,7 +80743,8 @@ As this project's AI coding tool, you must follow the additional conventions bel
80396
80743
  const nonRootToolRules = await (async () => {
80397
80744
  if (!settablePaths.nonRoot) return [];
80398
80745
  const nonRootOutputRoot = join(this.outputRoot, settablePaths.nonRoot.relativeDirPath);
80399
- const nonRootFilePaths = await findFilesByGlobs(`**/*.${factory.meta.extension}`, { cwd: nonRootOutputRoot });
80746
+ const isSharedGlobalNonRootDir = this.global && factory.meta.sharedGlobalNonRootDir === true;
80747
+ const nonRootFilePaths = await findFilesByGlobs(`${isSharedGlobalNonRootDir ? "" : "**/"}*.${factory.meta.extension}`, { cwd: nonRootOutputRoot });
80400
80748
  if (forDeletion) return buildDeletionRulesFromPaths(nonRootFilePaths, {
80401
80749
  outputRootOverride: nonRootOutputRoot,
80402
80750
  relativeDirPathOverride: settablePaths.nonRoot.relativeDirPath
@@ -80409,19 +80757,25 @@ As this project's AI coding tool, you must follow the additional conventions bel
80409
80757
  if (!ok) this.logger.warn(`Skipping reserved Rovodev path under modular-rules (import): ${join(modularRootRelative, relativeFilePath)}`);
80410
80758
  return ok;
80411
80759
  }) : nonRootFilePaths).filter((filePath) => rootFileNameInSameDir === void 0 || relative(nonRootOutputRoot, filePath) !== rootFileNameInSameDir);
80412
- return await Promise.all(nonRootPathsForImport.map((filePath) => {
80760
+ return (await Promise.all(nonRootPathsForImport.map(async (filePath) => {
80413
80761
  const relativeFilePath = relative(nonRootOutputRoot, filePath);
80414
80762
  checkPathTraversal({
80415
80763
  relativePath: relativeFilePath,
80416
80764
  intendedRootDir: nonRootOutputRoot
80417
80765
  });
80418
- return factory.class.fromFile({
80419
- outputRoot: this.outputRoot,
80420
- relativeDirPath: modularRootRelative,
80421
- relativeFilePath,
80422
- global: this.global
80423
- });
80424
- }));
80766
+ try {
80767
+ return await factory.class.fromFile({
80768
+ outputRoot: this.outputRoot,
80769
+ relativeDirPath: modularRootRelative,
80770
+ relativeFilePath,
80771
+ global: this.global
80772
+ });
80773
+ } catch (error) {
80774
+ if (!isSharedGlobalNonRootDir) throw error;
80775
+ this.logger.warn(`Skipping ${stripControlCharacters(join(modularRootRelative, relativeFilePath))} for ${this.toolTarget}: ${formatError(error)}`);
80776
+ return;
80777
+ }
80778
+ }))).filter((rule) => rule !== void 0);
80425
80779
  })();
80426
80780
  this.logger.debug(`Found ${nonRootToolRules.length} non-root tool rule files`);
80427
80781
  return [
@@ -80493,6 +80847,14 @@ As this project's AI coding tool, you must follow the additional conventions bel
80493
80847
  function isPackagingToolTarget(toolTarget) {
80494
80848
  return PACKAGING_TOOL_TARGETS.includes(toolTarget);
80495
80849
  }
80850
+ /**
80851
+ * Packaging targets write into a plugin directory and support project scope
80852
+ * only. In global mode their output root would be the home directory, so
80853
+ * callers reject or skip them up front instead of checking all of $HOME.
80854
+ */
80855
+ function formatPackagingTargetGlobalMessage(toolTarget) {
80856
+ return `Target '${toolTarget}' is a plugin packaging target and supports only project scope. Re-run without '--global'.`;
80857
+ }
80496
80858
  async function assertPluginRootSafe(params) {
80497
80859
  if (!isPackagingToolTarget(params.toolTarget)) return;
80498
80860
  await assertDirectoryIfExists(params.outputRoot);
@@ -81412,14 +81774,17 @@ function warnUnsupportedTargets(params) {
81412
81774
  } catch {
81413
81775
  oppositeScopeTargets = [];
81414
81776
  }
81415
- for (const target of config.getTargets()) if (!supportedTargets.includes(target) && config.getFeatures(target).includes(featureName)) {
81416
- const simulateOption = SIMULATE_OPTION_MAP[featureName];
81417
- if (simulateOption && simulatedTargets.includes(target)) logger.warn(`Target '${target}' only supports simulated '${featureName}'. Use '${simulateOption}' to enable it. Skipping.`);
81418
- else if (oppositeScopeTargets.includes(target)) {
81419
- const supportedScope = config.getGlobal() ? "project" : "global";
81420
- const retry = config.getGlobal() ? "without '--global'" : "with '--global'";
81421
- logger.warn(`Target '${target}' supports the feature '${featureName}' only in ${supportedScope} scope. Re-run ${retry}. Skipping.`);
81422
- } else logger.warn(`Target '${target}' does not support the feature '${featureName}'. Skipping.`);
81777
+ for (const target of config.getTargets()) {
81778
+ if (config.getGlobal() && isPackagingToolTarget(target)) continue;
81779
+ if (!supportedTargets.includes(target) && config.getFeatures(target).includes(featureName)) {
81780
+ const simulateOption = SIMULATE_OPTION_MAP[featureName];
81781
+ if (simulateOption && simulatedTargets.includes(target)) logger.warn(`Target '${target}' only supports simulated '${featureName}'. Use '${simulateOption}' to enable it. Skipping.`);
81782
+ else if (oppositeScopeTargets.includes(target)) {
81783
+ const supportedScope = config.getGlobal() ? "project" : "global";
81784
+ const retry = config.getGlobal() ? "without '--global'" : "with '--global'";
81785
+ logger.warn(`Target '${target}' supports the feature '${featureName}' only in ${supportedScope} scope. Re-run ${retry}. Skipping.`);
81786
+ } else logger.warn(`Target '${target}' does not support the feature '${featureName}'. Skipping.`);
81787
+ }
81423
81788
  }
81424
81789
  }
81425
81790
  /**
@@ -81672,10 +82037,16 @@ async function generate(params) {
81672
82037
  const { config, logger } = params;
81673
82038
  resetRootShadowingWarnings({ logger });
81674
82039
  resetRunWarningState();
81675
- for (const toolTarget of config.getTargets()) for (const outputRoot of config.getOutputRoots(toolTarget)) await assertPluginRootSafe({
81676
- toolTarget,
81677
- outputRoot
81678
- });
82040
+ for (const toolTarget of config.getTargets()) {
82041
+ if (config.getGlobal() && isPackagingToolTarget(toolTarget)) {
82042
+ logger.warn(`${formatPackagingTargetGlobalMessage(toolTarget)} Skipping.`);
82043
+ continue;
82044
+ }
82045
+ for (const outputRoot of config.getOutputRoots(toolTarget)) await assertPluginRootSafe({
82046
+ toolTarget,
82047
+ outputRoot
82048
+ });
82049
+ }
81679
82050
  await warnSkillSubagentNameCollisions({
81680
82051
  config,
81681
82052
  logger
@@ -81798,6 +82169,115 @@ function computeRootFileOwnership(params) {
81798
82169
  }
81799
82170
  return ownerByPath;
81800
82171
  }
82172
+ function createSkippedRootFileCheck({ rootFileOwner }) {
82173
+ const skippedByRoot = /* @__PURE__ */ new Map();
82174
+ const emittedByRoot = /* @__PURE__ */ new Map();
82175
+ const emittedKey = (toolTarget, relativePath) => `${toolTarget}\0${relativePath}`;
82176
+ const loadOwnerToolFiles = async (processor) => {
82177
+ const rulesyncFiles = await processor.loadRulesyncFiles();
82178
+ return {
82179
+ toolFiles: rulesyncFiles.length > 0 ? await processor.convertRulesyncFilesToToolFiles(rulesyncFiles) : [],
82180
+ sourceLoadFailed: processor.hasRulesyncSourceLoadFailure()
82181
+ };
82182
+ };
82183
+ return {
82184
+ observe: ({ toolTarget, outputRoot, processor, toolFiles, skipFilePaths }) => {
82185
+ if (rootFileOwner.size === 0) return;
82186
+ for (const toolFile of toolFiles) {
82187
+ const relativePath = toolFile.getRelativePathFromCwd();
82188
+ if (!rootFileOwner.has(relativePath)) continue;
82189
+ const emitted = emittedByRoot.get(outputRoot) ?? /* @__PURE__ */ new Set();
82190
+ emittedByRoot.set(outputRoot, emitted);
82191
+ emitted.add(emittedKey(toolTarget, relativePath));
82192
+ if (!skipFilePaths.has(relativePath)) continue;
82193
+ const skipped = skippedByRoot.get(outputRoot) ?? /* @__PURE__ */ new Map();
82194
+ skippedByRoot.set(outputRoot, skipped);
82195
+ skipped.set(relativePath, {
82196
+ processor,
82197
+ toolFile
82198
+ });
82199
+ }
82200
+ },
82201
+ verify: async ({ isOwnerProcessed, createOwnerProcessor }) => {
82202
+ let count = 0;
82203
+ const paths = [];
82204
+ let sourceLoadFailed = false;
82205
+ for (const [outputRoot, skipped] of skippedByRoot) {
82206
+ const ownerOutputs = /* @__PURE__ */ new Map();
82207
+ for (const [relativePath, fallback] of skipped) {
82208
+ const owner = rootFileOwner.get(relativePath);
82209
+ if (owner === void 0) continue;
82210
+ if (emittedByRoot.get(outputRoot)?.has(emittedKey(owner, relativePath))) continue;
82211
+ let expected = fallback;
82212
+ if (!isOwnerProcessed(owner)) {
82213
+ if (!ownerOutputs.has(owner)) {
82214
+ const processor = await createOwnerProcessor({
82215
+ toolTarget: owner,
82216
+ outputRoot
82217
+ });
82218
+ if (processor === void 0) ownerOutputs.set(owner, void 0);
82219
+ else {
82220
+ const loaded = await loadOwnerToolFiles(processor);
82221
+ if (loaded.sourceLoadFailed) sourceLoadFailed = true;
82222
+ ownerOutputs.set(owner, {
82223
+ processor,
82224
+ toolFiles: loaded.toolFiles
82225
+ });
82226
+ }
82227
+ }
82228
+ const ownerOutput = ownerOutputs.get(owner);
82229
+ const ownerFile = ownerOutput?.toolFiles.find((file) => file.getRelativePathFromCwd() === relativePath && file.getFilePath() === fallback.toolFile.getFilePath());
82230
+ if (ownerOutput !== void 0 && ownerFile !== void 0) expected = {
82231
+ processor: ownerOutput.processor,
82232
+ toolFile: ownerFile
82233
+ };
82234
+ }
82235
+ const result = await expected.processor.writeAiFiles([expected.toolFile]);
82236
+ count += result.count;
82237
+ paths.push(...result.paths);
82238
+ }
82239
+ }
82240
+ return {
82241
+ count,
82242
+ paths,
82243
+ hasDiff: count > 0,
82244
+ sourceLoadFailed
82245
+ };
82246
+ }
82247
+ };
82248
+ }
82249
+ /**
82250
+ * The skills a full `rulesync generate` hands to the rules step: what the
82251
+ * skills step collects for every config-file target that generates skills, in
82252
+ * the same order. Simulated skill sections in a root file list these, so a
82253
+ * narrowed check needs them to rebuild another target's root file.
82254
+ */
82255
+ async function loadConfigFileSkills({ config, logger }) {
82256
+ const supportedSkillsTargets = SkillsProcessor.getToolTargets({
82257
+ global: config.getGlobal(),
82258
+ includeSimulated: config.getSimulateSkills()
82259
+ });
82260
+ const skills = [];
82261
+ for (const toolTarget of intersection(config.getConfigFileTargets(), supportedSkillsTargets)) {
82262
+ if (!config.getConfigFileFeatures(toolTarget).includes("skills")) continue;
82263
+ for (const outputRoot of config.getOutputRoots(toolTarget)) {
82264
+ const processor = new SkillsProcessor({
82265
+ outputRoot: resolveToolOutputRoot({
82266
+ outputRoot,
82267
+ toolTarget,
82268
+ global: config.getGlobal()
82269
+ }),
82270
+ inputRoots: config.getInputRoots(),
82271
+ toolTarget,
82272
+ global: config.getGlobal(),
82273
+ dryRun: true,
82274
+ logger
82275
+ });
82276
+ for (const rulesyncDir of await processor.loadRulesyncDirs()) if (rulesyncDir instanceof RulesyncSkill) skills.push(rulesyncDir);
82277
+ }
82278
+ }
82279
+ return skills;
82280
+ }
81801
82281
  async function generateRulesCore(params) {
81802
82282
  const { config, logger, sweepPlan, skills } = params;
81803
82283
  let totalCount = 0;
@@ -81813,32 +82293,47 @@ async function generateRulesCore(params) {
81813
82293
  logger
81814
82294
  });
81815
82295
  const foldRootOverwriteWatch = createFoldRootOverwriteWatch({ logger });
82296
+ let fullRunSkills;
82297
+ const loadFullRunSkills = () => {
82298
+ fullRunSkills ??= loadConfigFileSkills({
82299
+ config,
82300
+ logger
82301
+ });
82302
+ return fullRunSkills;
82303
+ };
82304
+ const createProcessor = ({ toolTarget, outputRoot, featureOptions, skills: processorSkills }) => new RulesProcessor({
82305
+ outputRoot: resolveToolOutputRoot({
82306
+ outputRoot,
82307
+ toolTarget,
82308
+ global: config.getGlobal()
82309
+ }),
82310
+ inputRoots: config.getInputRoots(),
82311
+ toolTarget,
82312
+ global: config.getGlobal(),
82313
+ simulateCommands: config.getSimulateCommands(),
82314
+ simulateSubagents: config.getSimulateSubagents(),
82315
+ simulateSkills: config.getSimulateSkills(),
82316
+ language: config.getLanguage(),
82317
+ deriveSubprojectPathFromGlobs: config.getDeriveSubprojectPathFromGlobs(),
82318
+ skills: processorSkills,
82319
+ featureOptions,
82320
+ delete: config.getDelete(),
82321
+ dryRun: config.isPreviewMode(),
82322
+ logger
82323
+ });
81816
82324
  const isCheck = config.getCheck();
81817
82325
  const rootFileOwner = isCheck ? computeRootFileOwnership({
81818
- targets: config.getConfigFileTargets(),
82326
+ targets: config.getConfigFileTargets().filter((target) => (toolTargets.includes(target) ? config.getFeatures(target) : config.getConfigFileFeatures(target)).includes("rules")),
81819
82327
  global: config.getGlobal()
81820
82328
  }) : /* @__PURE__ */ new Map();
82329
+ const skippedRootFileCheck = createSkippedRootFileCheck({ rootFileOwner });
81821
82330
  for (const toolTarget of toolTargets) for (const outputRoot of config.getOutputRoots(toolTarget)) {
81822
82331
  if (!config.getFeatures(toolTarget).includes("rules")) continue;
81823
- const processor = new RulesProcessor({
81824
- outputRoot: resolveToolOutputRoot({
81825
- outputRoot,
81826
- toolTarget,
81827
- global: config.getGlobal()
81828
- }),
81829
- inputRoots: config.getInputRoots(),
82332
+ const processor = createProcessor({
81830
82333
  toolTarget,
81831
- global: config.getGlobal(),
81832
- simulateCommands: config.getSimulateCommands(),
81833
- simulateSubagents: config.getSimulateSubagents(),
81834
- simulateSkills: config.getSimulateSkills(),
81835
- language: config.getLanguage(),
81836
- deriveSubprojectPathFromGlobs: config.getDeriveSubprojectPathFromGlobs(),
81837
- skills,
82334
+ outputRoot,
81838
82335
  featureOptions: config.getFeatureOptions(toolTarget, "rules"),
81839
- delete: config.getDelete(),
81840
- dryRun: config.isPreviewMode(),
81841
- logger
82336
+ skills
81842
82337
  });
81843
82338
  const rulesyncFiles = await processor.loadRulesyncFiles();
81844
82339
  const skipFilePaths = /* @__PURE__ */ new Set();
@@ -81851,16 +82346,39 @@ async function generateRulesCore(params) {
81851
82346
  rulesyncFiles,
81852
82347
  sweepPlan,
81853
82348
  skipFilePaths: skipFilePaths.size > 0 ? skipFilePaths : void 0,
81854
- onToolFiles: (toolFiles) => foldRootOverwriteWatch.observe({
81855
- toolTarget,
81856
- toolFiles
81857
- })
82349
+ onToolFiles: (toolFiles) => {
82350
+ foldRootOverwriteWatch.observe({
82351
+ toolTarget,
82352
+ toolFiles
82353
+ });
82354
+ skippedRootFileCheck.observe({
82355
+ toolTarget,
82356
+ outputRoot,
82357
+ processor,
82358
+ toolFiles,
82359
+ skipFilePaths
82360
+ });
82361
+ }
81858
82362
  });
81859
82363
  totalCount += result.count;
81860
82364
  allPaths.push(...result.paths);
81861
82365
  if (result.hasDiff) hasDiff = true;
81862
82366
  if (result.sourceLoadFailed) sourceLoadFailed = true;
81863
82367
  }
82368
+ if (isCheck) {
82369
+ const skippedResult = await skippedRootFileCheck.verify({
82370
+ isOwnerProcessed: (owner) => toolTargets.includes(owner) && config.getFeatures(owner).includes("rules"),
82371
+ createOwnerProcessor: async (ownerParams) => supportedTargets.includes(ownerParams.toolTarget) && config.getOutputRoots(ownerParams.toolTarget).includes(ownerParams.outputRoot) ? createProcessor({
82372
+ ...ownerParams,
82373
+ featureOptions: config.getConfigFileFeatureOptions(ownerParams.toolTarget, "rules"),
82374
+ skills: config.getSimulateSkills() && SkillsProcessor.getToolTargetsSimulated().includes(ownerParams.toolTarget) ? await loadFullRunSkills() : void 0
82375
+ }) : void 0
82376
+ });
82377
+ totalCount += skippedResult.count;
82378
+ allPaths.push(...skippedResult.paths);
82379
+ if (skippedResult.hasDiff) hasDiff = true;
82380
+ if (skippedResult.sourceLoadFailed) sourceLoadFailed = true;
82381
+ }
81864
82382
  foldRootOverwriteWatch.report();
81865
82383
  return {
81866
82384
  count: totalCount,
@@ -82292,6 +82810,7 @@ function getToolOutputRoot({ config, tool }) {
82292
82810
  async function importFromTool(params) {
82293
82811
  const { config, tool, logger } = params;
82294
82812
  resetRunWarningState();
82813
+ if (config.getGlobal() && isPackagingToolTarget(tool)) throw new Error(formatPackagingTargetGlobalMessage(tool));
82295
82814
  await assertPluginRootSafe({
82296
82815
  toolTarget: tool,
82297
82816
  outputRoot: getToolOutputRoot({
@@ -82352,7 +82871,7 @@ async function importRulesCore(params) {
82352
82871
  if (!config.getFeatures(tool).includes("rules")) return 0;
82353
82872
  const global = config.getGlobal();
82354
82873
  if (!RulesProcessor.getToolTargets({ global }).includes(tool)) return 0;
82355
- const rulesProcessor = new RulesProcessor({
82874
+ const baseParams = {
82356
82875
  outputRoot: getToolOutputRoot({
82357
82876
  config,
82358
82877
  tool
@@ -82360,13 +82879,23 @@ async function importRulesCore(params) {
82360
82879
  toolTarget: tool,
82361
82880
  global,
82362
82881
  logger
82363
- });
82882
+ };
82883
+ const rulesProcessor = new RulesProcessor(baseParams);
82364
82884
  const toolFiles = await rulesProcessor.loadToolFiles();
82365
82885
  if (toolFiles.length === 0) {
82366
82886
  logger.warn(`No rule files found for ${tool}. Skipping import.`);
82367
82887
  return 0;
82368
82888
  }
82369
- await rulesProcessor.warnForFoldImportDuplicationRisk();
82889
+ try {
82890
+ await new RulesProcessor({
82891
+ ...baseParams,
82892
+ inputRoots: config.getInputRoots(),
82893
+ deriveSubprojectPathFromGlobs: config.getDeriveSubprojectPathFromGlobs(),
82894
+ featureOptions: config.getFeatureOptions(tool, "rules")
82895
+ }).warnForFoldImportDuplicationRisk();
82896
+ } catch (error) {
82897
+ logger.warn(`Skipped the ${tool} import duplication check: ${formatError(error)}`);
82898
+ }
82370
82899
  const rulesyncFiles = await rulesProcessor.convertToolFilesToRulesyncFiles(toolFiles);
82371
82900
  const { count: writtenCount } = await rulesProcessor.writeAiFiles(rulesyncFiles);
82372
82901
  if (config.getVerbose() && writtenCount > 0) logger.success(`Created ${writtenCount} rule files`);
@@ -82616,4 +83145,4 @@ async function importChecksCore(params) {
82616
83145
  //#endregion
82617
83146
  export { RulesyncCommand as $, toPosixPath as $t, CLAUDECODE_MEMORIES_DIR_NAME as A, RULESYNC_XDG_CONFIG_HOME_DEFAULT_DIR_NAME as An, checkPathTraversal as At, RulesyncSubagent as B, stripControlCharacters as Bn, listDirectoryEntryNames as Bt, COMMANDCODE_DIR as C, RULESYNC_PERMISSIONS_SCHEMA_URL as Cn, withWarnOnceScope as Ct, CODEXCLI_DIR as D, RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH as Dn, assertDirectoryIfExists as Dt, CODEXCLI_BASH_RULES_FILE_NAME as E, RULESYNC_SKILLS_RELATIVE_DIR_PATH as En, applyFileMode as Et, caseFoldIdentity as F, formatError as Fn, getFileSize as Ft, RulesyncRuleFrontmatterSchema as G, readFileContentOrNull as Gt, RulesyncSkill as H, stripHiddenCharacters as Hn, listSubdirectoryNames as Ht, groupSpellingsByCaseFoldedIdentity as I, truncateText as In, getHomeDirectory as It, RulesyncIgnore as J, removeFile as Jt, RulesyncPermissions as K, removeDirectory as Kt, AUGMENTCODE_DIR as L, hasDeceptiveHiddenCharacters as Ln, isFileNotFoundError as Lt, CLAUDECODE_SKILLS_DIR_PATH as M, ALL_FEATURES as Mn, directoryExists as Mt, FACTORYDROID_DIR as N, ALL_FEATURES_WITH_WILDCARD as Nn, ensureDir as Nt, CLAUDECODE_DIR as O, RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH as On, assertTreeContainsNoSymlinks as Ot, FACTORYDROID_SETTINGS_LOCAL_FILE_NAME as P, DEPRECATED_FEATURE_REPLACEMENTS as Pn, fileExists as Pt, parseJsonc as Q, runWithDirectoryRollback as Qt, AUGMENTCODE_SETTINGS_LOCAL_FILE_NAME as R, hasEnclosingMarkOutsideKeycap as Rn, isFileSystemError as Rt, QWENCODE_LOCAL_RULE_FILE_NAME as S, RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH as Sn, resetRunWarningState as St, ChecksProcessor as T, RULESYNC_RULES_RELATIVE_DIR_PATH as Tn, ErrorCodes as Tt, RulesyncSkillFrontmatterSchema as U, pathEscapesRoot as Ut, RulesyncSubagentFrontmatterSchema as V, stripControlCharactersKeepingLineFeeds as Vn, listFilePathsRecursively as Vt, RulesyncRule as W, readFileContent as Wt, getRulesyncSourceCandidates as X, removeTempDirectory as Xt, RulesyncHooks as Y, removeFileStrict as Yt, resolveRulesyncSourceWritePath as Z, resolvePath as Zt, IgnoreProcessor as _, RULESYNC_IGNORE_RELATIVE_FILE_PATH as _n, JsonLogger as _t, getProcessorRegistryEntry as a, ToolTargetSchema as an, SHARED_USER_MANAGED_CONFIG_PATHS as at, CommandsProcessor as b, RULESYNC_MCP_SCHEMA_URL as bn, warnOnConflictingFlags as bt, RulesProcessor as c, MAX_FILE_SIZE as cn, mergeInputRootConfigs as ct, CODEBUDDY_DIR as d, RULESYNC_COMMANDS_RELATIVE_DIR_PATH as dn, ConfigFileSchema as dt, writeFileBuffer as en, RulesyncCommandFrontmatterSchema as et, CODEBUDDY_LOCAL_RULE_FILE_NAME as f, RULESYNC_CONFIG_RELATIVE_FILE_PATH as fn, GITIGNORE_DESTINATION_KEY as ft, McpProcessor as g, RULESYNC_HOOKS_RELATIVE_FILE_PATH as gn, ConsoleLogger as gt, shortenToWidth as h, RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH as hn, findControlCharacter as ht, inspectInputRoots as i, PACKAGING_TOOL_TARGETS as in, loadYaml as it, CLAUDECODE_SETTINGS_LOCAL_FILE_NAME as j, parseCommaSeparatedList as jn, createTempDirectory as jt, CLAUDECODE_LOCAL_RULE_FILE_NAME as k, RULESYNC_USER_CONFIG_DIR_NAME as kn, assertWritablePathInsideRoot as kt, SubagentsProcessor as l, RULESYNC_AIIGNORE_RELATIVE_FILE_PATH as ln, resolveEffectiveInputRoots as lt, displayWidthOf as m, RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH as mn, assertTargetsFeaturesExclusive as mt, formatSourceLoadFailure as n, ALL_TOOL_TARGETS as nn, RulesyncCheckFrontmatterSchema as nt, convertFromTool as o, CURATED_RULES_FEATURE_SUBDIR as on, SKILL_FILE_NAME as ot, ELLIPSIS_WIDTH as p, RULESYNC_CONFIG_SCHEMA_URL as pn, SourceEntrySchema as pt, RulesyncMcp as q, removeDirectoryStrict as qt, generate as r, ALL_TOOL_TARGETS_WITH_WILDCARD as rn, stringifyFrontmatter as rt, isPackagingToolTarget as s, FEATURE_SOURCE_TREE_ENTRIES as sn, ConfigResolver as st, importFromTool as t, writeFileContent as tn, RulesyncCheck as tt, SkillsProcessor as u, RULESYNC_CHECKS_RELATIVE_DIR_PATH as un, CONFLICTING_TARGET_PAIRS as ut, HooksProcessor as v, RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH as vn, WarningCollectingLogger as vt, COMMANDCODE_SETTINGS_LOCAL_FILE_NAME as w, RULESYNC_RELATIVE_DIR_PATH as wn, CLIError as wt, QWENCODE_DIR as x, RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH as xn, withFallbackLoggerTarget as xt, CRUSH_LOCAL_RULE_FILE_NAME as y, RULESYNC_MCP_RELATIVE_FILE_PATH as yn, fallbackLogger as yt, getLocalSkillDirNames as z, quoteForLog as zn, isSymlink as zt };
82618
83147
 
82619
- //# sourceMappingURL=import-CSKYRGa5.js.map
83148
+ //# sourceMappingURL=import-CGIOcUpR.js.map