rulesync 11.0.0 → 12.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.
@@ -264,6 +264,7 @@ const subagentsProcessorToolTargetTuple = [
264
264
  "kiro-ide",
265
265
  "opencode",
266
266
  "qwencode",
267
+ "reasonix",
267
268
  "roo",
268
269
  "rovodev",
269
270
  "takt",
@@ -5092,6 +5093,9 @@ const REASONIX_SETTINGS_FILE_NAME = "settings.json";
5092
5093
  const REASONIX_COMMANDS_DIR_PATH = join(REASONIX_DIR, "commands");
5093
5094
  const REASONIX_RULE_FILE_NAME = "REASONIX.md";
5094
5095
  const REASONIX_SKILLS_DIR_PATH = join(REASONIX_DIR, "skills");
5096
+ const REASONIX_SUBAGENTS_DIR_PATH = REASONIX_SKILLS_DIR_PATH;
5097
+ const REASONIX_SUBAGENT_INVOCATION = "manual";
5098
+ const REASONIX_SUBAGENT_RUN_AS = "subagent";
5095
5099
  //#endregion
5096
5100
  //#region src/features/commands/reasonix-command.ts
5097
5101
  /**
@@ -17495,6 +17499,14 @@ const CodexBasePermissionProfileSchema = z.enum(CODEX_BASE_PERMISSION_PROFILES);
17495
17499
  * | `guardian_subagent`), or a table for the richer reviewer config.
17496
17500
  * Defaults to `auto_review` when neither the override nor the existing
17497
17501
  * config sets it.
17502
+ * - `git_write_rules` — whether the managed profile's `:workspace_roots` table
17503
+ * emits the default `.git` carve-outs (`".git/**" = "write"` with
17504
+ * `".git/config" = "read"` kept read-only as a security guard). Codex's
17505
+ * `:workspace` baseline makes `.git` read-only, which denies basic git
17506
+ * workflows (commit/stage writes to `.git/index`, `.git/objects`, refs,
17507
+ * logs), so the carve-outs are emitted by default. Defaults to `true`; only
17508
+ * an explicit `false` suppresses them. Like `base_permission_profile` it is
17509
+ * consumed by the profile builder, not written as a top-level config key.
17498
17510
  *
17499
17511
  * Two surfaces are deliberately NOT authorable here so the override can never
17500
17512
  * clobber a feature-owned key: `mcp_servers.*` per-MCP gating is owned by the
@@ -17520,7 +17532,8 @@ const CodexcliPermissionsOverrideSchema = z.looseObject({
17520
17532
  /** @deprecated Superseded by `base_permission_profile` (permission profiles). */
17521
17533
  sandbox_workspace_write: z.optional(z.looseObject({})),
17522
17534
  apps: z.optional(z.looseObject({})),
17523
- approvals_reviewer: z.optional(z.union([CodexApprovalsReviewerSchema, z.looseObject({})]))
17535
+ approvals_reviewer: z.optional(z.union([CodexApprovalsReviewerSchema, z.looseObject({})])),
17536
+ git_write_rules: z.optional(z.boolean())
17524
17537
  });
