rulesync 17.1.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.
@@ -44360,7 +44360,7 @@ function addCodexFilesystemRules({ categoryRules, filesystem, workspaceRootFiles
44360
44360
  });
44361
44361
  if (access === void 0) continue;
44362
44362
  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.`);
44363
- 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.`);
44363
+ 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.`);
44364
44364
  addFilesystemRule({
44365
44365
  filesystem,
44366
44366
  workspaceRootFilesystem,
@@ -44520,6 +44520,14 @@ function addFilesystemRule({ filesystem, workspaceRootFilesystem, pattern, acces
44520
44520
  workspaceRootFilesystem[pattern] = access;
44521
44521
  }
44522
44522
  function normalizeCodexFilesystemAccess({ pattern, access, writeRestriction, logger }) {
44523
+ if ((access === "read" || access === "write") && isWindowsDevicePath(pattern)) {
44524
+ if (writeRestriction !== void 0) {
44525
+ 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.`);
44526
+ return "deny";
44527
+ }
44528
+ 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.`);
44529
+ return;
44530
+ }
44523
44531
  if ((access === "read" || access === "write") && hasUnsupportedCodexFilesystemGlob(pattern)) {
44524
44532
  if (writeRestriction !== void 0) {
44525
44533
  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.`);
@@ -44533,6 +44541,9 @@ function normalizeCodexFilesystemAccess({ pattern, access, writeRestriction, log
44533
44541
  function hasCodexFilesystemGlob(pattern) {
44534
44542
  return pattern.includes("*") || pattern.includes("?") || pattern.includes("[") || pattern.includes("]");
44535
44543
  }
44544
+ function isWindowsDevicePath(pattern) {
44545
+ return pattern.startsWith("\\\\?\\") || pattern.startsWith("\\\\.\\");
44546
+ }
44536
44547
  function hasUnsupportedCodexFilesystemGlob(pattern) {
44537
44548
  return hasCodexFilesystemGlob(pattern.endsWith("/**") ? pattern.slice(0, -3) : pattern);
44538
44549
  }
@@ -44674,18 +44685,41 @@ function mapReadAction(action) {
44674
44685
  function mapWriteAction(action) {
44675
44686
  return action === "allow" ? "write" : "read";
44676
44687
  }
44688
+ /**
44689
+ * Merge the canonical read/edit/write category rules into one Codex access
44690
+ * level per path pattern (Codex models a single `deny` < `read` < `write`
44691
+ * level, with no `ask`).
44692
+ *
44693
+ * - `edit` and `write` collapse onto Codex's write side; when both carry the
44694
+ * same pattern, the more restrictive action wins (`deny` > `ask` > `allow`).
44695
+ * - `read: allow` + write-side `allow` → `"write"`.
44696
+ * - A write-side non-allow → `"read"` (readable but not writable — exactly
44697
+ * what Codex's `"read"` level expresses). An `ask` action is approximated
44698
+ * this way because Codex has no path-level write approval.
44699
+ * - A write-side-only non-allow (no `read` rule for the same pattern) whose
44700
+ * path is covered by a broader `read` deny/ask rule (e.g. `X/**` covering
44701
+ * `X/id_rsa`, or `:root`) → `"deny"`. Codex resolves the most specific
44702
+ * path, so emitting `"read"` there would re-open reads the user denied.
44703
+ * - A `read` non-allow on the same pattern → `"deny"` regardless of the
44704
+ * write side; a contradictory write-side `allow` (unreadable but writable
44705
+ * is not expressible in Codex) is warned about.
44706
+ * - Single-category patterns keep the existing mapReadAction/mapWriteAction
44707
+ * mappings.
44708
+ *
44709
+ * Iteration order is read → edit → write with first-seen pattern order, so
44710
+ * the emitted table is stable regardless of the authored category order.
44711
+ * Note the merge is one-way: `"{path}" = "read"` imports back as
44712
+ * `read: allow` only. The write-side restriction is implied by Codex's access
44713
+ * level and not re-materialized, so both `read: allow` + write-side
44714
+ * `ask`/`deny` and a write-side-only `ask`/`deny` (e.g. `edit: { "/x/**":
44715
+ * "deny" }` with no `read` rule) lose their write restriction on
44716
+ * `rulesync import`. This is deliberate: importing `"read"` as an extra
44717
+ * `edit: deny` would push a restriction to every other tool that the Codex
44718
+ * profile alone cannot prove was authored.
44719
+ */
44677
44720
  function mergeFilesystemCategoryRules({ categoryRules, logger }) {
44678
44721
  const readRules = categoryRules.read ?? {};
44679
- const writeSideRestrictiveness = {
44680
- deny: 2,
44681
- ask: 1,
44682
- allow: 0
44683
- };
44684
- const writeSideRules = {};
44685
- for (const category of ["edit", "write"]) for (const [pattern, action] of Object.entries(categoryRules[category] ?? {})) {
44686
- const existing = writeSideRules[pattern];
44687
- if (existing === void 0 || writeSideRestrictiveness[action] > writeSideRestrictiveness[existing]) writeSideRules[pattern] = action;
44688
- }
44722
+ const writeSideRules = collapseWriteSideRules(categoryRules);
44689
44723
  const patterns = [];
44690
44724
  const seen = /* @__PURE__ */ new Set();
44691
44725
  for (const pattern of [...Object.keys(readRules), ...Object.keys(writeSideRules)]) if (!seen.has(pattern)) {
@@ -44696,37 +44730,74 @@ function mergeFilesystemCategoryRules({ categoryRules, logger }) {
44696
44730
  for (const pattern of patterns) {
44697
44731
  const readAction = readRules[pattern];
44698
44732
  const writeAction = writeSideRules[pattern];
44699
- if (readAction === void 0) {
44733
+ if (writeAction === void 0) {
44700
44734
  merged.push({
44701
44735
  pattern,
44702
- access: mapWriteAction(writeAction),
44703
- ...writeAction === "ask" || writeAction === "deny" ? { writeRestriction: writeAction } : {}
44736
+ access: mapReadAction(readAction)
44704
44737
  });
44705
44738
  continue;
44706
44739
  }
44707
- if (writeAction === void 0) {
44740
+ if (readAction !== void 0 && readAction !== "allow") {
44741
+ 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".`);
44708
44742
  merged.push({
44709
44743
  pattern,
44710
- access: mapReadAction(readAction)
44744
+ access: "deny"
44711
44745
  });
44712
44746
  continue;
44713
44747
  }
44714
- if (readAction === "allow") {
44748
+ const writeRestriction = writeAction === "allow" ? void 0 : writeAction;
44749
+ const readUnspecified = readAction === void 0;
44750
+ if (readUnspecified && writeRestriction !== void 0 && isCoveredByReadRestriction({
44751
+ pattern,
44752
+ readRules
44753
+ })) {
44715
44754
  merged.push({
44716
44755
  pattern,
44717
- access: writeAction === "allow" ? "write" : "read",
44718
- ...writeAction === "ask" || writeAction === "deny" ? { writeRestriction: writeAction } : {}
44756
+ access: "deny"
44719
44757
  });
44720
44758
  continue;
44721
44759
  }
44722
- 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".`);
44723
44760
  merged.push({
44724
44761
  pattern,
44725
- access: "deny"
44762
+ access: mapWriteAction(writeAction),
44763
+ ...writeRestriction !== void 0 ? { writeRestriction } : {},
44764
+ ...readUnspecified ? { readUnspecified } : {}
44726
44765
  });
44727
44766
  }
44728
44767
  return merged;
44729
44768
  }
44769
+ function collapseWriteSideRules(categoryRules) {
44770
+ const writeSideRestrictiveness = {
44771
+ deny: 2,
44772
+ ask: 1,
44773
+ allow: 0
44774
+ };
44775
+ const writeSideRules = {};
44776
+ for (const category of ["edit", "write"]) for (const [pattern, action] of Object.entries(categoryRules[category] ?? {})) {
44777
+ const existing = writeSideRules[pattern];
44778
+ if (existing === void 0 || writeSideRestrictiveness[action] > writeSideRestrictiveness[existing]) writeSideRules[pattern] = action;
44779
+ }
44780
+ return writeSideRules;
44781
+ }
44782
+ function isCoveredByReadRestriction({ pattern, readRules }) {
44783
+ const key = stripLeadingDotSlash(pattern);
44784
+ return Object.entries(readRules).some(([readPattern, action]) => {
44785
+ if (action === "allow" || readPattern === pattern) return false;
44786
+ if (readPattern === ":root" || readPattern === "/**") return true;
44787
+ if (readPattern === "**") return !canBeCodexFilesystemRoot(pattern);
44788
+ const withoutTrailingGlob = readPattern.endsWith("/**") ? readPattern.slice(0, -3) : readPattern;
44789
+ if (withoutTrailingGlob.startsWith(":") || hasCodexFilesystemGlob(withoutTrailingGlob)) return false;
44790
+ const base = stripTrailingSlash(stripLeadingDotSlash(withoutTrailingGlob));
44791
+ if (base === "") return false;
44792
+ return key === base || key.startsWith(base.endsWith("/") ? base : `${base}/`);
44793
+ });
44794
+ }
44795
+ function stripLeadingDotSlash(pattern) {
44796
+ return pattern.startsWith("./") ? pattern.slice(2) : pattern;
44797
+ }
44798
+ function stripTrailingSlash(pattern) {
44799
+ return pattern.length > 1 && pattern.endsWith("/") ? pattern.slice(0, -1) : pattern;
44800
+ }
44730
44801
  function buildCodexBashRulesContent(config) {
44731
44802
  const bashRules = bashRulesHonoringAllTools(config.permission);
44732
44803
  const entries = Object.entries(bashRules);
@@ -59571,21 +59642,43 @@ const CodexCliSkillFrontmatterSchema = zod_mini.z.looseObject({
59571
59642
  */
59572
59643
  const CODEX_OPENAI_YAML_RELATIVE_PATH = CODEXCLI_OPENAI_YAML_RELATIVE_PATH;
59573
59644
  /**
59574
- * Build the `agents/openai.yaml` object from a rulesync `codexcli` section.
59575
- * Only produced when the user opts in via `interface`, `policy`, or `dependencies`;
59576
- * a lone legacy `short-description` keeps mapping to `SKILL.md` `metadata` only.
59645
+ * Resolve Codex's `policy.allow_implicit_invocation` from the rulesync inputs.
59646
+ * An explicit `codexcli.policy.allow_implicit_invocation` wins; otherwise the root
59647
+ * `disable-model-invocation: true` maps onto `false`. With neither set the value is
59648
+ * left undefined, since Codex already defaults to `true`.
59649
+ * @see https://github.com/openai/codex/blob/main/codex-rs/skills/src/model.rs
59650
+ */
59651
+ function resolveAllowImplicitInvocation({ codexcli, disableModelInvocation }) {
59652
+ const explicit = codexcli?.policy?.allow_implicit_invocation;
59653
+ if (explicit !== void 0) return explicit;
59654
+ return disableModelInvocation === true ? false : void 0;
59655
+ }
59656
+ /**
59657
+ * Build the `agents/openai.yaml` object from a rulesync `codexcli` section and the
59658
+ * root-level `disable-model-invocation` flag.
59659
+ * Only produced when the user opts in via `interface`, `policy`, or `dependencies`,
59660
+ * or when the root `disable-model-invocation: true` maps onto
59661
+ * `policy.allow_implicit_invocation: false`; a lone legacy `short-description` keeps
59662
+ * mapping to `SKILL.md` `metadata` only.
59577
59663
  * When the sidecar is produced and `interface.short_description` is absent, the
59578
59664
  * legacy `short-description` is routed there (its canonical home per Codex docs).
59579
59665
  */
59580
- function buildOpenaiYamlObject(codexcli) {
59581
- if (!codexcli) return;
59582
- if (!Boolean(codexcli.interface || codexcli.policy || codexcli.dependencies)) return;
59583
- const interfaceSection = { ...codexcli.interface };
59584
- if (interfaceSection.short_description === void 0 && codexcli["short-description"] !== void 0) interfaceSection.short_description = codexcli["short-description"];
59666
+ function buildOpenaiYamlObject({ codexcli, disableModelInvocation }) {
59667
+ const allowImplicitInvocation = resolveAllowImplicitInvocation({
59668
+ codexcli,
59669
+ disableModelInvocation
59670
+ });
59671
+ const policySection = {
59672
+ ...codexcli?.policy,
59673
+ ...allowImplicitInvocation !== void 0 && { allow_implicit_invocation: allowImplicitInvocation }
59674
+ };
59675
+ if (!Boolean(codexcli?.interface || codexcli?.policy || codexcli?.dependencies) && Object.keys(policySection).length === 0) return;
59676
+ const interfaceSection = { ...codexcli?.interface };
59677
+ if (interfaceSection.short_description === void 0 && codexcli?.["short-description"] !== void 0) interfaceSection.short_description = codexcli["short-description"];
59585
59678
  const result = {};
59586
59679
  if (Object.keys(interfaceSection).length > 0) result.interface = interfaceSection;
59587
- if (codexcli.policy && Object.keys(codexcli.policy).length > 0) result.policy = codexcli.policy;
59588
- if (codexcli.dependencies && Object.keys(codexcli.dependencies).length > 0) result.dependencies = codexcli.dependencies;
59680
+ if (Object.keys(policySection).length > 0) result.policy = policySection;
59681
+ if (codexcli?.dependencies && Object.keys(codexcli.dependencies).length > 0) result.dependencies = codexcli.dependencies;
59589
59682
  return Object.keys(result).length > 0 ? result : void 0;
59590
59683
  }
59591
59684
  /**
@@ -59614,6 +59707,11 @@ function extractOpenaiYamlFile(otherFiles) {
59614
59707
  }
59615
59708
  /**
59616
59709
  * Map a parsed `agents/openai.yaml` object back into a rulesync `codexcli` section.
59710
+ * `policy.allow_implicit_invocation` stays under `codexcli.policy` rather than being
59711
+ * promoted to the root `disable-model-invocation`, matching how every other tool's
59712
+ * importer keeps its invocation flags in its own section: promoting it would change
59713
+ * the output of every other target on the next generate, while the section value
59714
+ * regenerates the same sidecar because it takes precedence over the root flag.
59617
59715
  */
59618
59716
  function openaiYamlToCodexcliSection(parsed) {
59619
59717
  if (!parsed) return;
@@ -59712,7 +59810,10 @@ var CodexCliSkill = class CodexCliSkill extends ToolSkill {
59712
59810
  };
59713
59811
  const target = toPosixPath(CODEX_OPENAI_YAML_RELATIVE_PATH);
59714
59812
  const baseOtherFiles = rulesyncSkill.getOtherFiles().filter((file) => toPosixPath(file.relativeFilePathToDirPath) !== target);
59715
- const openaiObject = buildOpenaiYamlObject(rulesyncFrontmatter.codexcli);
59813
+ const openaiObject = buildOpenaiYamlObject({
59814
+ codexcli: rulesyncFrontmatter.codexcli,
59815
+ disableModelInvocation: rulesyncFrontmatter["disable-model-invocation"]
59816
+ });
59716
59817
  const otherFiles = openaiObject ? [...baseOtherFiles, {
59717
59818
  relativeFilePathToDirPath: CODEX_OPENAI_YAML_RELATIVE_PATH,
59718
59819
  fileBuffer: Buffer.from((0, js_yaml.dump)(openaiObject, {
@@ -79137,6 +79238,7 @@ const findFilesWithFallback = async (primaryFilePaths, alternativeRoots, buildAl
79137
79238
  * delete generated files it can no longer regenerate and report success.
79138
79239
  */
79139
79240
  const rootRelativeGlob = (...segments) => node_path.posix.join(...segments.filter((segment) => segment !== void 0).map(toPosixPath));
79241
+ const isRootRule = (rule) => Boolean(rule.getFrontmatter().root || rule.getFrontmatter().localRoot);
79140
79242
  var RulesProcessor = class extends FeatureProcessor {
79141
79243
  toolTarget;
79142
79244
  simulateCommands;
@@ -79148,7 +79250,8 @@ var RulesProcessor = class extends FeatureProcessor {
79148
79250
  getFactory;
79149
79251
  skills;
79150
79252
  featureOptions;
79151
- constructor({ outputRoot = process.cwd(), inputRoots, toolTarget, simulateCommands = false, simulateSubagents = false, simulateSkills = false, language, deriveSubprojectPathFromGlobs = false, global = false, getFactory = defaultGetFactory, skills, featureOptions, dryRun = false, logger }) {
79253
+ deleteOrphans;
79254
+ 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 }) {
79152
79255
  super({
79153
79256
  outputRoot,
79154
79257
  inputRoots,
@@ -79167,6 +79270,7 @@ var RulesProcessor = class extends FeatureProcessor {
79167
79270
  this.getFactory = getFactory;
79168
79271
  this.skills = skills;
79169
79272
  this.featureOptions = featureOptions;
79273
+ this.deleteOrphans = deleteOrphans;
79170
79274
  }
79171
79275
  async convertRulesyncFilesToToolFiles(rulesyncFiles) {
79172
79276
  const rulesyncRules = rulesyncFiles.filter((file) => file instanceof RulesyncRule);
@@ -79174,7 +79278,7 @@ var RulesProcessor = class extends FeatureProcessor {
79174
79278
  const factory = this.getFactory(this.toolTarget);
79175
79279
  const { meta } = factory;
79176
79280
  this.warnForIncludeRootOption(meta);
79177
- const emittedRules = this.omitsRootFiles() ? alignedRules.filter((rule) => !rule.getFrontmatter().root && !rule.getFrontmatter().localRoot) : alignedRules;
79281
+ const emittedRules = this.omitsRootFiles() ? await this.dropRootRules(alignedRules) : alignedRules;
79178
79282
  const localRootRules = emittedRules.filter((rule) => rule.getFrontmatter().localRoot);
79179
79283
  const convertedRules = emittedRules.filter((rule) => !rule.getFrontmatter().localRoot).map((rulesyncRule) => {
79180
79284
  if (!factory.class.isTargetedByRulesyncRule(rulesyncRule)) return null;
@@ -79319,8 +79423,9 @@ var RulesProcessor = class extends FeatureProcessor {
79319
79423
  * non-root `.claude/rules/*.md`. Claude Code falls back to `AGENTS.md` only
79320
79424
  * when none of `CLAUDE.md`, `.claude/CLAUDE.md` or `CLAUDE.local.md` exists,
79321
79425
  * so emitting either root file would shadow an `AGENTS.md` written by another
79322
- * target. Stale root files are swept by the regular orphan deletion because
79323
- * they are no longer part of the generated output. Global scope keeps the
79426
+ * target. With `delete` on, stale root files are swept by the regular orphan
79427
+ * deletion because they are no longer part of the generated output; without
79428
+ * it, conversion warns about them instead. Global scope keeps the
79324
79429
  * root file: `~/.claude/CLAUDE.md` never shadows `AGENTS.md`, and Claude Code
79325
79430
  * reads no global `AGENTS.md` to fall back to. `claudecode-legacy` is not
79326
79431
  * covered: its `.claude/memories/*.md` are only reachable through the
@@ -79350,19 +79455,53 @@ var RulesProcessor = class extends FeatureProcessor {
79350
79455
  }) !== "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.");
79351
79456
  }
79352
79457
  /**
79353
- * With `includeRoot: false`, a hand-authored root file at an alternative
79354
- * location (`.claude/CLAUDE.md`, `.claude/CLAUDE.local.md`) still keeps
79355
- * Claude Code from reading `AGENTS.md`. Rulesync never writes those files, so
79356
- * it leaves them in place and tells the user instead of deleting them.
79458
+ * Drop the root and local-root rules for `includeRoot: false`, reporting what
79459
+ * would otherwise go unnoticed: a dropped rule no other target emits, and a
79460
+ * root file on disk that keeps shadowing `AGENTS.md`. With `delete` on, the
79461
+ * orphan sweep removes the project-root files and reports the rest itself
79462
+ * (see {@link loadToolFiles}), so conversion only reports them when no sweep
79463
+ * runs.
79357
79464
  */
79358
- async warnForShadowingAlternativeRoots(alternativeRoots) {
79359
- const localRootFileName = this.getFactory(this.toolTarget).meta.localRootFileName;
79360
- const globs = alternativeRoots.flatMap((alt) => [rootRelativeGlob(alt.relativeDirPath, alt.relativeFilePath), ...localRootFileName ? [rootRelativeGlob(alt.relativeDirPath, localRootFileName)] : []]);
79361
- if (globs.length === 0) return;
79362
- const filePaths = await findFilesByGlobs(globs, { cwd: this.outputRoot });
79465
+ async dropRootRules(rules) {
79466
+ this.warnForClaudecodeOnlyRootRules(rules.filter(isRootRule));
79467
+ if (!this.deleteOrphans || this.hasRulesyncSourceLoadFailure()) await this.warnForShadowingRootFiles({ includePrimaryRoots: true });
79468
+ return rules.filter((rule) => !isRootRule(rule));
79469
+ }
79470
+ /**
79471
+ * A root or local-root rule that targets `claudecode` alone is dropped by
79472
+ * `includeRoot: false`, and no other target writes it either, so its content
79473
+ * would end up nowhere without a word.
79474
+ */
79475
+ warnForClaudecodeOnlyRootRules(rootRules) {
79476
+ for (const rule of rootRules) {
79477
+ const targets = rule.getFrontmatter().targets ?? ["*"];
79478
+ if (targets.length === 0 || !targets.every((target) => target === "claudecode")) continue;
79479
+ 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.`);
79480
+ }
79481
+ }
79482
+ /**
79483
+ * With `includeRoot: false`, a root file still on disk keeps Claude Code
79484
+ * from reading `AGENTS.md`: a project-root `CLAUDE.md` or `CLAUDE.local.md`
79485
+ * (`includePrimaryRoots`), which only the `delete` sweep removes, and a
79486
+ * hand-authored `.claude/CLAUDE.md` at an alternative location, which
79487
+ * Rulesync never writes and so never deletes. `.claude/CLAUDE.local.md` is
79488
+ * not checked: it is not one of the files that shadow `AGENTS.md`.
79489
+ * @see https://code.claude.com/docs/en/memory#when-claude-code-reads-agents-md
79490
+ */
79491
+ async warnForShadowingRootFiles({ includePrimaryRoots }) {
79492
+ const factory = this.getFactory(this.toolTarget);
79493
+ const settablePaths = factory.class.getSettablePaths({ global: this.global });
79494
+ const primaryDirPath = settablePaths.root?.relativeDirPath ?? ".";
79495
+ const primaryGlobs = (includePrimaryRoots && settablePaths.root ? [settablePaths.root.relativeFilePath, ...factory.meta.localRootFileName ? [factory.meta.localRootFileName] : []] : []).map((fileName) => rootRelativeGlob(primaryDirPath, fileName));
79496
+ const alternativeGlobs = (settablePaths.alternativeRoots ?? []).map((alt) => rootRelativeGlob(alt.relativeDirPath, alt.relativeFilePath));
79497
+ const primaryFilePaths = primaryGlobs.length > 0 ? await findFilesByGlobs(primaryGlobs, { cwd: this.outputRoot }) : [];
79498
+ const alternativeFilePaths = alternativeGlobs.length > 0 ? await findFilesByGlobs(alternativeGlobs, { cwd: this.outputRoot }) : [];
79499
+ const filePaths = [...primaryFilePaths, ...alternativeFilePaths];
79363
79500
  if (filePaths.length === 0) return;
79364
79501
  const names = filePaths.map((filePath) => stripControlCharacters((0, node_path.relative)(this.outputRoot, filePath)));
79365
- this.logger.warn(`${names.join(", ")} still ${names.length === 1 ? "keeps" : "keep"} Claude Code from reading AGENTS.md although \`includeRoot: false\` is set for claudecode. Rulesync does not delete ${names.length === 1 ? "it" : "them"}; remove ${names.length === 1 ? "it" : "them"} by hand once the content is covered by AGENTS.md.`);
79502
+ const one = names.length === 1;
79503
+ const deleteHint = primaryFilePaths.length > 0 ? " Enable `delete` to have Rulesync remove a project-root CLAUDE.md or CLAUDE.local.md itself." : "";
79504
+ 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}`);
79366
79505
  }
79367
79506
  isClaudecodeTarget() {
79368
79507
  return this.toolTarget === "claudecode" || this.toolTarget === "claudecode-legacy";
@@ -80003,7 +80142,7 @@ As this project's AI coding tool, you must follow the additional conventions bel
80003
80142
  const rootToolRules = await (async () => {
80004
80143
  if (!settablePaths.root) return [];
80005
80144
  if (forDeletion && omitsRootFiles) {
80006
- await this.warnForShadowingAlternativeRoots(settablePaths.alternativeRoots ?? []);
80145
+ await this.warnForShadowingRootFiles({ includePrimaryRoots: false });
80007
80146
  return buildDeletionRulesFromPaths(primaryRootFilePaths);
80008
80147
  }
80009
80148
  const uniqueRootFilePaths = await findFilesWithFallback(primaryRootFilePaths, settablePaths.alternativeRoots, (alt) => rootRelativeGlob(alt.relativeDirPath, alt.relativeFilePath), this.outputRoot);
@@ -81565,6 +81704,7 @@ async function generateRulesCore(params) {
81565
81704
  deriveSubprojectPathFromGlobs: config.getDeriveSubprojectPathFromGlobs(),
81566
81705
  skills,
81567
81706
  featureOptions: config.getFeatureOptions(toolTarget, "rules"),
81707
+ delete: config.getDelete(),
81568
81708
  dryRun: config.isPreviewMode(),
81569
81709
  logger
81570
81710
  });
package/dist/index.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_import = require("./import-BtKWW__v.cjs");
2
+ const require_import = require("./import-KY5Q7laH.cjs");
3
3
  //#region src/index.ts
4
4
  async function generate(options = {}) {
5
5
  const { silent = true, verbose = false, ...rest } = options;
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { Mn as ALL_FEATURES, gt as ConsoleLogger, i as inspectInputRoots, nn as ALL_TOOL_TARGETS, o as convertFromTool$1, r as generate$1, st as ConfigResolver, t as importFromTool$1 } from "./import-Csmx6n4n.js";
1
+ import { Mn as ALL_FEATURES, gt as ConsoleLogger, i as inspectInputRoots, nn as ALL_TOOL_TARGETS, o as convertFromTool$1, r as generate$1, st as ConfigResolver, t as importFromTool$1 } from "./import-CA3RFpGB.js";
2
2
  //#region src/index.ts
3
3
  async function generate(options = {}) {
4
4
  const { silent = true, verbose = false, ...rest } = options;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rulesync",
3
- "version": "17.1.0",
3
+ "version": "18.0.0",
4
4
  "description": "Unified AI rules management CLI tool that generates configuration files for various AI development tools",
5
5
  "keywords": [
6
6
  "ai",