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.
- package/dist/cli/index.cjs +4 -4
- package/dist/cli/index.js +4 -4
- package/dist/cli/index.js.map +1 -1
- package/dist/{import-CEgvxK12.js → import-CA3RFpGB.js} +246 -39
- package/dist/{import-CEgvxK12.js.map → import-CA3RFpGB.js.map} +1 -1
- package/dist/{import-j-fApVPQ.cjs → import-KY5Q7laH.cjs} +245 -38
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/package.json +16 -16
|
@@ -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
|
|
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 (
|
|
44733
|
+
if (writeAction === void 0) {
|
|
44700
44734
|
merged.push({
|
|
44701
44735
|
pattern,
|
|
44702
|
-
access:
|
|
44703
|
-
...writeAction === "ask" || writeAction === "deny" ? { writeRestriction: writeAction } : {}
|
|
44736
|
+
access: mapReadAction(readAction)
|
|
44704
44737
|
});
|
|
44705
44738
|
continue;
|
|
44706
44739
|
}
|
|
44707
|
-
if (
|
|
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:
|
|
44744
|
+
access: "deny"
|
|
44711
44745
|
});
|
|
44712
44746
|
continue;
|
|
44713
44747
|
}
|
|
44714
|
-
|
|
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:
|
|
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:
|
|
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
|
-
*
|
|
59575
|
-
*
|
|
59576
|
-
*
|
|
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
|
-
|
|
59582
|
-
|
|
59583
|
-
|
|
59584
|
-
|
|
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 (
|
|
59588
|
-
if (codexcli
|
|
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(
|
|
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, {
|
|
@@ -78601,7 +78702,8 @@ const RulesProcessorToolTargetSchema = zod_mini.z.enum(rulesProcessorToolTargetT
|
|
|
78601
78702
|
const formatRulePaths = (rules) => rules.map((r) => (0, node_path.join)(r.getRelativeDirPath(), r.getRelativeFilePath())).join(", ");
|
|
78602
78703
|
const RulesFeatureOptionsSchema = zod_mini.z.looseObject({
|
|
78603
78704
|
ruleDiscoveryMode: zod_mini.z.optional(zod_mini.z.enum(["none", "explicit"])),
|
|
78604
|
-
includeLocalRoot: zod_mini.z.optional(zod_mini.z.boolean())
|
|
78705
|
+
includeLocalRoot: zod_mini.z.optional(zod_mini.z.boolean()),
|
|
78706
|
+
includeRoot: zod_mini.z.optional(zod_mini.z.boolean())
|
|
78605
78707
|
});
|
|
78606
78708
|
const resolveRuleDiscoveryMode = ({ defaultMode, options }) => {
|
|
78607
78709
|
if (defaultMode === "claudecode-legacy") return defaultMode;
|
|
@@ -78618,6 +78720,13 @@ const resolveIncludeLocalRoot = (options) => {
|
|
|
78618
78720
|
if (!parsed.success) throw new Error(`Invalid options for rules feature: ${parsed.error.message}. \`includeLocalRoot\` must be a boolean.`);
|
|
78619
78721
|
return parsed.data.includeLocalRoot ?? true;
|
|
78620
78722
|
};
|
|
78723
|
+
const IncludeRootSchema = zod_mini.z.looseObject({ includeRoot: zod_mini.z.optional(zod_mini.z.boolean()) });
|
|
78724
|
+
const resolveIncludeRoot = (options) => {
|
|
78725
|
+
if (!options) return true;
|
|
78726
|
+
const parsed = IncludeRootSchema.safeParse(options);
|
|
78727
|
+
if (!parsed.success) throw new Error(`Invalid options for rules feature: ${parsed.error.message}. \`includeRoot\` must be a boolean.`);
|
|
78728
|
+
return parsed.data.includeRoot ?? true;
|
|
78729
|
+
};
|
|
78621
78730
|
/**
|
|
78622
78731
|
* Whether `candidate` is `base` itself or a class that extends it.
|
|
78623
78732
|
*
|
|
@@ -79129,6 +79238,7 @@ const findFilesWithFallback = async (primaryFilePaths, alternativeRoots, buildAl
|
|
|
79129
79238
|
* delete generated files it can no longer regenerate and report success.
|
|
79130
79239
|
*/
|
|
79131
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);
|
|
79132
79242
|
var RulesProcessor = class extends FeatureProcessor {
|
|
79133
79243
|
toolTarget;
|
|
79134
79244
|
simulateCommands;
|
|
@@ -79140,7 +79250,8 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
79140
79250
|
getFactory;
|
|
79141
79251
|
skills;
|
|
79142
79252
|
featureOptions;
|
|
79143
|
-
|
|
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 }) {
|
|
79144
79255
|
super({
|
|
79145
79256
|
outputRoot,
|
|
79146
79257
|
inputRoots,
|
|
@@ -79159,15 +79270,17 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
79159
79270
|
this.getFactory = getFactory;
|
|
79160
79271
|
this.skills = skills;
|
|
79161
79272
|
this.featureOptions = featureOptions;
|
|
79273
|
+
this.deleteOrphans = deleteOrphans;
|
|
79162
79274
|
}
|
|
79163
79275
|
async convertRulesyncFilesToToolFiles(rulesyncFiles) {
|
|
79164
79276
|
const rulesyncRules = rulesyncFiles.filter((file) => file instanceof RulesyncRule);
|
|
79165
79277
|
const alignedRules = this.alignPiContextFile(rulesyncRules);
|
|
79166
|
-
const localRootRules = alignedRules.filter((rule) => rule.getFrontmatter().localRoot);
|
|
79167
|
-
const nonLocalRootRules = alignedRules.filter((rule) => !rule.getFrontmatter().localRoot);
|
|
79168
79278
|
const factory = this.getFactory(this.toolTarget);
|
|
79169
79279
|
const { meta } = factory;
|
|
79170
|
-
|
|
79280
|
+
this.warnForIncludeRootOption(meta);
|
|
79281
|
+
const emittedRules = this.omitsRootFiles() ? await this.dropRootRules(alignedRules) : alignedRules;
|
|
79282
|
+
const localRootRules = emittedRules.filter((rule) => rule.getFrontmatter().localRoot);
|
|
79283
|
+
const convertedRules = emittedRules.filter((rule) => !rule.getFrontmatter().localRoot).map((rulesyncRule) => {
|
|
79171
79284
|
if (!factory.class.isTargetedByRulesyncRule(rulesyncRule)) return null;
|
|
79172
79285
|
return {
|
|
79173
79286
|
toolRule: factory.class.fromRulesyncRule({
|
|
@@ -79304,6 +79417,92 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
79304
79417
|
getPromptBlockLanguage() {
|
|
79305
79418
|
return this.isClaudecodeTarget() ? void 0 : this.language;
|
|
79306
79419
|
}
|
|
79420
|
+
/**
|
|
79421
|
+
* `includeRoot: false` on the `claudecode` target (project scope only)
|
|
79422
|
+
* drops the root `CLAUDE.md` and the personal `CLAUDE.local.md`, keeping the
|
|
79423
|
+
* non-root `.claude/rules/*.md`. Claude Code falls back to `AGENTS.md` only
|
|
79424
|
+
* when none of `CLAUDE.md`, `.claude/CLAUDE.md` or `CLAUDE.local.md` exists,
|
|
79425
|
+
* so emitting either root file would shadow an `AGENTS.md` written by another
|
|
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
|
|
79429
|
+
* root file: `~/.claude/CLAUDE.md` never shadows `AGENTS.md`, and Claude Code
|
|
79430
|
+
* reads no global `AGENTS.md` to fall back to. `claudecode-legacy` is not
|
|
79431
|
+
* covered: its `.claude/memories/*.md` are only reachable through the
|
|
79432
|
+
* references listed in the root file.
|
|
79433
|
+
* @see https://code.claude.com/docs/en/memory#when-claude-code-reads-agents-md
|
|
79434
|
+
*/
|
|
79435
|
+
omitsRootFiles() {
|
|
79436
|
+
return this.toolTarget === "claudecode" && !this.global && !resolveIncludeRoot(this.featureOptions);
|
|
79437
|
+
}
|
|
79438
|
+
/**
|
|
79439
|
+
* Warn when `includeRoot: false` is set where it has no effect, or where it
|
|
79440
|
+
* silently disables another option that writes into the root file.
|
|
79441
|
+
*/
|
|
79442
|
+
warnForIncludeRootOption(meta) {
|
|
79443
|
+
if (resolveIncludeRoot(this.featureOptions)) return;
|
|
79444
|
+
if (this.toolTarget !== "claudecode") {
|
|
79445
|
+
this.logger.warn(`The rules option \`includeRoot\` is only supported by the claudecode target; ignoring it for ${this.toolTarget}.`);
|
|
79446
|
+
return;
|
|
79447
|
+
}
|
|
79448
|
+
if (this.global) {
|
|
79449
|
+
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.");
|
|
79450
|
+
return;
|
|
79451
|
+
}
|
|
79452
|
+
if (resolveRuleDiscoveryMode({
|
|
79453
|
+
defaultMode: meta.ruleDiscoveryMode,
|
|
79454
|
+
options: this.featureOptions
|
|
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.");
|
|
79456
|
+
}
|
|
79457
|
+
/**
|
|
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.
|
|
79464
|
+
*/
|
|
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];
|
|
79500
|
+
if (filePaths.length === 0) return;
|
|
79501
|
+
const names = filePaths.map((filePath) => stripControlCharacters((0, node_path.relative)(this.outputRoot, filePath)));
|
|
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}`);
|
|
79505
|
+
}
|
|
79307
79506
|
isClaudecodeTarget() {
|
|
79308
79507
|
return this.toolTarget === "claudecode" || this.toolTarget === "claudecode-legacy";
|
|
79309
79508
|
}
|
|
@@ -79938,9 +80137,14 @@ As this project's AI coding tool, you must follow the additional conventions bel
|
|
|
79938
80137
|
* Resolved once, up front, so the two blocks that need it do not depend
|
|
79939
80138
|
* on each other's evaluation order.
|
|
79940
80139
|
*/
|
|
80140
|
+
const omitsRootFiles = this.omitsRootFiles();
|
|
79941
80141
|
const primaryRootFilePaths = settablePaths.root ? await findFilesByGlobs(rootRelativeGlob(settablePaths.root.relativeDirPath ?? ".", settablePaths.root.relativeFilePath), { cwd: this.outputRoot }) : [];
|
|
79942
80142
|
const rootToolRules = await (async () => {
|
|
79943
80143
|
if (!settablePaths.root) return [];
|
|
80144
|
+
if (forDeletion && omitsRootFiles) {
|
|
80145
|
+
await this.warnForShadowingRootFiles({ includePrimaryRoots: false });
|
|
80146
|
+
return buildDeletionRulesFromPaths(primaryRootFilePaths);
|
|
80147
|
+
}
|
|
79944
80148
|
const uniqueRootFilePaths = await findFilesWithFallback(primaryRootFilePaths, settablePaths.alternativeRoots, (alt) => rootRelativeGlob(alt.relativeDirPath, alt.relativeFilePath), this.outputRoot);
|
|
79945
80149
|
if (forDeletion) return buildDeletionRulesFromPaths(uniqueRootFilePaths);
|
|
79946
80150
|
return await buildImportRulesFromPaths(uniqueRootFilePaths);
|
|
@@ -79952,7 +80156,9 @@ As this project's AI coding tool, you must follow the additional conventions bel
|
|
|
79952
80156
|
const filePaths = await (async () => {
|
|
79953
80157
|
if (factory.class.getLocalRootFileGlob) return await findFilesByGlobs(factory.class.getLocalRootFileGlob({ fileName }), { cwd: this.outputRoot });
|
|
79954
80158
|
if (!settablePaths.root) return [];
|
|
79955
|
-
|
|
80159
|
+
const primaryLocalFilePaths = await findFilesByGlobs(rootRelativeGlob(settablePaths.root.relativeDirPath ?? ".", fileName), { cwd: this.outputRoot });
|
|
80160
|
+
if (forDeletion && omitsRootFiles) return primaryLocalFilePaths;
|
|
80161
|
+
return await findFilesWithFallback(primaryLocalFilePaths, settablePaths.alternativeRoots, (alt) => rootRelativeGlob(alt.relativeDirPath, fileName), this.outputRoot);
|
|
79956
80162
|
})();
|
|
79957
80163
|
if (forDeletion) return buildDeletionRulesFromPaths(filePaths);
|
|
79958
80164
|
return (await Promise.all(filePaths.map(async (filePath) => {
|
|
@@ -81498,6 +81704,7 @@ async function generateRulesCore(params) {
|
|
|
81498
81704
|
deriveSubprojectPathFromGlobs: config.getDeriveSubprojectPathFromGlobs(),
|
|
81499
81705
|
skills,
|
|
81500
81706
|
featureOptions: config.getFeatureOptions(toolTarget, "rules"),
|
|
81707
|
+
delete: config.getDelete(),
|
|
81501
81708
|
dryRun: config.isPreviewMode(),
|
|
81502
81709
|
logger
|
|
81503
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-
|
|
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-
|
|
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": "
|
|
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",
|
|
@@ -53,46 +53,46 @@
|
|
|
53
53
|
"@octokit/request-error": "7.1.2",
|
|
54
54
|
"@octokit/rest": "22.0.1",
|
|
55
55
|
"@toon-format/toon": "4.1.1",
|
|
56
|
-
"@valibot/to-json-schema": "1.
|
|
56
|
+
"@valibot/to-json-schema": "1.8.0",
|
|
57
57
|
"chokidar": "5.0.0",
|
|
58
58
|
"commander": "15.0.0",
|
|
59
59
|
"effect": "3.22.2",
|
|
60
60
|
"es-toolkit": "1.52.0",
|
|
61
|
-
"fastmcp": "4.20.
|
|
61
|
+
"fastmcp": "4.20.14",
|
|
62
62
|
"globby": "16.2.4",
|
|
63
63
|
"gray-matter": "4.0.3",
|
|
64
|
-
"js-yaml": "5.4.
|
|
64
|
+
"js-yaml": "5.4.2",
|
|
65
65
|
"jsonc-parser": "3.3.1",
|
|
66
66
|
"minisearch": "7.2.0",
|
|
67
67
|
"smol-toml": "1.8.0",
|
|
68
68
|
"sury": "10.0.4",
|
|
69
|
-
"zod": "4.6.
|
|
69
|
+
"zod": "4.6.5"
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
|
-
"@anthropic-ai/claude-agent-sdk": "0.3.
|
|
73
|
-
"@openrouter/sdk": "1.
|
|
72
|
+
"@anthropic-ai/claude-agent-sdk": "0.3.278",
|
|
73
|
+
"@openrouter/sdk": "1.3.8",
|
|
74
74
|
"@secretlint/secretlint-rule-preset-recommend": "13.0.5",
|
|
75
75
|
"@tsconfig/node24": "24.0.5",
|
|
76
|
-
"@types/node": "26.
|
|
76
|
+
"@types/node": "26.6.2",
|
|
77
77
|
"@typescript/native-preview": "7.0.0-dev.20260707.2",
|
|
78
|
-
"@vitest/coverage-v8": "5.0.
|
|
79
|
-
"cspell": "10.3.
|
|
80
|
-
"knip": "6.
|
|
78
|
+
"@vitest/coverage-v8": "5.0.1",
|
|
79
|
+
"cspell": "10.3.3",
|
|
80
|
+
"knip": "6.37.0",
|
|
81
81
|
"lint-staged": "17.5.1",
|
|
82
|
-
"oxfmt": "0.
|
|
83
|
-
"oxlint": "1.
|
|
82
|
+
"oxfmt": "0.68.0",
|
|
83
|
+
"oxlint": "1.83.0",
|
|
84
84
|
"repomix": "1.18.0",
|
|
85
|
-
"resend": "6.
|
|
85
|
+
"resend": "6.28.1",
|
|
86
86
|
"secretlint": "13.0.5",
|
|
87
87
|
"simple-git": "3.36.0",
|
|
88
88
|
"simple-git-hooks": "2.14.0",
|
|
89
89
|
"sort-package-json": "4.0.0",
|
|
90
90
|
"tsdown": "0.23.0",
|
|
91
|
-
"tsx": "4.23.
|
|
91
|
+
"tsx": "4.23.15",
|
|
92
92
|
"typescript": "7.0.2",
|
|
93
93
|
"vite": "8.3.0",
|
|
94
94
|
"vitepress": "1.6.4",
|
|
95
|
-
"vitest": "5.0.
|
|
95
|
+
"vitest": "5.0.1"
|
|
96
96
|
},
|
|
97
97
|
"engines": {
|
|
98
98
|
"node": ">=22.0.0",
|