17525
17538
  /**
17526
17539
  * Permissions configuration.
@@ -19489,11 +19502,16 @@ var ClinePermissions = class ClinePermissions extends ToolPermissions {
19489
19502
  const RULESYNC_PROFILE_NAME = "rulesync";
19490
19503
  const CODEX_WORKSPACE_ROOTS_KEY = ":workspace_roots";
19491
19504
  const CODEX_WORKSPACE_BASELINE = ":workspace";
19505
+ const CODEX_READ_ONLY_BASELINE = ":read-only";
19492
19506
  const CODEX_EXTENDABLE_BASELINES = new Set(CODEX_BASE_PERMISSION_PROFILES);
19493
19507
  const CODEX_DEFAULT_APPROVAL_POLICY = "on-request";
19494
19508
  const CODEX_DEFAULT_APPROVALS_REVIEWER = "auto_review";
19495
19509
  const CODEX_GLOB_SCAN_MAX_DEPTH = 8;
19496
19510
  const CODEX_MINIMAL_KEY = ":minimal";
19511
+ const CODEX_GIT_WRITE_RULES = {
19512
+ ".git/**": "write",
19513
+ ".git/config": "read"
19514
+ };
19497
19515
  const GLOBAL_WILDCARD_DOMAIN = "*";
19498
19516
  var CodexcliPermissions = class CodexcliPermissions extends ToolPermissions {
19499
19517
  static getSettablePaths(_options = {}) {
@@ -19660,6 +19678,11 @@ function convertRulesyncToCodexProfile({ config, logger }) {
19660
19678
  }
19661
19679
  logger?.warn(`Codex CLI permissions support only read/edit/write/webfetch categories. Skipping: ${toolName}`);
19662
19680
  }
19681
+ applyDefaultGitWriteRules({
19682
+ config,
19683
+ filesystem,
19684
+ workspaceRootFilesystem
19685
+ });
19663
19686
  if (Object.keys(workspaceRootFilesystem).length > 0) {
19664
19687
  if (typeof filesystem[CODEX_WORKSPACE_ROOTS_KEY] === "string") logger?.warn(`"${CODEX_WORKSPACE_ROOTS_KEY}" is set as a direct filesystem access rule in the permissions, but it will be overwritten by workspace-root rules. Consider removing the direct "${CODEX_WORKSPACE_ROOTS_KEY}" entry.`);
19665
19688
  if (Object.keys(workspaceRootFilesystem).some((pattern) => pattern.includes("**"))) filesystem.glob_scan_max_depth = CODEX_GLOB_SCAN_MAX_DEPTH;
@@ -19677,6 +19700,12 @@ function convertRulesyncToCodexProfile({ config, logger }) {
19677
19700
  ...network ? { network } : {}
19678
19701
  };
19679
19702
  }
19703
+ function applyDefaultGitWriteRules({ config, filesystem, workspaceRootFilesystem }) {
19704
+ if (config.codexcli?.git_write_rules === false) return;
19705
+ if (config.codexcli?.base_permission_profile === CODEX_READ_ONLY_BASELINE) return;
19706
+ if (typeof filesystem[CODEX_WORKSPACE_ROOTS_KEY] === "string") return;
19707
+ for (const [pattern, access] of Object.entries(CODEX_GIT_WRITE_RULES)) workspaceRootFilesystem[pattern] ??= access;
19708
+ }
19680
19709
  function convertCodexProfileToRulesync({ profile, domainsHadUnknown }) {
19681
19710
  const permission = {};
19682
19711
  if (profile?.filesystem) {
@@ -19688,7 +19717,10 @@ function convertCodexProfileToRulesync({ profile, domainsHadUnknown }) {
19688
19717
  addRulesyncFilesystemRule(permission, pattern, access);
19689
19718
  continue;
19690
19719
  }
19691
- if (isCodexFilesystemRuleTable(access)) for (const [nestedPattern, nestedAccess] of Object.entries(access)) addRulesyncFilesystemRule(permission, nestedPattern, nestedAccess);
19720
+ if (isCodexFilesystemRuleTable(access)) for (const [nestedPattern, nestedAccess] of Object.entries(access)) {
19721
+ if (pattern === CODEX_WORKSPACE_ROOTS_KEY && CODEX_GIT_WRITE_RULES[nestedPattern] === nestedAccess) continue;
19722
+ addRulesyncFilesystemRule(permission, nestedPattern, nestedAccess);
19723
+ }
19692
19724
  }
19693
19725
  }
19694
19726
  if (profile?.network && profile.network.enabled !== false) {
@@ -19730,9 +19762,28 @@ function toCodexProfile(value) {
19730
19762
  }
19731
19763
  function warnAboutPreservedProfileState({ existingProfile, newProfile, existingDomainsHadUnknown, logger }) {
19732
19764
  if (existingProfile !== void 0 && existingProfile.extends !== newProfile.extends) logger?.warn(`Existing "extends" value "${existingProfile.extends ?? "(none)"}" will be replaced by Rulesync-managed "${newProfile.extends ?? "(none)"}".`);
19765
+ warnAboutPreservedNetworkState({
19766
+ existingProfile,
19767
+ newProfile,
19768
+ logger
19769
+ });
19770
+ if (existingDomainsHadUnknown) logger?.warn(`Existing "network.domains" contained unrecognized values. These entries were skipped and will not be imported.`);
19771
+ }
19772
+ function networkHasAllowDomain(network) {
19773
+ return Object.values(network?.domains ?? {}).some((action) => action === "allow");
19774
+ }
19775
+ function warnAboutNetworkEnabledState({ existingProfile, newProfile, logger }) {
19776
+ if (existingProfile?.network?.enabled !== void 0 && newProfile.network?.enabled === void 0 && !networkHasAllowDomain(existingProfile.network)) logger?.warn(`Preserving existing "network.enabled" from config. Review this value manually as it may enable network access beyond the Rulesync-managed domain rules.`);
19777
+ if (existingProfile?.network?.enabled === false && newProfile.network?.enabled === true) logger?.warn(`Existing "network.enabled = false" will be replaced by Rulesync-managed "enabled = true" because the canonical model contains an allow domain.`);
19778
+ }
19779
+ function warnAboutPreservedNetworkState({ existingProfile, newProfile, logger }) {
19733
19780
  if (existingProfile?.network?.unix_sockets !== void 0) logger?.warn(`Preserving existing "network.unix_sockets" from config. Review these entries manually as they may grant broad system access.`);
19781
+ warnAboutNetworkEnabledState({
19782
+ existingProfile,
19783
+ newProfile,
19784
+ logger
19785
+ });
19734
19786
  if (existingProfile?.network?.mode !== void 0) logger?.warn(`Preserving existing "network.mode" from config. Review this value manually as it may grant broader network access than the Rulesync-managed domain rules.`);
19735
- if (existingDomainsHadUnknown) logger?.warn(`Existing "network.domains" contained unrecognized values. These entries were skipped and will not be imported.`);
19736
19787
  }
19737
19788
  const MANAGED_PROFILE_KEYS = /* @__PURE__ */ new Set([
19738
19789
  "description",
@@ -19774,6 +19825,7 @@ function preserveUnmanagedProfileKeys({ rawExistingProfile, profile, logger }) {
19774
19825
  function mergeWithExistingProfile({ newProfile, existingProfile }) {
19775
19826
  if (!existingProfile) return newProfile;
19776
19827
  const mergedNetwork = { ...newProfile.network };
19828
+ if (existingProfile.network?.enabled !== void 0 && mergedNetwork.enabled === void 0 && !networkHasAllowDomain(existingProfile.network)) mergedNetwork.enabled = existingProfile.network.enabled;
19777
19829
  if (existingProfile.network?.mode !== void 0 && mergedNetwork.mode === void 0) mergedNetwork.mode = existingProfile.network.mode;
19778
19830
  if (existingProfile.network?.unix_sockets !== void 0 && mergedNetwork.unix_sockets === void 0) mergedNetwork.unix_sockets = existingProfile.network.unix_sockets;
19779
19831
  const hasNetwork = Object.keys(mergedNetwork).length > 0;
@@ -19825,7 +19877,7 @@ function computeCodexcliOverridePatch({ existing, override, logger }) {
19825
19877
  const patch = {};
19826
19878
  const allowed = new Set(CODEXCLI_OVERRIDE_KEYS);
19827
19879
  for (const [key, value] of Object.entries(override ?? {})) {
19828
- if (key === "base_permission_profile") continue;
19880
+ if (key === "base_permission_profile" || key === "git_write_rules") continue;
19829
19881
  if (!allowed.has(key)) {
19830
19882
  logger?.warn(`Codex CLI permission override key "${key}" is not managed and was skipped. "permissions"/"default_permissions" are owned by the canonical permission model and "mcp_servers" gating by the MCP feature.`);
19831
19883
  continue;
@@ -28465,6 +28517,24 @@ var ReasonixSkill = class ReasonixSkill extends ToolSkill {
28465
28517
  global: loaded.global
28466
28518
  });
28467
28519
  }
28520
+ /**
28521
+ * Whether the skill directory belongs to the skills feature.
28522
+ *
28523
+ * `.reasonix/skills/` is shared with the subagents feature: a directory whose
28524
+ * SKILL.md declares `runAs: subagent` is a subagent profile, not a regular
28525
+ * skill, so it must be neither imported as a skill nor deleted as an orphan
28526
+ * skill. Directories without a readable/parsable SKILL.md keep the default
28527
+ * skills-feature ownership, matching the previous behavior for such dirs.
28528
+ */
28529
+ static async isDirOwned({ outputRoot, relativeDirPath, dirName }) {
28530
+ const skillFilePath = join(outputRoot, relativeDirPath, dirName, SKILL_FILE_NAME);
28531
+ try {
28532
+ const { frontmatter } = parseFrontmatter(await readFileContent(skillFilePath), skillFilePath);
28533
+ return frontmatter["runAs"] !== REASONIX_SUBAGENT_RUN_AS;
28534
+ } catch {
28535
+ return true;
28536
+ }
28537
+ }
28468
28538
  static forDeletion({ outputRoot = process.cwd(), relativeDirPath, dirName, global = false }) {
28469
28539
  return new ReasonixSkill({
28470
28540
  outputRoot,
@@ -29763,6 +29833,11 @@ var SkillsProcessor = class extends DirFeatureProcessor {
29763
29833
  for (const dirPath of dirPaths) {
29764
29834
  const dirName = basename(dirPath);
29765
29835
  if (seenDirNames.has(dirName)) continue;
29836
+ if (factory.class.isDirOwned && !await factory.class.isDirOwned({
29837
+ outputRoot: this.outputRoot,
29838
+ relativeDirPath: root,
29839
+ dirName
29840
+ })) continue;
29766
29841
  seenDirNames.add(dirName);
29767
29842
  loadEntries.push({
29768
29843
  root,
@@ -29789,6 +29864,11 @@ var SkillsProcessor = class extends DirFeatureProcessor {
29789
29864
  const dirPaths = await findFilesByGlobs(join(skillsDirPath, "*"), { type: "dir" });
29790
29865
  for (const dirPath of dirPaths) {
29791
29866
  const dirName = basename(dirPath);
29867
+ if (factory.class.isDirOwned && !await factory.class.isDirOwned({
29868
+ outputRoot: this.outputRoot,
29869
+ relativeDirPath: root,
29870
+ dirName
29871
+ })) continue;
29792
29872
  const toolSkill = factory.class.forDeletion({
29793
29873
  outputRoot: this.outputRoot,
29794
29874
  relativeDirPath: root,
@@ -33088,6 +33168,188 @@ var OpenCodeSubagent = class OpenCodeSubagent extends OpenCodeStyleSubagent {
33088
33168
  }
33089
33169
  };
33090
33170
  //#endregion
33171
+ //#region src/features/subagents/reasonix-subagent.ts
33172
+ const ReasonixSubagentFrontmatterSchema = z.looseObject({
33173
+ name: z.string(),
33174
+ description: z.optional(z.string()),
33175
+ invocation: z.optional(z.string()),
33176
+ runAs: z.optional(z.string()),
33177
+ model: z.optional(z.string()),
33178
+ effort: z.optional(z.string()),
33179
+ "allowed-tools": z.optional(z.array(z.string())),
33180
+ color: z.optional(z.string())
33181
+ });
33182
+ /**
33183
+ * Represents a DeepSeek-Reasonix subagent profile.
33184
+ *
33185
+ * Reasonix discovers subagents as directory-layout Skills (`<name>/SKILL.md`)
33186
+ * under `.reasonix/skills/` (project) and `~/.reasonix/skills/` (global); the
33187
+ * global scope is served by the processor supplying the home directory as
33188
+ * outputRoot, so the relative directory path is identical for both scopes. A
33189
+ * subagent is a Skill whose frontmatter declares `invocation: manual` and
33190
+ * `runAs: subagent`.
33191
+ *
33192
+ * @see https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/docs/SUBAGENT_PROFILES.md
33193
+ */
33194
+ var ReasonixSubagent = class ReasonixSubagent extends ToolSubagent {
33195
+ frontmatter;
33196
+ body;
33197
+ constructor({ frontmatter, body, fileContent, ...rest }) {
33198
+ if (rest.validate !== false) {
33199
+ const result = ReasonixSubagentFrontmatterSchema.safeParse(frontmatter);
33200
+ if (!result.success) throw new Error(`Invalid frontmatter in ${join(rest.relativeDirPath, rest.relativeFilePath)}: ${formatError(result.error)}`);
33201
+ }
33202
+ super({
33203
+ ...rest,
33204
+ fileContent: fileContent ?? stringifyFrontmatter(body, frontmatter)
33205
+ });
33206
+ this.frontmatter = frontmatter;
33207
+ this.body = body;
33208
+ }
33209
+ static getSettablePaths({ global: _global = false } = {}) {
33210
+ return { relativeDirPath: REASONIX_SUBAGENTS_DIR_PATH };
33211
+ }
33212
+ getFrontmatter() {
33213
+ return this.frontmatter;
33214
+ }
33215
+ getBody() {
33216
+ return this.body;
33217
+ }
33218
+ toRulesyncSubagent() {
33219
+ const { name, description, invocation: _invocation, runAs: _runAs, ...restFields } = this.frontmatter;
33220
+ const reasonixSection = { ...restFields };
33221
+ const rulesyncFrontmatter = {
33222
+ targets: ["*"],
33223
+ name,
33224
+ description,
33225
+ ...Object.keys(reasonixSection).length > 0 && { reasonix: reasonixSection }
33226
+ };
33227
+ return new RulesyncSubagent({
33228
+ outputRoot: this.getOutputRoot(),
33229
+ frontmatter: rulesyncFrontmatter,
33230
+ body: this.body,
33231
+ relativeDirPath: RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH,
33232
+ relativeFilePath: `${this.getSubagentName()}.md`,
33233
+ validate: true
33234
+ });
33235
+ }
33236
+ /**
33237
+ * Derive the subagent name from this instance's relative file path.
33238
+ *
33239
+ * The tool-side layout is `<name>/SKILL.md`, so the name is the parent
33240
+ * directory of the file. If the path is unexpectedly flat (e.g. a legacy
33241
+ * `<name>.md`), fall back to the basename without extension.
33242
+ */
33243
+ getSubagentName() {
33244
+ const relativeFilePath = this.getRelativeFilePath();
33245
+ const dir = dirname(relativeFilePath);
33246
+ if (dir && dir !== ".") return basename(dir);
33247
+ return basename(relativeFilePath, extname(relativeFilePath));
33248
+ }
33249
+ static fromRulesyncSubagent({ outputRoot = process.cwd(), rulesyncSubagent, validate = true, global = false }) {
33250
+ const rulesyncFrontmatter = rulesyncSubagent.getFrontmatter();
33251
+ const reasonixSection = this.filterToolSpecificSection(rulesyncFrontmatter.reasonix ?? {}, ["name", "description"]);
33252
+ const rawReasonixFrontmatter = {
33253
+ name: rulesyncFrontmatter.name,
33254
+ description: rulesyncFrontmatter.description,
33255
+ ...reasonixSection,
33256
+ invocation: REASONIX_SUBAGENT_INVOCATION,
33257
+ runAs: REASONIX_SUBAGENT_RUN_AS
33258
+ };
33259
+ const result = ReasonixSubagentFrontmatterSchema.safeParse(rawReasonixFrontmatter);
33260
+ if (!result.success) throw new Error(`Invalid reasonix subagent frontmatter in ${rulesyncSubagent.getRelativeFilePath()}: ${formatError(result.error)}`);
33261
+ const reasonixFrontmatter = result.data;
33262
+ const body = rulesyncSubagent.getBody();
33263
+ const fileContent = stringifyFrontmatter(body, reasonixFrontmatter);
33264
+ const paths = this.getSettablePaths({ global });
33265
+ const subagentName = basename(rulesyncSubagent.getRelativeFilePath(), extname(rulesyncSubagent.getRelativeFilePath()));
33266
+ return new ReasonixSubagent({
33267
+ outputRoot,
33268
+ frontmatter: reasonixFrontmatter,
33269
+ body,
33270
+ relativeDirPath: paths.relativeDirPath,
33271
+ relativeFilePath: join(subagentName, SKILL_FILE_NAME),
33272
+ fileContent,
33273
+ validate,
33274
+ global
33275
+ });
33276
+ }
33277
+ validate() {
33278
+ if (!this.frontmatter) return {
33279
+ success: true,
33280
+ error: null
33281
+ };
33282
+ const result = ReasonixSubagentFrontmatterSchema.safeParse(this.frontmatter);
33283
+ if (result.success) return {
33284
+ success: true,
33285
+ error: null
33286
+ };
33287
+ else return {
33288
+ success: false,
33289
+ error: /* @__PURE__ */ new Error(`Invalid frontmatter in ${join(this.relativeDirPath, this.relativeFilePath)}: ${formatError(result.error)}`)
33290
+ };
33291
+ }
33292
+ static isTargetedByRulesyncSubagent(rulesyncSubagent) {
33293
+ return this.isTargetedByRulesyncSubagentDefault({
33294
+ rulesyncSubagent,
33295
+ toolTarget: "reasonix"
33296
+ });
33297
+ }
33298
+ static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
33299
+ const paths = this.getSettablePaths({ global });
33300
+ const filePath = join(outputRoot, paths.relativeDirPath, relativeFilePath);
33301
+ const fileContent = await readFileContent(filePath);
33302
+ const { frontmatter, body: content } = parseFrontmatter(fileContent, filePath);
33303
+ const result = ReasonixSubagentFrontmatterSchema.safeParse(frontmatter);
33304
+ if (!result.success) throw new Error(`Invalid frontmatter in ${filePath}: ${formatError(result.error)}`);
33305
+ return new ReasonixSubagent({
33306
+ outputRoot,
33307
+ relativeDirPath: paths.relativeDirPath,
33308
+ relativeFilePath,
33309
+ frontmatter: result.data,
33310
+ body: content.trim(),
33311
+ fileContent,
33312
+ validate,
33313
+ global
33314
+ });
33315
+ }
33316
+ /**
33317
+ * Whether the SKILL.md at the given path is a subagent profile.
33318
+ *
33319
+ * `.reasonix/skills/` is shared with the skills feature: a regular skill and
33320
+ * a subagent profile differ only by their frontmatter markers. Only files
33321
+ * carrying `runAs: subagent` belong to this feature, so regular skills are
33322
+ * neither imported as subagents nor deleted as orphans by the subagents
33323
+ * feature. `runAs` alone is checked (not `invocation`) because it is the
33324
+ * marker that switches the execution mode; generation always emits both.
33325
+ * Unreadable or unparsable files are treated as not owned, erring on the
33326
+ * side of leaving foreign files untouched.
33327
+ */
33328
+ static async isFileOwned({ outputRoot, relativeDirPath, relativeFilePath }) {
33329
+ const filePath = join(outputRoot, relativeDirPath, relativeFilePath);
33330
+ try {
33331
+ const { frontmatter } = parseFrontmatter(await readFileContent(filePath), filePath);
33332
+ return frontmatter["runAs"] === REASONIX_SUBAGENT_RUN_AS;
33333
+ } catch {
33334
+ return false;
33335
+ }
33336
+ }
33337
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
33338
+ return new ReasonixSubagent({
33339
+ outputRoot,
33340
+ relativeDirPath,
33341
+ relativeFilePath,
33342
+ frontmatter: {
33343
+ name: "",
33344
+ description: ""
33345
+ },
33346
+ body: "",
33347
+ fileContent: "",
33348
+ validate: false
33349
+ });
33350
+ }
33351
+ };
33352
+ //#endregion
33091
33353
  //#region src/features/subagents/roo-subagent.ts
33092
33354
  /**
33093
33355
  * Default tool groups assigned to a generated mode when the rulesync source
@@ -33722,6 +33984,14 @@ const toolSubagentFactories = /* @__PURE__ */ new Map([
33722
33984
  filePattern: "*.md"
33723
33985
  }
33724
33986
  }],
33987
+ ["reasonix", {
33988
+ class: ReasonixSubagent,
33989
+ meta: {
33990
+ supportsSimulated: false,
33991
+ supportsGlobal: true,
33992
+ filePattern: join("*", "SKILL.md")
33993
+ }
33994
+ }],
33725
33995
  ["roo", {
33726
33996
  class: RooSubagent,
33727
33997
  meta: {
@@ -33874,8 +34144,17 @@ var SubagentsProcessor = class extends FeatureProcessor {
33874
34144
  const baseDir = join(this.outputRoot, dirPath);
33875
34145
  const subagentFilePaths = await findFilesByGlobs(join(baseDir, factory.meta.filePattern));
33876
34146
  const toRelativeFilePath = (path) => relative(baseDir, path);
34147
+ let ownedFilePaths = subagentFilePaths;
34148
+ if (factory.class.isFileOwned) {
34149
+ const ownership = await Promise.all(subagentFilePaths.map((path) => factory.class.isFileOwned({
34150
+ outputRoot: this.outputRoot,
34151
+ relativeDirPath: dirPath,
34152
+ relativeFilePath: toRelativeFilePath(path)
34153
+ })));
34154
+ ownedFilePaths = subagentFilePaths.filter((_, index) => ownership[index]);
34155
+ }
33877
34156
  if (forDeletion) {
33878
- toolSubagents.push(...subagentFilePaths.map((path) => factory.class.forDeletion({
34157
+ toolSubagents.push(...ownedFilePaths.map((path) => factory.class.forDeletion({
33879
34158
  outputRoot: this.outputRoot,
33880
34159
  relativeDirPath: dirPath,
33881
34160
  relativeFilePath: toRelativeFilePath(path),
@@ -33883,7 +34162,7 @@ var SubagentsProcessor = class extends FeatureProcessor {
33883
34162
  })).filter((subagent) => subagent.isDeletable()));
33884
34163
  continue;
33885
34164
  }
33886
- const loaded = await Promise.all(subagentFilePaths.map((path) => factory.class.fromFile({
34165
+ const loaded = await Promise.all(ownedFilePaths.map((path) => factory.class.fromFile({
33887
34166
  outputRoot: this.outputRoot,
33888
34167
  relativeDirPath: dirPath,
33889
34168
  relativeFilePath: toRelativeFilePath(path),
@@ -39702,11 +39981,51 @@ const GENERATION_STEP_GRAPH = [
39702
39981
  }
39703
39982
  ];
39704
39983
  /**
39984
+ * Warn when a rulesync skill and a rulesync subagent share a name for a tool
39985
+ * that emits both features into the same directory (e.g. Reasonix, where both
39986
+ * write `<name>/SKILL.md` under `.reasonix/skills/`). The colliding outputs
39987
+ * target the same on-disk file, so whichever generation step runs later
39988
+ * silently overwrites the other's file.
39989
+ */
39990
+ async function warnSkillSubagentNameCollisions(params) {
39991
+ const { config, logger } = params;
39992
+ const global = config.getGlobal();
39993
+ for (const toolTarget of config.getTargets()) {
39994
+ const features = config.getFeatures(toolTarget);
39995
+ if (!features.includes("skills") || !features.includes("subagents")) continue;
39996
+ if (!SubagentsProcessor.getToolTargets({ global }).includes(toolTarget) || !SkillsProcessor.getToolTargets({ global }).includes(toolTarget)) continue;
39997
+ const subagentFactory = SubagentsProcessor.getFactory(toolTarget);
39998
+ const skillFactory = SkillsProcessor.getFactory(toolTarget);
39999
+ if (!subagentFactory || !skillFactory) continue;
40000
+ const subagentsDirPath = subagentFactory.class.getSettablePaths({ global }).relativeDirPath;
40001
+ if (subagentsDirPath !== skillFactory.class.getSettablePaths({ global }).relativeDirPath) continue;
40002
+ const subagentsProcessor = new SubagentsProcessor({
40003
+ inputRoot: config.getInputRoot(),
40004
+ toolTarget,
40005
+ global,
40006
+ logger
40007
+ });
40008
+ const subagentNames = new Set((await subagentsProcessor.loadRulesyncFiles()).filter((file) => file instanceof RulesyncSubagent).filter((file) => subagentFactory.class.isTargetedByRulesyncSubagent(file)).map((file) => basename(file.getRelativeFilePath(), extname(file.getRelativeFilePath()))));
40009
+ if (subagentNames.size === 0) continue;
40010
+ const skillNames = (await new SkillsProcessor({
40011
+ inputRoot: config.getInputRoot(),
40012
+ toolTarget,
40013
+ global,
40014
+ logger
40015
+ }).loadRulesyncDirs()).filter((dir) => dir instanceof RulesyncSkill).filter((skill) => skillFactory.class.isTargetedByRulesyncSkill(skill)).map((skill) => skill.getDirName());
40016
+ for (const name of skillNames) if (subagentNames.has(name)) logger.warn(`Skill "${name}" and subagent "${name}" both target '${toolTarget}' and write the same path '${join(subagentsDirPath, name)}'; the later generation step overwrites the other's output. Rename one of them or narrow their targets.`);
40017
+ }
40018
+ }
40019
+ /**
39705
40020
  * Generate configuration files for AI tools.
39706
40021
  * @throws Error if generation fails
39707
40022
  */
39708
40023
  async function generate(params) {
39709
40024
  const { config, logger } = params;
40025
+ await warnSkillSubagentNameCollisions({
40026
+ config,
40027
+ logger
40028
+ });
39710
40029
  let skillsResult;
39711
40030
  const runners = {
39712
40031
  ignore: () => generateIgnoreCore({
@@ -40420,4 +40739,4 @@ async function importPermissionsCore(params) {
40420
40739
  //#endregion
40421
40740
  export { removeFile as $, CLAUDECODE_SKILLS_DIR_PATH as A, RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH as At, ErrorCodes as B, RulesyncHooks as C, RULESYNC_PERMISSIONS_FILE_NAME as Ct, CLAUDECODE_LOCAL_RULE_FILE_NAME as D, RULESYNC_RELATIVE_DIR_PATH as Dt, CLAUDECODE_DIR as E, RULESYNC_PERMISSIONS_SCHEMA_URL as Et, ConfigResolver as F, fileExists as G, createTempDirectory as H, findControlCharacter as I, getHomeDirectory as J, findFilesByGlobs as K, ConsoleLogger as L, RulesyncCommandFrontmatterSchema as M, formatError as Mt, stringifyFrontmatter as N, ALL_FEATURES as Nt, CLAUDECODE_MEMORIES_DIR_NAME as O, RULESYNC_RULES_RELATIVE_DIR_PATH as Ot, loadYaml as P, ALL_FEATURES_WITH_WILDCARD as Pt, removeDirectory as Q, JsonLogger as R, HooksProcessor as S, RULESYNC_OVERVIEW_FILE_NAME as St, CODEXCLI_DIR as T, RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH as Tt, directoryExists as U, checkPathTraversal as V, ensureDir as W, listDirectoryFiles as X, isSymlink as Y, readFileContent as Z, RulesyncPermissions as _, RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH as _t, convertFromTool as a, ToolTargetSchema as at, IgnoreProcessor as b, RULESYNC_MCP_RELATIVE_FILE_PATH as bt, RulesyncRuleFrontmatterSchema as c, RULESYNC_AIIGNORE_RELATIVE_FILE_PATH as ct, RulesyncSubagentFrontmatterSchema as d, RULESYNC_CONFIG_SCHEMA_URL as dt, removeTempDirectory as et, SkillsProcessor as f, RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH as ft, SKILL_FILE_NAME as g, RULESYNC_IGNORE_RELATIVE_FILE_PATH as gt, RulesyncSkillFrontmatterSchema as h, RULESYNC_HOOKS_RELATIVE_FILE_PATH as ht, getProcessorRegistryEntry as i, ALL_TOOL_TARGETS_WITH_WILDCARD as it, RulesyncCommand as j, RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH as jt, CLAUDECODE_SETTINGS_LOCAL_FILE_NAME as k, RULESYNC_SKILLS_RELATIVE_DIR_PATH as kt, SubagentsProcessor as l, RULESYNC_COMMANDS_RELATIVE_DIR_PATH as lt, RulesyncSkill as m, RULESYNC_HOOKS_JSONC_FILE_NAME as mt, checkRulesyncDirExists as n, writeFileContent as nt, RulesProcessor as o, MAX_FILE_SIZE as ot, getLocalSkillDirNames as p, RULESYNC_HOOKS_FILE_NAME as pt, getFileSize as q, generate as r, ALL_TOOL_TARGETS as rt, RulesyncRule as s, RULESYNC_AIIGNORE_FILE_NAME as st, importFromTool as t, toPosixPath as tt, RulesyncSubagent as u, RULESYNC_CONFIG_RELATIVE_FILE_PATH as ut, McpProcessor as v, RULESYNC_MCP_FILE_NAME as vt, CommandsProcessor as w, RULESYNC_PERMISSIONS_JSONC_FILE_NAME as wt, RulesyncIgnore as x, RULESYNC_MCP_SCHEMA_URL as xt, RulesyncMcp as y, RULESYNC_MCP_JSONC_FILE_NAME as yt, CLIError as z };
40422
40741
 
40423
- //# sourceMappingURL=import-AKytnIKl.js.map
40742
+ //# sourceMappingURL=import-hM4jDZn1.js.map