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.
- package/README.md +1 -1
- package/dist/cli/index.cjs +2 -2
- package/dist/cli/index.js +2 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/{import-ESr9cSFw.cjs → import-C0N0Lo_F.cjs} +338 -19
- package/dist/{import-AKytnIKl.js → import-hM4jDZn1.js} +326 -7
- package/dist/import-hM4jDZn1.js.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/import-AKytnIKl.js.map +0 -1
|
@@ -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))
|
|
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(...
|
|
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(
|
|
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-
|
|
40742
|
+
//# sourceMappingURL=import-hM4jDZn1.js.map
|