rulesync 17.0.0 → 18.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.
@@ -44335,7 +44335,7 @@ function addCodexFilesystemRules({ categoryRules, filesystem, workspaceRootFiles
44335
44335
  });
44336
44336
  if (access === void 0) continue;
44337
44337
  if (access === "read" && rule.writeRestriction === "ask") logger?.warn(`Codex CLI cannot express "ask" for filesystem write permissions: pattern "${rule.pattern}" will be emitted as read-only.`);
44338
- if (access === "read" && rule.writeRestriction === "deny") logger?.warn(`Codex CLI maps a write-side deny to read-only access: pattern "${rule.pattern}" will be emitted as "read". A broader read deny may be overridden by this more-specific path.`);
44338
+ if (access === "read" && rule.writeRestriction === "deny" && rule.readUnspecified) logger?.warn(`Codex CLI maps a write-side deny to read-only access: pattern "${rule.pattern}" will be emitted as "read". A broader read deny may be overridden by this more-specific path.`);
44339
44339
  addFilesystemRule({
44340
44340
  filesystem,
44341
44341
  workspaceRootFilesystem,
@@ -44495,6 +44495,14 @@ function addFilesystemRule({ filesystem, workspaceRootFilesystem, pattern, acces
44495
44495
  workspaceRootFilesystem[pattern] = access;
44496
44496
  }
44497
44497
  function normalizeCodexFilesystemAccess({ pattern, access, writeRestriction, logger }) {
44498
+ if ((access === "read" || access === "write") && isWindowsDevicePath(pattern)) {
44499
+ if (writeRestriction !== void 0) {
44500
+ logger?.warn(`Codex CLI does not support Windows device paths for read/write access: pattern "${pattern}" will be emitted as "deny" because its write-side ${writeRestriction} restriction cannot be represented as read-only access. Use a plain drive path such as "C:\\..." instead.`);
44501
+ return "deny";
44502
+ }
44503
+ logger?.warn(`Skipping unsupported Codex CLI ${access} filesystem entry for Windows device path "${pattern}"; the base permission profile remains in effect. Use a plain drive path such as "C:\\..." instead.`);
44504
+ return;
44505
+ }
44498
44506
  if ((access === "read" || access === "write") && hasUnsupportedCodexFilesystemGlob(pattern)) {
44499
44507
  if (writeRestriction !== void 0) {
44500
44508
  logger?.warn(`Codex CLI only supports deny access for non-trailing filesystem globs: pattern "${pattern}" will be emitted as "deny" because its write-side ${writeRestriction} restriction cannot be represented as read-only access. Use an exact path or trailing "/**" for read/write access.`);
@@ -44508,6 +44516,9 @@ function normalizeCodexFilesystemAccess({ pattern, access, writeRestriction, log
44508
44516
  function hasCodexFilesystemGlob(pattern) {
44509
44517
  return pattern.includes("*") || pattern.includes("?") || pattern.includes("[") || pattern.includes("]");
44510
44518
  }
44519
+ function isWindowsDevicePath(pattern) {
44520
+ return pattern.startsWith("\\\\?\\") || pattern.startsWith("\\\\.\\");
44521
+ }
44511
44522
  function hasUnsupportedCodexFilesystemGlob(pattern) {
44512
44523
  return hasCodexFilesystemGlob(pattern.endsWith("/**") ? pattern.slice(0, -3) : pattern);
44513
44524
  }
@@ -44649,18 +44660,41 @@ function mapReadAction(action) {
44649
44660
  function mapWriteAction(action) {
44650
44661
  return action === "allow" ? "write" : "read";
44651
44662
  }
44663
+ /**
44664
+ * Merge the canonical read/edit/write category rules into one Codex access
44665
+ * level per path pattern (Codex models a single `deny` < `read` < `write`
44666
+ * level, with no `ask`).
44667
+ *
44668
+ * - `edit` and `write` collapse onto Codex's write side; when both carry the
44669
+ * same pattern, the more restrictive action wins (`deny` > `ask` > `allow`).
44670
+ * - `read: allow` + write-side `allow` → `"write"`.
44671
+ * - A write-side non-allow → `"read"` (readable but not writable — exactly
44672
+ * what Codex's `"read"` level expresses). An `ask` action is approximated
44673
+ * this way because Codex has no path-level write approval.
44674
+ * - A write-side-only non-allow (no `read` rule for the same pattern) whose
44675
+ * path is covered by a broader `read` deny/ask rule (e.g. `X/**` covering
44676
+ * `X/id_rsa`, or `:root`) → `"deny"`. Codex resolves the most specific
44677
+ * path, so emitting `"read"` there would re-open reads the user denied.
44678
+ * - A `read` non-allow on the same pattern → `"deny"` regardless of the
44679
+ * write side; a contradictory write-side `allow` (unreadable but writable
44680
+ * is not expressible in Codex) is warned about.
44681
+ * - Single-category patterns keep the existing mapReadAction/mapWriteAction
44682
+ * mappings.
44683
+ *
44684
+ * Iteration order is read → edit → write with first-seen pattern order, so
44685
+ * the emitted table is stable regardless of the authored category order.
44686
+ * Note the merge is one-way: `"{path}" = "read"` imports back as
44687
+ * `read: allow` only. The write-side restriction is implied by Codex's access
44688
+ * level and not re-materialized, so both `read: allow` + write-side
44689
+ * `ask`/`deny` and a write-side-only `ask`/`deny` (e.g. `edit: { "/x/**":
44690
+ * "deny" }` with no `read` rule) lose their write restriction on
44691
+ * `rulesync import`. This is deliberate: importing `"read"` as an extra
44692
+ * `edit: deny` would push a restriction to every other tool that the Codex
44693
+ * profile alone cannot prove was authored.
44694
+ */
44652
44695
  function mergeFilesystemCategoryRules({ categoryRules, logger }) {
44653
44696
  const readRules = categoryRules.read ?? {};
44654
- const writeSideRestrictiveness = {
44655
- deny: 2,
44656
- ask: 1,
44657
- allow: 0
44658
- };
44659
- const writeSideRules = {};
44660
- for (const category of ["edit", "write"]) for (const [pattern, action] of Object.entries(categoryRules[category] ?? {})) {
44661
- const existing = writeSideRules[pattern];
44662
- if (existing === void 0 || writeSideRestrictiveness[action] > writeSideRestrictiveness[existing]) writeSideRules[pattern] = action;
44663
- }
44697
+ const writeSideRules = collapseWriteSideRules(categoryRules);
44664
44698
  const patterns = [];
44665
44699
  const seen = /* @__PURE__ */ new Set();
44666
44700
  for (const pattern of [...Object.keys(readRules), ...Object.keys(writeSideRules)]) if (!seen.has(pattern)) {
@@ -44671,37 +44705,74 @@ function mergeFilesystemCategoryRules({ categoryRules, logger }) {
44671
44705
  for (const pattern of patterns) {
44672
44706
  const readAction = readRules[pattern];
44673
44707
  const writeAction = writeSideRules[pattern];
44674
- if (readAction === void 0) {
44708
+ if (writeAction === void 0) {
44675
44709
  merged.push({
44676
44710
  pattern,
44677
- access: mapWriteAction(writeAction),
44678
- ...writeAction === "ask" || writeAction === "deny" ? { writeRestriction: writeAction } : {}
44711
+ access: mapReadAction(readAction)
44679
44712
  });
44680
44713
  continue;
44681
44714
  }
44682
- if (writeAction === void 0) {
44715
+ if (readAction !== void 0 && readAction !== "allow") {
44716
+ if (writeAction === "allow") logger?.warn(`Codex CLI cannot express "writable but not readable": pattern "${pattern}" has read: ${readAction} and a write-side allow. Emitting "deny".`);
44683
44717
  merged.push({
44684
44718
  pattern,
44685
- access: mapReadAction(readAction)
44719
+ access: "deny"
44686
44720
  });
44687
44721
  continue;
44688
44722
  }
44689
- if (readAction === "allow") {
44723
+ const writeRestriction = writeAction === "allow" ? void 0 : writeAction;
44724
+ const readUnspecified = readAction === void 0;
44725
+ if (readUnspecified && writeRestriction !== void 0 && isCoveredByReadRestriction({
44726
+ pattern,
44727
+ readRules
44728
+ })) {
44690
44729
  merged.push({
44691
44730
  pattern,
44692
- access: writeAction === "allow" ? "write" : "read",
44693
- ...writeAction === "ask" || writeAction === "deny" ? { writeRestriction: writeAction } : {}
44731
+ access: "deny"
44694
44732
  });
44695
44733
  continue;
44696
44734
  }
44697
- if (writeAction === "allow") logger?.warn(`Codex CLI cannot express "writable but not readable": pattern "${pattern}" has read: ${readAction} and a write-side allow. Emitting "deny".`);
44698
44735
  merged.push({
44699
44736
  pattern,
44700
- access: "deny"
44737
+ access: mapWriteAction(writeAction),
44738
+ ...writeRestriction !== void 0 ? { writeRestriction } : {},
44739
+ ...readUnspecified ? { readUnspecified } : {}
44701
44740
  });
44702
44741
  }
44703
44742
  return merged;
44704
44743
  }
44744
+ function collapseWriteSideRules(categoryRules) {
44745
+ const writeSideRestrictiveness = {
44746
+ deny: 2,
44747
+ ask: 1,
44748
+ allow: 0
44749
+ };
44750
+ const writeSideRules = {};
44751
+ for (const category of ["edit", "write"]) for (const [pattern, action] of Object.entries(categoryRules[category] ?? {})) {
44752
+ const existing = writeSideRules[pattern];
44753
+ if (existing === void 0 || writeSideRestrictiveness[action] > writeSideRestrictiveness[existing]) writeSideRules[pattern] = action;
44754
+ }
44755
+ return writeSideRules;
44756
+ }
44757
+ function isCoveredByReadRestriction({ pattern, readRules }) {
44758
+ const key = stripLeadingDotSlash(pattern);
44759
+ return Object.entries(readRules).some(([readPattern, action]) => {
44760
+ if (action === "allow" || readPattern === pattern) return false;
44761
+ if (readPattern === ":root" || readPattern === "/**") return true;
44762
+ if (readPattern === "**") return !canBeCodexFilesystemRoot(pattern);
44763
+ const withoutTrailingGlob = readPattern.endsWith("/**") ? readPattern.slice(0, -3) : readPattern;
44764
+ if (withoutTrailingGlob.startsWith(":") || hasCodexFilesystemGlob(withoutTrailingGlob)) return false;
44765
+ const base = stripTrailingSlash(stripLeadingDotSlash(withoutTrailingGlob));
44766
+ if (base === "") return false;
44767
+ return key === base || key.startsWith(base.endsWith("/") ? base : `${base}/`);
44768
+ });
44769
+ }
44770
+ function stripLeadingDotSlash(pattern) {
44771
+ return pattern.startsWith("./") ? pattern.slice(2) : pattern;
44772
+ }
44773
+ function stripTrailingSlash(pattern) {
44774
+ return pattern.length > 1 && pattern.endsWith("/") ? pattern.slice(0, -1) : pattern;
44775
+ }
44705
44776
  function buildCodexBashRulesContent(config) {
44706
44777
  const bashRules = bashRulesHonoringAllTools(config.permission);
44707
44778
  const entries = Object.entries(bashRules);
@@ -59546,21 +59617,43 @@ const CodexCliSkillFrontmatterSchema = z.looseObject({
59546
59617
  */
59547
59618
  const CODEX_OPENAI_YAML_RELATIVE_PATH = CODEXCLI_OPENAI_YAML_RELATIVE_PATH;
59548
59619
  /**
59549
- * Build the `agents/openai.yaml` object from a rulesync `codexcli` section.
59550
- * Only produced when the user opts in via `interface`, `policy`, or `dependencies`;
59551
- * a lone legacy `short-description` keeps mapping to `SKILL.md` `metadata` only.
59620
+ * Resolve Codex's `policy.allow_implicit_invocation` from the rulesync inputs.
59621
+ * An explicit `codexcli.policy.allow_implicit_invocation` wins; otherwise the root
59622
+ * `disable-model-invocation: true` maps onto `false`. With neither set the value is
59623
+ * left undefined, since Codex already defaults to `true`.
59624
+ * @see https://github.com/openai/codex/blob/main/codex-rs/skills/src/model.rs
59625
+ */
59626
+ function resolveAllowImplicitInvocation({ codexcli, disableModelInvocation }) {
59627
+ const explicit = codexcli?.policy?.allow_implicit_invocation;
59628
+ if (explicit !== void 0) return explicit;
59629
+ return disableModelInvocation === true ? false : void 0;
59630
+ }
59631
+ /**
59632
+ * Build the `agents/openai.yaml` object from a rulesync `codexcli` section and the
59633
+ * root-level `disable-model-invocation` flag.
59634
+ * Only produced when the user opts in via `interface`, `policy`, or `dependencies`,
59635
+ * or when the root `disable-model-invocation: true` maps onto
59636
+ * `policy.allow_implicit_invocation: false`; a lone legacy `short-description` keeps
59637
+ * mapping to `SKILL.md` `metadata` only.
59552
59638
  * When the sidecar is produced and `interface.short_description` is absent, the
59553
59639
  * legacy `short-description` is routed there (its canonical home per Codex docs).
59554
59640
  */
59555
- function buildOpenaiYamlObject(codexcli) {
59556
- if (!codexcli) return;
59557
- if (!Boolean(codexcli.interface || codexcli.policy || codexcli.dependencies)) return;
59558
- const interfaceSection = { ...codexcli.interface };
59559
- if (interfaceSection.short_description === void 0 && codexcli["short-description"] !== void 0) interfaceSection.short_description = codexcli["short-description"];
59641
+ function buildOpenaiYamlObject({ codexcli, disableModelInvocation }) {
59642
+ const allowImplicitInvocation = resolveAllowImplicitInvocation({
59643
+ codexcli,
59644
+ disableModelInvocation
59645
+ });
59646
+ const policySection = {
59647
+ ...codexcli?.policy,
59648
+ ...allowImplicitInvocation !== void 0 && { allow_implicit_invocation: allowImplicitInvocation }
59649
+ };
59650
+ if (!Boolean(codexcli?.interface || codexcli?.policy || codexcli?.dependencies) && Object.keys(policySection).length === 0) return;
59651
+ const interfaceSection = { ...codexcli?.interface };
59652
+ if (interfaceSection.short_description === void 0 && codexcli?.["short-description"] !== void 0) interfaceSection.short_description = codexcli["short-description"];
59560
59653
  const result = {};
59561
59654
  if (Object.keys(interfaceSection).length > 0) result.interface = interfaceSection;
59562
- if (codexcli.policy && Object.keys(codexcli.policy).length > 0) result.policy = codexcli.policy;
59563
- if (codexcli.dependencies && Object.keys(codexcli.dependencies).length > 0) result.dependencies = codexcli.dependencies;
59655
+ if (Object.keys(policySection).length > 0) result.policy = policySection;
59656
+ if (codexcli?.dependencies && Object.keys(codexcli.dependencies).length > 0) result.dependencies = codexcli.dependencies;
59564
59657
  return Object.keys(result).length > 0 ? result : void 0;
59565
59658
  }
59566
59659
  /**
@@ -59589,6 +59682,11 @@ function extractOpenaiYamlFile(otherFiles) {
59589
59682
  }
59590
59683
  /**
59591
59684
  * Map a parsed `agents/openai.yaml` object back into a rulesync `codexcli` section.
59685
+ * `policy.allow_implicit_invocation` stays under `codexcli.policy` rather than being
59686
+ * promoted to the root `disable-model-invocation`, matching how every other tool's
59687
+ * importer keeps its invocation flags in its own section: promoting it would change
59688
+ * the output of every other target on the next generate, while the section value
59689
+ * regenerates the same sidecar because it takes precedence over the root flag.
59592
59690
  */
59593
59691
  function openaiYamlToCodexcliSection(parsed) {
59594
59692
  if (!parsed) return;
@@ -59687,7 +59785,10 @@ var CodexCliSkill = class CodexCliSkill extends ToolSkill {
59687
59785
  };
59688
59786
  const target = toPosixPath(CODEX_OPENAI_YAML_RELATIVE_PATH);
59689
59787
  const baseOtherFiles = rulesyncSkill.getOtherFiles().filter((file) => toPosixPath(file.relativeFilePathToDirPath) !== target);
59690
- const openaiObject = buildOpenaiYamlObject(rulesyncFrontmatter.codexcli);
59788
+ const openaiObject = buildOpenaiYamlObject({
59789
+ codexcli: rulesyncFrontmatter.codexcli,
59790
+ disableModelInvocation: rulesyncFrontmatter["disable-model-invocation"]
59791
+ });
59691
59792
  const otherFiles = openaiObject ? [...baseOtherFiles, {
59692
59793
  relativeFilePathToDirPath: CODEX_OPENAI_YAML_RELATIVE_PATH,
59693
59794
  fileBuffer: Buffer.from(dump(openaiObject, {
@@ -78576,7 +78677,8 @@ const RulesProcessorToolTargetSchema = z.enum(rulesProcessorToolTargetTuple);
78576
78677
  const formatRulePaths = (rules) => rules.map((r) => join(r.getRelativeDirPath(), r.getRelativeFilePath())).join(", ");
78577
78678
  const RulesFeatureOptionsSchema = z.looseObject({
78578
78679
  ruleDiscoveryMode: z.optional(z.enum(["none", "explicit"])),
78579
- includeLocalRoot: z.optional(z.boolean())
78680
+ includeLocalRoot: z.optional(z.boolean()),
78681
+ includeRoot: z.optional(z.boolean())
78580
78682
  });
78581
78683
  const resolveRuleDiscoveryMode = ({ defaultMode, options }) => {
78582
78684
  if (defaultMode === "claudecode-legacy") return defaultMode;
@@ -78593,6 +78695,13 @@ const resolveIncludeLocalRoot = (options) => {
78593
78695
  if (!parsed.success) throw new Error(`Invalid options for rules feature: ${parsed.error.message}. \`includeLocalRoot\` must be a boolean.`);
78594
78696
  return parsed.data.includeLocalRoot ?? true;
78595
78697
  };
78698
+ const IncludeRootSchema = z.looseObject({ includeRoot: z.optional(z.boolean()) });
78699
+ const resolveIncludeRoot = (options) => {
78700
+ if (!options) return true;
78701
+ const parsed = IncludeRootSchema.safeParse(options);
78702
+ if (!parsed.success) throw new Error(`Invalid options for rules feature: ${parsed.error.message}. \`includeRoot\` must be a boolean.`);
78703
+ return parsed.data.includeRoot ?? true;
78704
+ };
78596
78705
  /**
78597
78706
  * Whether `candidate` is `base` itself or a class that extends it.
78598
78707
  *
@@ -79104,6 +79213,7 @@ const findFilesWithFallback = async (primaryFilePaths, alternativeRoots, buildAl
79104
79213
  * delete generated files it can no longer regenerate and report success.
79105
79214
  */
79106
79215
  const rootRelativeGlob = (...segments) => posix.join(...segments.filter((segment) => segment !== void 0).map(toPosixPath));
79216
+ const isRootRule = (rule) => Boolean(rule.getFrontmatter().root || rule.getFrontmatter().localRoot);
79107
79217
  var RulesProcessor = class extends FeatureProcessor {
79108
79218
  toolTarget;
79109
79219
  simulateCommands;
@@ -79115,7 +79225,8 @@ var RulesProcessor = class extends FeatureProcessor {
79115
79225
  getFactory;
79116
79226
  skills;
79117
79227
  featureOptions;
79118
- constructor({ outputRoot = process.cwd(), inputRoots, toolTarget, simulateCommands = false, simulateSubagents = false, simulateSkills = false, language, deriveSubprojectPathFromGlobs = false, global = false, getFactory = defaultGetFactory, skills, featureOptions, dryRun = false, logger }) {
79228
+ deleteOrphans;
79229
+ constructor({ outputRoot = process.cwd(), inputRoots, toolTarget, simulateCommands = false, simulateSubagents = false, simulateSkills = false, language, deriveSubprojectPathFromGlobs = false, global = false, getFactory = defaultGetFactory, skills, featureOptions, delete: deleteOrphans = false, dryRun = false, logger }) {
79119
79230
  super({
79120
79231
  outputRoot,
79121
79232
  inputRoots,
@@ -79134,15 +79245,17 @@ var RulesProcessor = class extends FeatureProcessor {
79134
79245
  this.getFactory = getFactory;
79135
79246
  this.skills = skills;
79136
79247
  this.featureOptions = featureOptions;
79248
+ this.deleteOrphans = deleteOrphans;
79137
79249
  }
79138
79250
  async convertRulesyncFilesToToolFiles(rulesyncFiles) {
79139
79251
  const rulesyncRules = rulesyncFiles.filter((file) => file instanceof RulesyncRule);
79140
79252
  const alignedRules = this.alignPiContextFile(rulesyncRules);
79141
- const localRootRules = alignedRules.filter((rule) => rule.getFrontmatter().localRoot);
79142
- const nonLocalRootRules = alignedRules.filter((rule) => !rule.getFrontmatter().localRoot);
79143
79253
  const factory = this.getFactory(this.toolTarget);
79144
79254
  const { meta } = factory;
79145
- const convertedRules = nonLocalRootRules.map((rulesyncRule) => {
79255
+ this.warnForIncludeRootOption(meta);
79256
+ const emittedRules = this.omitsRootFiles() ? await this.dropRootRules(alignedRules) : alignedRules;
79257
+ const localRootRules = emittedRules.filter((rule) => rule.getFrontmatter().localRoot);
79258
+ const convertedRules = emittedRules.filter((rule) => !rule.getFrontmatter().localRoot).map((rulesyncRule) => {
79146
79259
  if (!factory.class.isTargetedByRulesyncRule(rulesyncRule)) return null;
79147
79260
  return {
79148
79261
  toolRule: factory.class.fromRulesyncRule({
@@ -79279,6 +79392,92 @@ var RulesProcessor = class extends FeatureProcessor {
79279
79392
  getPromptBlockLanguage() {
79280
79393
  return this.isClaudecodeTarget() ? void 0 : this.language;
79281
79394
  }
79395
+ /**
79396
+ * `includeRoot: false` on the `claudecode` target (project scope only)
79397
+ * drops the root `CLAUDE.md` and the personal `CLAUDE.local.md`, keeping the
79398
+ * non-root `.claude/rules/*.md`. Claude Code falls back to `AGENTS.md` only
79399
+ * when none of `CLAUDE.md`, `.claude/CLAUDE.md` or `CLAUDE.local.md` exists,
79400
+ * so emitting either root file would shadow an `AGENTS.md` written by another
79401
+ * target. With `delete` on, stale root files are swept by the regular orphan
79402
+ * deletion because they are no longer part of the generated output; without
79403
+ * it, conversion warns about them instead. Global scope keeps the
79404
+ * root file: `~/.claude/CLAUDE.md` never shadows `AGENTS.md`, and Claude Code
79405
+ * reads no global `AGENTS.md` to fall back to. `claudecode-legacy` is not
79406
+ * covered: its `.claude/memories/*.md` are only reachable through the
79407
+ * references listed in the root file.
79408
+ * @see https://code.claude.com/docs/en/memory#when-claude-code-reads-agents-md
79409
+ */
79410
+ omitsRootFiles() {
79411
+ return this.toolTarget === "claudecode" && !this.global && !resolveIncludeRoot(this.featureOptions);
79412
+ }
79413
+ /**
79414
+ * Warn when `includeRoot: false` is set where it has no effect, or where it
79415
+ * silently disables another option that writes into the root file.
79416
+ */
79417
+ warnForIncludeRootOption(meta) {
79418
+ if (resolveIncludeRoot(this.featureOptions)) return;
79419
+ if (this.toolTarget !== "claudecode") {
79420
+ this.logger.warn(`The rules option \`includeRoot\` is only supported by the claudecode target; ignoring it for ${this.toolTarget}.`);
79421
+ return;
79422
+ }
79423
+ if (this.global) {
79424
+ this.logger.warn("The rules option `includeRoot: false` has no effect in global mode: ~/.claude/CLAUDE.md does not shadow AGENTS.md, so it is still generated.");
79425
+ return;
79426
+ }
79427
+ if (resolveRuleDiscoveryMode({
79428
+ defaultMode: meta.ruleDiscoveryMode,
79429
+ options: this.featureOptions
79430
+ }) !== "auto") this.logger.warn("The rules option `ruleDiscoveryMode` has no effect with `includeRoot: false` on claudecode: the rule references it adds are written into CLAUDE.md, which is not generated.");
79431
+ }
79432
+ /**
79433
+ * Drop the root and local-root rules for `includeRoot: false`, reporting what
79434
+ * would otherwise go unnoticed: a dropped rule no other target emits, and a
79435
+ * root file on disk that keeps shadowing `AGENTS.md`. With `delete` on, the
79436
+ * orphan sweep removes the project-root files and reports the rest itself
79437
+ * (see {@link loadToolFiles}), so conversion only reports them when no sweep
79438
+ * runs.
79439
+ */
79440
+ async dropRootRules(rules) {
79441
+ this.warnForClaudecodeOnlyRootRules(rules.filter(isRootRule));
79442
+ if (!this.deleteOrphans || this.hasRulesyncSourceLoadFailure()) await this.warnForShadowingRootFiles({ includePrimaryRoots: true });
79443
+ return rules.filter((rule) => !isRootRule(rule));
79444
+ }
79445
+ /**
79446
+ * A root or local-root rule that targets `claudecode` alone is dropped by
79447
+ * `includeRoot: false`, and no other target writes it either, so its content
79448
+ * would end up nowhere without a word.
79449
+ */
79450
+ warnForClaudecodeOnlyRootRules(rootRules) {
79451
+ for (const rule of rootRules) {
79452
+ const targets = rule.getFrontmatter().targets ?? ["*"];
79453
+ if (targets.length === 0 || !targets.every((target) => target === "claudecode")) continue;
79454
+ this.logger.warn(`${stripControlCharacters(rule.getRelativeFilePath())} is a root rule that targets only claudecode, so \`includeRoot: false\` drops it and no target generates its content. Add the target that writes AGENTS.md to its \`targets\`, or turn it into a non-root rule.`);
79455
+ }
79456
+ }
79457
+ /**
79458
+ * With `includeRoot: false`, a root file still on disk keeps Claude Code
79459
+ * from reading `AGENTS.md`: a project-root `CLAUDE.md` or `CLAUDE.local.md`
79460
+ * (`includePrimaryRoots`), which only the `delete` sweep removes, and a
79461
+ * hand-authored `.claude/CLAUDE.md` at an alternative location, which
79462
+ * Rulesync never writes and so never deletes. `.claude/CLAUDE.local.md` is
79463
+ * not checked: it is not one of the files that shadow `AGENTS.md`.
79464
+ * @see https://code.claude.com/docs/en/memory#when-claude-code-reads-agents-md
79465
+ */
79466
+ async warnForShadowingRootFiles({ includePrimaryRoots }) {
79467
+ const factory = this.getFactory(this.toolTarget);
79468
+ const settablePaths = factory.class.getSettablePaths({ global: this.global });
79469
+ const primaryDirPath = settablePaths.root?.relativeDirPath ?? ".";
79470
+ const primaryGlobs = (includePrimaryRoots && settablePaths.root ? [settablePaths.root.relativeFilePath, ...factory.meta.localRootFileName ? [factory.meta.localRootFileName] : []] : []).map((fileName) => rootRelativeGlob(primaryDirPath, fileName));
79471
+ const alternativeGlobs = (settablePaths.alternativeRoots ?? []).map((alt) => rootRelativeGlob(alt.relativeDirPath, alt.relativeFilePath));
79472
+ const primaryFilePaths = primaryGlobs.length > 0 ? await findFilesByGlobs(primaryGlobs, { cwd: this.outputRoot }) : [];
79473
+ const alternativeFilePaths = alternativeGlobs.length > 0 ? await findFilesByGlobs(alternativeGlobs, { cwd: this.outputRoot }) : [];
79474
+ const filePaths = [...primaryFilePaths, ...alternativeFilePaths];
79475
+ if (filePaths.length === 0) return;
79476
+ const names = filePaths.map((filePath) => stripControlCharacters(relative(this.outputRoot, filePath)));
79477
+ const one = names.length === 1;
79478
+ const deleteHint = primaryFilePaths.length > 0 ? " Enable `delete` to have Rulesync remove a project-root CLAUDE.md or CLAUDE.local.md itself." : "";
79479
+ this.logger.warn(`${names.join(", ")} still ${one ? "keeps" : "keep"} Claude Code from reading AGENTS.md although \`includeRoot: false\` is set for claudecode. Rulesync leaves ${one ? "it" : "them"} in place; remove ${one ? "it" : "them"} by hand once the content is covered by AGENTS.md.${deleteHint}`);
79480
+ }
79282
79481
  isClaudecodeTarget() {
79283
79482
  return this.toolTarget === "claudecode" || this.toolTarget === "claudecode-legacy";
79284
79483
  }
@@ -79913,9 +80112,14 @@ As this project's AI coding tool, you must follow the additional conventions bel
79913
80112
  * Resolved once, up front, so the two blocks that need it do not depend
79914
80113
  * on each other's evaluation order.
79915
80114
  */
80115
+ const omitsRootFiles = this.omitsRootFiles();
79916
80116
  const primaryRootFilePaths = settablePaths.root ? await findFilesByGlobs(rootRelativeGlob(settablePaths.root.relativeDirPath ?? ".", settablePaths.root.relativeFilePath), { cwd: this.outputRoot }) : [];
79917
80117
  const rootToolRules = await (async () => {
79918
80118
  if (!settablePaths.root) return [];
80119
+ if (forDeletion && omitsRootFiles) {
80120
+ await this.warnForShadowingRootFiles({ includePrimaryRoots: false });
80121
+ return buildDeletionRulesFromPaths(primaryRootFilePaths);
80122
+ }
79919
80123
  const uniqueRootFilePaths = await findFilesWithFallback(primaryRootFilePaths, settablePaths.alternativeRoots, (alt) => rootRelativeGlob(alt.relativeDirPath, alt.relativeFilePath), this.outputRoot);
79920
80124
  if (forDeletion) return buildDeletionRulesFromPaths(uniqueRootFilePaths);
79921
80125
  return await buildImportRulesFromPaths(uniqueRootFilePaths);
@@ -79927,7 +80131,9 @@ As this project's AI coding tool, you must follow the additional conventions bel
79927
80131
  const filePaths = await (async () => {
79928
80132
  if (factory.class.getLocalRootFileGlob) return await findFilesByGlobs(factory.class.getLocalRootFileGlob({ fileName }), { cwd: this.outputRoot });
79929
80133
  if (!settablePaths.root) return [];
79930
- return await findFilesWithFallback(await findFilesByGlobs(rootRelativeGlob(settablePaths.root.relativeDirPath ?? ".", fileName), { cwd: this.outputRoot }), settablePaths.alternativeRoots, (alt) => rootRelativeGlob(alt.relativeDirPath, fileName), this.outputRoot);
80134
+ const primaryLocalFilePaths = await findFilesByGlobs(rootRelativeGlob(settablePaths.root.relativeDirPath ?? ".", fileName), { cwd: this.outputRoot });
80135
+ if (forDeletion && omitsRootFiles) return primaryLocalFilePaths;
80136
+ return await findFilesWithFallback(primaryLocalFilePaths, settablePaths.alternativeRoots, (alt) => rootRelativeGlob(alt.relativeDirPath, fileName), this.outputRoot);
79931
80137
  })();
79932
80138
  if (forDeletion) return buildDeletionRulesFromPaths(filePaths);
79933
80139
  return (await Promise.all(filePaths.map(async (filePath) => {
@@ -81473,6 +81679,7 @@ async function generateRulesCore(params) {
81473
81679
  deriveSubprojectPathFromGlobs: config.getDeriveSubprojectPathFromGlobs(),
81474
81680
  skills,
81475
81681
  featureOptions: config.getFeatureOptions(toolTarget, "rules"),
81682
+ delete: config.getDelete(),
81476
81683
  dryRun: config.isPreviewMode(),
81477
81684
  logger
81478
81685
  });
@@ -82252,4 +82459,4 @@ async function importChecksCore(params) {
82252
82459
  //#endregion
82253
82460
  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 };
82254
82461
 
82255
- //# sourceMappingURL=import-CEgvxK12.js.map
82462
+ //# sourceMappingURL=import-CA3RFpGB.js.map