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
|
@@ -114,25 +114,25 @@ function formatError(error) {
|
|
|
114
114
|
}
|
|
115
115
|
//#endregion
|
|
116
116
|
//#region src/constants/rulesync-paths.ts
|
|
117
|
-
const { join: join$
|
|
117
|
+
const { join: join$252 } = node_path.posix;
|
|
118
118
|
const RULESYNC_CONFIG_RELATIVE_FILE_PATH = "rulesync.jsonc";
|
|
119
119
|
const RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH = "rulesync.local.jsonc";
|
|
120
120
|
const RULESYNC_RELATIVE_DIR_PATH = ".rulesync";
|
|
121
|
-
const RULESYNC_RULES_RELATIVE_DIR_PATH = join$
|
|
122
|
-
const RULESYNC_COMMANDS_RELATIVE_DIR_PATH = join$
|
|
123
|
-
const RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH = join$
|
|
124
|
-
const RULESYNC_MCP_RELATIVE_FILE_PATH = join$
|
|
125
|
-
const RULESYNC_HOOKS_RELATIVE_FILE_PATH = join$
|
|
126
|
-
const RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH = join$
|
|
127
|
-
const RULESYNC_MCP_JSONC_RELATIVE_FILE_PATH = join$
|
|
128
|
-
const RULESYNC_HOOKS_JSONC_RELATIVE_FILE_PATH = join$
|
|
129
|
-
const RULESYNC_PERMISSIONS_JSONC_RELATIVE_FILE_PATH = join$
|
|
121
|
+
const RULESYNC_RULES_RELATIVE_DIR_PATH = join$252(RULESYNC_RELATIVE_DIR_PATH, "rules");
|
|
122
|
+
const RULESYNC_COMMANDS_RELATIVE_DIR_PATH = join$252(RULESYNC_RELATIVE_DIR_PATH, "commands");
|
|
123
|
+
const RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH = join$252(RULESYNC_RELATIVE_DIR_PATH, "subagents");
|
|
124
|
+
const RULESYNC_MCP_RELATIVE_FILE_PATH = join$252(RULESYNC_RELATIVE_DIR_PATH, "mcp.json");
|
|
125
|
+
const RULESYNC_HOOKS_RELATIVE_FILE_PATH = join$252(RULESYNC_RELATIVE_DIR_PATH, "hooks.json");
|
|
126
|
+
const RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH = join$252(RULESYNC_RELATIVE_DIR_PATH, "permissions.json");
|
|
127
|
+
const RULESYNC_MCP_JSONC_RELATIVE_FILE_PATH = join$252(RULESYNC_RELATIVE_DIR_PATH, "mcp.jsonc");
|
|
128
|
+
const RULESYNC_HOOKS_JSONC_RELATIVE_FILE_PATH = join$252(RULESYNC_RELATIVE_DIR_PATH, "hooks.jsonc");
|
|
129
|
+
const RULESYNC_PERMISSIONS_JSONC_RELATIVE_FILE_PATH = join$252(RULESYNC_RELATIVE_DIR_PATH, "permissions.jsonc");
|
|
130
130
|
const RULESYNC_AIIGNORE_FILE_NAME = ".aiignore";
|
|
131
|
-
const RULESYNC_AIIGNORE_RELATIVE_FILE_PATH = join$
|
|
131
|
+
const RULESYNC_AIIGNORE_RELATIVE_FILE_PATH = join$252(RULESYNC_RELATIVE_DIR_PATH, ".aiignore");
|
|
132
132
|
const RULESYNC_IGNORE_RELATIVE_FILE_PATH = ".rulesyncignore";
|
|
133
133
|
const RULESYNC_OVERVIEW_FILE_NAME = "overview.md";
|
|
134
|
-
const RULESYNC_SKILLS_RELATIVE_DIR_PATH = join$
|
|
135
|
-
const RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH = join$
|
|
134
|
+
const RULESYNC_SKILLS_RELATIVE_DIR_PATH = join$252(RULESYNC_RELATIVE_DIR_PATH, "skills");
|
|
135
|
+
const RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH = join$252(RULESYNC_SKILLS_RELATIVE_DIR_PATH, ".curated");
|
|
136
136
|
const RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH = "rulesync.lock";
|
|
137
137
|
const RULESYNC_MCP_FILE_NAME = "mcp.json";
|
|
138
138
|
const RULESYNC_HOOKS_FILE_NAME = "hooks.json";
|
|
@@ -289,6 +289,7 @@ const subagentsProcessorToolTargetTuple = [
|
|
|
289
289
|
"kiro-ide",
|
|
290
290
|
"opencode",
|
|
291
291
|
"qwencode",
|
|
292
|
+
"reasonix",
|
|
292
293
|
"roo",
|
|
293
294
|
"rovodev",
|
|
294
295
|
"takt",
|
|
@@ -5118,6 +5119,9 @@ const REASONIX_SETTINGS_FILE_NAME = "settings.json";
|
|
|
5118
5119
|
const REASONIX_COMMANDS_DIR_PATH = (0, node_path.join)(REASONIX_DIR, "commands");
|
|
5119
5120
|
const REASONIX_RULE_FILE_NAME = "REASONIX.md";
|
|
5120
5121
|
const REASONIX_SKILLS_DIR_PATH = (0, node_path.join)(REASONIX_DIR, "skills");
|
|
5122
|
+
const REASONIX_SUBAGENTS_DIR_PATH = REASONIX_SKILLS_DIR_PATH;
|
|
5123
|
+
const REASONIX_SUBAGENT_INVOCATION = "manual";
|
|
5124
|
+
const REASONIX_SUBAGENT_RUN_AS = "subagent";
|
|
5121
5125
|
//#endregion
|
|
5122
5126
|
//#region src/features/commands/reasonix-command.ts
|
|
5123
5127
|
/**
|
|
@@ -17521,6 +17525,14 @@ const CodexBasePermissionProfileSchema = zod_mini.z.enum(CODEX_BASE_PERMISSION_P
|
|
|
17521
17525
|
* | `guardian_subagent`), or a table for the richer reviewer config.
|
|
17522
17526
|
* Defaults to `auto_review` when neither the override nor the existing
|
|
17523
17527
|
* config sets it.
|
|
17528
|
+
* - `git_write_rules` — whether the managed profile's `:workspace_roots` table
|
|
17529
|
+
* emits the default `.git` carve-outs (`".git/**" = "write"` with
|
|
17530
|
+
* `".git/config" = "read"` kept read-only as a security guard). Codex's
|
|
17531
|
+
* `:workspace` baseline makes `.git` read-only, which denies basic git
|
|
17532
|
+
* workflows (commit/stage writes to `.git/index`, `.git/objects`, refs,
|
|
17533
|
+
* logs), so the carve-outs are emitted by default. Defaults to `true`; only
|
|
17534
|
+
* an explicit `false` suppresses them. Like `base_permission_profile` it is
|
|
17535
|
+
* consumed by the profile builder, not written as a top-level config key.
|
|
17524
17536
|
*
|
|
17525
17537
|
* Two surfaces are deliberately NOT authorable here so the override can never
|
|
17526
17538
|
* clobber a feature-owned key: `mcp_servers.*` per-MCP gating is owned by the
|
|
@@ -17546,7 +17558,8 @@ const CodexcliPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
17546
17558
|
/** @deprecated Superseded by `base_permission_profile` (permission profiles). */
|
|
17547
17559
|
sandbox_workspace_write: zod_mini.z.optional(zod_mini.z.looseObject({})),
|
|
17548
17560
|
apps: zod_mini.z.optional(zod_mini.z.looseObject({})),
|
|
17549
|
-
approvals_reviewer: zod_mini.z.optional(zod_mini.z.union([CodexApprovalsReviewerSchema, zod_mini.z.looseObject({})]))
|
|
17561
|
+
approvals_reviewer: zod_mini.z.optional(zod_mini.z.union([CodexApprovalsReviewerSchema, zod_mini.z.looseObject({})])),
|
|
17562
|
+
git_write_rules: zod_mini.z.optional(zod_mini.z.boolean())
|
|
17550
17563
|
});
|
|
17551
17564
|
/**
|
|
17552
17565
|
* Permissions configuration.
|
|
@@ -19515,11 +19528,16 @@ var ClinePermissions = class ClinePermissions extends ToolPermissions {
|
|
|
19515
19528
|
const RULESYNC_PROFILE_NAME = "rulesync";
|
|
19516
19529
|
const CODEX_WORKSPACE_ROOTS_KEY = ":workspace_roots";
|
|
19517
19530
|
const CODEX_WORKSPACE_BASELINE = ":workspace";
|
|
19531
|
+
const CODEX_READ_ONLY_BASELINE = ":read-only";
|
|
19518
19532
|
const CODEX_EXTENDABLE_BASELINES = new Set(CODEX_BASE_PERMISSION_PROFILES);
|
|
19519
19533
|
const CODEX_DEFAULT_APPROVAL_POLICY = "on-request";
|
|
19520
19534
|
const CODEX_DEFAULT_APPROVALS_REVIEWER = "auto_review";
|
|
19521
19535
|
const CODEX_GLOB_SCAN_MAX_DEPTH = 8;
|
|
19522
19536
|
const CODEX_MINIMAL_KEY = ":minimal";
|
|
19537
|
+
const CODEX_GIT_WRITE_RULES = {
|
|
19538
|
+
".git/**": "write",
|
|
19539
|
+
".git/config": "read"
|
|
19540
|
+
};
|
|
19523
19541
|
const GLOBAL_WILDCARD_DOMAIN = "*";
|
|
19524
19542
|
var CodexcliPermissions = class CodexcliPermissions extends ToolPermissions {
|
|
19525
19543
|
static getSettablePaths(_options = {}) {
|
|
@@ -19686,6 +19704,11 @@ function convertRulesyncToCodexProfile({ config, logger }) {
|
|
|
19686
19704
|
}
|
|
19687
19705
|
logger?.warn(`Codex CLI permissions support only read/edit/write/webfetch categories. Skipping: ${toolName}`);
|
|
19688
19706
|
}
|
|
19707
|
+
applyDefaultGitWriteRules({
|
|
19708
|
+
config,
|
|
19709
|
+
filesystem,
|
|
19710
|
+
workspaceRootFilesystem
|
|
19711
|
+
});
|
|
19689
19712
|
if (Object.keys(workspaceRootFilesystem).length > 0) {
|
|
19690
19713
|
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.`);
|
|
19691
19714
|
if (Object.keys(workspaceRootFilesystem).some((pattern) => pattern.includes("**"))) filesystem.glob_scan_max_depth = CODEX_GLOB_SCAN_MAX_DEPTH;
|
|
@@ -19703,6 +19726,12 @@ function convertRulesyncToCodexProfile({ config, logger }) {
|
|
|
19703
19726
|
...network ? { network } : {}
|
|
19704
19727
|
};
|
|
19705
19728
|
}
|
|
19729
|
+
function applyDefaultGitWriteRules({ config, filesystem, workspaceRootFilesystem }) {
|
|
19730
|
+
if (config.codexcli?.git_write_rules === false) return;
|
|
19731
|
+
if (config.codexcli?.base_permission_profile === CODEX_READ_ONLY_BASELINE) return;
|
|
19732
|
+
if (typeof filesystem[CODEX_WORKSPACE_ROOTS_KEY] === "string") return;
|
|
19733
|
+
for (const [pattern, access] of Object.entries(CODEX_GIT_WRITE_RULES)) workspaceRootFilesystem[pattern] ??= access;
|
|
19734
|
+
}
|
|
19706
19735
|
function convertCodexProfileToRulesync({ profile, domainsHadUnknown }) {
|
|
19707
19736
|
const permission = {};
|
|
19708
19737
|
if (profile?.filesystem) {
|
|
@@ -19714,7 +19743,10 @@ function convertCodexProfileToRulesync({ profile, domainsHadUnknown }) {
|
|
|
19714
19743
|
addRulesyncFilesystemRule(permission, pattern, access);
|
|
19715
19744
|
continue;
|
|
19716
19745
|
}
|
|
19717
|
-
if (isCodexFilesystemRuleTable(access)) for (const [nestedPattern, nestedAccess] of Object.entries(access))
|
|
19746
|
+
if (isCodexFilesystemRuleTable(access)) for (const [nestedPattern, nestedAccess] of Object.entries(access)) {
|
|
19747
|
+
if (pattern === CODEX_WORKSPACE_ROOTS_KEY && CODEX_GIT_WRITE_RULES[nestedPattern] === nestedAccess) continue;
|
|
19748
|
+
addRulesyncFilesystemRule(permission, nestedPattern, nestedAccess);
|
|
19749
|
+
}
|
|
19718
19750
|
}
|
|
19719
19751
|
}
|
|
19720
19752
|
if (profile?.network && profile.network.enabled !== false) {
|
|
@@ -19756,9 +19788,28 @@ function toCodexProfile(value) {
|
|
|
19756
19788
|
}
|
|
19757
19789
|
function warnAboutPreservedProfileState({ existingProfile, newProfile, existingDomainsHadUnknown, logger }) {
|
|
19758
19790
|
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)"}".`);
|
|
19791
|
+
warnAboutPreservedNetworkState({
|
|
19792
|
+
existingProfile,
|
|
19793
|
+
newProfile,
|
|
19794
|
+
logger
|
|
19795
|
+
});
|
|
19796
|
+
if (existingDomainsHadUnknown) logger?.warn(`Existing "network.domains" contained unrecognized values. These entries were skipped and will not be imported.`);
|
|
19797
|
+
}
|
|
19798
|
+
function networkHasAllowDomain(network) {
|
|
19799
|
+
return Object.values(network?.domains ?? {}).some((action) => action === "allow");
|
|
19800
|
+
}
|
|
19801
|
+
function warnAboutNetworkEnabledState({ existingProfile, newProfile, logger }) {
|
|
19802
|
+
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.`);
|
|
19803
|
+
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.`);
|
|
19804
|
+
}
|
|
19805
|
+
function warnAboutPreservedNetworkState({ existingProfile, newProfile, logger }) {
|
|
19759
19806
|
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.`);
|
|
19807
|
+
warnAboutNetworkEnabledState({
|
|
19808
|
+
existingProfile,
|
|
19809
|
+
newProfile,
|
|
19810
|
+
logger
|
|
19811
|
+
});
|
|
19760
19812
|
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.`);
|
|
19761
|
-
if (existingDomainsHadUnknown) logger?.warn(`Existing "network.domains" contained unrecognized values. These entries were skipped and will not be imported.`);
|
|
19762
19813
|
}
|
|
19763
19814
|
const MANAGED_PROFILE_KEYS = /* @__PURE__ */ new Set([
|
|
19764
19815
|
"description",
|
|
@@ -19800,6 +19851,7 @@ function preserveUnmanagedProfileKeys({ rawExistingProfile, profile, logger }) {
|
|
|
19800
19851
|
function mergeWithExistingProfile({ newProfile, existingProfile }) {
|
|
19801
19852
|
if (!existingProfile) return newProfile;
|
|
19802
19853
|
const mergedNetwork = { ...newProfile.network };
|
|
19854
|
+
if (existingProfile.network?.enabled !== void 0 && mergedNetwork.enabled === void 0 && !networkHasAllowDomain(existingProfile.network)) mergedNetwork.enabled = existingProfile.network.enabled;
|
|
19803
19855
|
if (existingProfile.network?.mode !== void 0 && mergedNetwork.mode === void 0) mergedNetwork.mode = existingProfile.network.mode;
|
|
19804
19856
|
if (existingProfile.network?.unix_sockets !== void 0 && mergedNetwork.unix_sockets === void 0) mergedNetwork.unix_sockets = existingProfile.network.unix_sockets;
|
|
19805
19857
|
const hasNetwork = Object.keys(mergedNetwork).length > 0;
|
|
@@ -19851,7 +19903,7 @@ function computeCodexcliOverridePatch({ existing, override, logger }) {
|
|
|
19851
19903
|
const patch = {};
|
|
19852
19904
|
const allowed = new Set(CODEXCLI_OVERRIDE_KEYS);
|
|
19853
19905
|
for (const [key, value] of Object.entries(override ?? {})) {
|
|
19854
|
-
if (key === "base_permission_profile") continue;
|
|
19906
|
+
if (key === "base_permission_profile" || key === "git_write_rules") continue;
|
|
19855
19907
|
if (!allowed.has(key)) {
|
|
19856
19908
|
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.`);
|
|
19857
19909
|
continue;
|
|
@@ -28491,6 +28543,24 @@ var ReasonixSkill = class ReasonixSkill extends ToolSkill {
|
|
|
28491
28543
|
global: loaded.global
|
|
28492
28544
|
});
|
|
28493
28545
|
}
|
|
28546
|
+
/**
|
|
28547
|
+
* Whether the skill directory belongs to the skills feature.
|
|
28548
|
+
*
|
|
28549
|
+
* `.reasonix/skills/` is shared with the subagents feature: a directory whose
|
|
28550
|
+
* SKILL.md declares `runAs: subagent` is a subagent profile, not a regular
|
|
28551
|
+
* skill, so it must be neither imported as a skill nor deleted as an orphan
|
|
28552
|
+
* skill. Directories without a readable/parsable SKILL.md keep the default
|
|
28553
|
+
* skills-feature ownership, matching the previous behavior for such dirs.
|
|
28554
|
+
*/
|
|
28555
|
+
static async isDirOwned({ outputRoot, relativeDirPath, dirName }) {
|
|
28556
|
+
const skillFilePath = (0, node_path.join)(outputRoot, relativeDirPath, dirName, SKILL_FILE_NAME);
|
|
28557
|
+
try {
|
|
28558
|
+
const { frontmatter } = parseFrontmatter(await readFileContent(skillFilePath), skillFilePath);
|
|
28559
|
+
return frontmatter["runAs"] !== REASONIX_SUBAGENT_RUN_AS;
|
|
28560
|
+
} catch {
|
|
28561
|
+
return true;
|
|
28562
|
+
}
|
|
28563
|
+
}
|
|
28494
28564
|
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, dirName, global = false }) {
|
|
28495
28565
|
return new ReasonixSkill({
|
|
28496
28566
|
outputRoot,
|
|
@@ -29789,6 +29859,11 @@ var SkillsProcessor = class extends DirFeatureProcessor {
|
|
|
29789
29859
|
for (const dirPath of dirPaths) {
|
|
29790
29860
|
const dirName = (0, node_path.basename)(dirPath);
|
|
29791
29861
|
if (seenDirNames.has(dirName)) continue;
|
|
29862
|
+
if (factory.class.isDirOwned && !await factory.class.isDirOwned({
|
|
29863
|
+
outputRoot: this.outputRoot,
|
|
29864
|
+
relativeDirPath: root,
|
|
29865
|
+
dirName
|
|
29866
|
+
})) continue;
|
|
29792
29867
|
seenDirNames.add(dirName);
|
|
29793
29868
|
loadEntries.push({
|
|
29794
29869
|
root,
|
|
@@ -29815,6 +29890,11 @@ var SkillsProcessor = class extends DirFeatureProcessor {
|
|
|
29815
29890
|
const dirPaths = await findFilesByGlobs((0, node_path.join)(skillsDirPath, "*"), { type: "dir" });
|
|
29816
29891
|
for (const dirPath of dirPaths) {
|
|
29817
29892
|
const dirName = (0, node_path.basename)(dirPath);
|
|
29893
|
+
if (factory.class.isDirOwned && !await factory.class.isDirOwned({
|
|
29894
|
+
outputRoot: this.outputRoot,
|
|
29895
|
+
relativeDirPath: root,
|
|
29896
|
+
dirName
|
|
29897
|
+
})) continue;
|
|
29818
29898
|
const toolSkill = factory.class.forDeletion({
|
|
29819
29899
|
outputRoot: this.outputRoot,
|
|
29820
29900
|
relativeDirPath: root,
|
|
@@ -33114,6 +33194,188 @@ var OpenCodeSubagent = class OpenCodeSubagent extends OpenCodeStyleSubagent {
|
|
|
33114
33194
|
}
|
|
33115
33195
|
};
|
|
33116
33196
|
//#endregion
|
|
33197
|
+
//#region src/features/subagents/reasonix-subagent.ts
|
|
33198
|
+
const ReasonixSubagentFrontmatterSchema = zod_mini.z.looseObject({
|
|
33199
|
+
name: zod_mini.z.string(),
|
|
33200
|
+
description: zod_mini.z.optional(zod_mini.z.string()),
|
|
33201
|
+
invocation: zod_mini.z.optional(zod_mini.z.string()),
|
|
33202
|
+
runAs: zod_mini.z.optional(zod_mini.z.string()),
|
|
33203
|
+
model: zod_mini.z.optional(zod_mini.z.string()),
|
|
33204
|
+
effort: zod_mini.z.optional(zod_mini.z.string()),
|
|
33205
|
+
"allowed-tools": zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
33206
|
+
color: zod_mini.z.optional(zod_mini.z.string())
|
|
33207
|
+
});
|
|
33208
|
+
/**
|
|
33209
|
+
* Represents a DeepSeek-Reasonix subagent profile.
|
|
33210
|
+
*
|
|
33211
|
+
* Reasonix discovers subagents as directory-layout Skills (`<name>/SKILL.md`)
|
|
33212
|
+
* under `.reasonix/skills/` (project) and `~/.reasonix/skills/` (global); the
|
|
33213
|
+
* global scope is served by the processor supplying the home directory as
|
|
33214
|
+
* outputRoot, so the relative directory path is identical for both scopes. A
|
|
33215
|
+
* subagent is a Skill whose frontmatter declares `invocation: manual` and
|
|
33216
|
+
* `runAs: subagent`.
|
|
33217
|
+
*
|
|
33218
|
+
* @see https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/docs/SUBAGENT_PROFILES.md
|
|
33219
|
+
*/
|
|
33220
|
+
var ReasonixSubagent = class ReasonixSubagent extends ToolSubagent {
|
|
33221
|
+
frontmatter;
|
|
33222
|
+
body;
|
|
33223
|
+
constructor({ frontmatter, body, fileContent, ...rest }) {
|
|
33224
|
+
if (rest.validate !== false) {
|
|
33225
|
+
const result = ReasonixSubagentFrontmatterSchema.safeParse(frontmatter);
|
|
33226
|
+
if (!result.success) throw new Error(`Invalid frontmatter in ${(0, node_path.join)(rest.relativeDirPath, rest.relativeFilePath)}: ${formatError(result.error)}`);
|
|
33227
|
+
}
|
|
33228
|
+
super({
|
|
33229
|
+
...rest,
|
|
33230
|
+
fileContent: fileContent ?? stringifyFrontmatter(body, frontmatter)
|
|
33231
|
+
});
|
|
33232
|
+
this.frontmatter = frontmatter;
|
|
33233
|
+
this.body = body;
|
|
33234
|
+
}
|
|
33235
|
+
static getSettablePaths({ global: _global = false } = {}) {
|
|
33236
|
+
return { relativeDirPath: REASONIX_SUBAGENTS_DIR_PATH };
|
|
33237
|
+
}
|
|
33238
|
+
getFrontmatter() {
|
|
33239
|
+
return this.frontmatter;
|
|
33240
|
+
}
|
|
33241
|
+
getBody() {
|
|
33242
|
+
return this.body;
|
|
33243
|
+
}
|
|
33244
|
+
toRulesyncSubagent() {
|
|
33245
|
+
const { name, description, invocation: _invocation, runAs: _runAs, ...restFields } = this.frontmatter;
|
|
33246
|
+
const reasonixSection = { ...restFields };
|
|
33247
|
+
const rulesyncFrontmatter = {
|
|
33248
|
+
targets: ["*"],
|
|
33249
|
+
name,
|
|
33250
|
+
description,
|
|
33251
|
+
...Object.keys(reasonixSection).length > 0 && { reasonix: reasonixSection }
|
|
33252
|
+
};
|
|
33253
|
+
return new RulesyncSubagent({
|
|
33254
|
+
outputRoot: this.getOutputRoot(),
|
|
33255
|
+
frontmatter: rulesyncFrontmatter,
|
|
33256
|
+
body: this.body,
|
|
33257
|
+
relativeDirPath: RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH,
|
|
33258
|
+
relativeFilePath: `${this.getSubagentName()}.md`,
|
|
33259
|
+
validate: true
|
|
33260
|
+
});
|
|
33261
|
+
}
|
|
33262
|
+
/**
|
|
33263
|
+
* Derive the subagent name from this instance's relative file path.
|
|
33264
|
+
*
|
|
33265
|
+
* The tool-side layout is `<name>/SKILL.md`, so the name is the parent
|
|
33266
|
+
* directory of the file. If the path is unexpectedly flat (e.g. a legacy
|
|
33267
|
+
* `<name>.md`), fall back to the basename without extension.
|
|
33268
|
+
*/
|
|
33269
|
+
getSubagentName() {
|
|
33270
|
+
const relativeFilePath = this.getRelativeFilePath();
|
|
33271
|
+
const dir = (0, node_path.dirname)(relativeFilePath);
|
|
33272
|
+
if (dir && dir !== ".") return (0, node_path.basename)(dir);
|
|
33273
|
+
return (0, node_path.basename)(relativeFilePath, (0, node_path.extname)(relativeFilePath));
|
|
33274
|
+
}
|
|
33275
|
+
static fromRulesyncSubagent({ outputRoot = process.cwd(), rulesyncSubagent, validate = true, global = false }) {
|
|
33276
|
+
const rulesyncFrontmatter = rulesyncSubagent.getFrontmatter();
|
|
33277
|
+
const reasonixSection = this.filterToolSpecificSection(rulesyncFrontmatter.reasonix ?? {}, ["name", "description"]);
|
|
33278
|
+
const rawReasonixFrontmatter = {
|
|
33279
|
+
name: rulesyncFrontmatter.name,
|
|
33280
|
+
description: rulesyncFrontmatter.description,
|
|
33281
|
+
...reasonixSection,
|
|
33282
|
+
invocation: REASONIX_SUBAGENT_INVOCATION,
|
|
33283
|
+
runAs: REASONIX_SUBAGENT_RUN_AS
|
|
33284
|
+
};
|
|
33285
|
+
const result = ReasonixSubagentFrontmatterSchema.safeParse(rawReasonixFrontmatter);
|
|
33286
|
+
if (!result.success) throw new Error(`Invalid reasonix subagent frontmatter in ${rulesyncSubagent.getRelativeFilePath()}: ${formatError(result.error)}`);
|
|
33287
|
+
const reasonixFrontmatter = result.data;
|
|
33288
|
+
const body = rulesyncSubagent.getBody();
|
|
33289
|
+
const fileContent = stringifyFrontmatter(body, reasonixFrontmatter);
|
|
33290
|
+
const paths = this.getSettablePaths({ global });
|
|
33291
|
+
const subagentName = (0, node_path.basename)(rulesyncSubagent.getRelativeFilePath(), (0, node_path.extname)(rulesyncSubagent.getRelativeFilePath()));
|
|
33292
|
+
return new ReasonixSubagent({
|
|
33293
|
+
outputRoot,
|
|
33294
|
+
frontmatter: reasonixFrontmatter,
|
|
33295
|
+
body,
|
|
33296
|
+
relativeDirPath: paths.relativeDirPath,
|
|
33297
|
+
relativeFilePath: (0, node_path.join)(subagentName, SKILL_FILE_NAME),
|
|
33298
|
+
fileContent,
|
|
33299
|
+
validate,
|
|
33300
|
+
global
|
|
33301
|
+
});
|
|
33302
|
+
}
|
|
33303
|
+
validate() {
|
|
33304
|
+
if (!this.frontmatter) return {
|
|
33305
|
+
success: true,
|
|
33306
|
+
error: null
|
|
33307
|
+
};
|
|
33308
|
+
const result = ReasonixSubagentFrontmatterSchema.safeParse(this.frontmatter);
|
|
33309
|
+
if (result.success) return {
|
|
33310
|
+
success: true,
|
|
33311
|
+
error: null
|
|
33312
|
+
};
|
|
33313
|
+
else return {
|
|
33314
|
+
success: false,
|
|
33315
|
+
error: /* @__PURE__ */ new Error(`Invalid frontmatter in ${(0, node_path.join)(this.relativeDirPath, this.relativeFilePath)}: ${formatError(result.error)}`)
|
|
33316
|
+
};
|
|
33317
|
+
}
|
|
33318
|
+
static isTargetedByRulesyncSubagent(rulesyncSubagent) {
|
|
33319
|
+
return this.isTargetedByRulesyncSubagentDefault({
|
|
33320
|
+
rulesyncSubagent,
|
|
33321
|
+
toolTarget: "reasonix"
|
|
33322
|
+
});
|
|
33323
|
+
}
|
|
33324
|
+
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
|
|
33325
|
+
const paths = this.getSettablePaths({ global });
|
|
33326
|
+
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, relativeFilePath);
|
|
33327
|
+
const fileContent = await readFileContent(filePath);
|
|
33328
|
+
const { frontmatter, body: content } = parseFrontmatter(fileContent, filePath);
|
|
33329
|
+
const result = ReasonixSubagentFrontmatterSchema.safeParse(frontmatter);
|
|
33330
|
+
if (!result.success) throw new Error(`Invalid frontmatter in ${filePath}: ${formatError(result.error)}`);
|
|
33331
|
+
return new ReasonixSubagent({
|
|
33332
|
+
outputRoot,
|
|
33333
|
+
relativeDirPath: paths.relativeDirPath,
|
|
33334
|
+
relativeFilePath,
|
|
33335
|
+
frontmatter: result.data,
|
|
33336
|
+
body: content.trim(),
|
|
33337
|
+
fileContent,
|
|
33338
|
+
validate,
|
|
33339
|
+
global
|
|
33340
|
+
});
|
|
33341
|
+
}
|
|
33342
|
+
/**
|
|
33343
|
+
* Whether the SKILL.md at the given path is a subagent profile.
|
|
33344
|
+
*
|
|
33345
|
+
* `.reasonix/skills/` is shared with the skills feature: a regular skill and
|
|
33346
|
+
* a subagent profile differ only by their frontmatter markers. Only files
|
|
33347
|
+
* carrying `runAs: subagent` belong to this feature, so regular skills are
|
|
33348
|
+
* neither imported as subagents nor deleted as orphans by the subagents
|
|
33349
|
+
* feature. `runAs` alone is checked (not `invocation`) because it is the
|
|
33350
|
+
* marker that switches the execution mode; generation always emits both.
|
|
33351
|
+
* Unreadable or unparsable files are treated as not owned, erring on the
|
|
33352
|
+
* side of leaving foreign files untouched.
|
|
33353
|
+
*/
|
|
33354
|
+
static async isFileOwned({ outputRoot, relativeDirPath, relativeFilePath }) {
|
|
33355
|
+
const filePath = (0, node_path.join)(outputRoot, relativeDirPath, relativeFilePath);
|
|
33356
|
+
try {
|
|
33357
|
+
const { frontmatter } = parseFrontmatter(await readFileContent(filePath), filePath);
|
|
33358
|
+
return frontmatter["runAs"] === REASONIX_SUBAGENT_RUN_AS;
|
|
33359
|
+
} catch {
|
|
33360
|
+
return false;
|
|
33361
|
+
}
|
|
33362
|
+
}
|
|
33363
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
33364
|
+
return new ReasonixSubagent({
|
|
33365
|
+
outputRoot,
|
|
33366
|
+
relativeDirPath,
|
|
33367
|
+
relativeFilePath,
|
|
33368
|
+
frontmatter: {
|
|
33369
|
+
name: "",
|
|
33370
|
+
description: ""
|
|
33371
|
+
},
|
|
33372
|
+
body: "",
|
|
33373
|
+
fileContent: "",
|
|
33374
|
+
validate: false
|
|
33375
|
+
});
|
|
33376
|
+
}
|
|
33377
|
+
};
|
|
33378
|
+
//#endregion
|
|
33117
33379
|
//#region src/features/subagents/roo-subagent.ts
|
|
33118
33380
|
/**
|
|
33119
33381
|
* Default tool groups assigned to a generated mode when the rulesync source
|
|
@@ -33748,6 +34010,14 @@ const toolSubagentFactories = /* @__PURE__ */ new Map([
|
|
|
33748
34010
|
filePattern: "*.md"
|
|
33749
34011
|
}
|
|
33750
34012
|
}],
|
|
34013
|
+
["reasonix", {
|
|
34014
|
+
class: ReasonixSubagent,
|
|
34015
|
+
meta: {
|
|
34016
|
+
supportsSimulated: false,
|
|
34017
|
+
supportsGlobal: true,
|
|
34018
|
+
filePattern: (0, node_path.join)("*", "SKILL.md")
|
|
34019
|
+
}
|
|
34020
|
+
}],
|
|
33751
34021
|
["roo", {
|
|
33752
34022
|
class: RooSubagent,
|
|
33753
34023
|
meta: {
|
|
@@ -33900,8 +34170,17 @@ var SubagentsProcessor = class extends FeatureProcessor {
|
|
|
33900
34170
|
const baseDir = (0, node_path.join)(this.outputRoot, dirPath);
|
|
33901
34171
|
const subagentFilePaths = await findFilesByGlobs((0, node_path.join)(baseDir, factory.meta.filePattern));
|
|
33902
34172
|
const toRelativeFilePath = (path) => (0, node_path.relative)(baseDir, path);
|
|
34173
|
+
let ownedFilePaths = subagentFilePaths;
|
|
34174
|
+
if (factory.class.isFileOwned) {
|
|
34175
|
+
const ownership = await Promise.all(subagentFilePaths.map((path) => factory.class.isFileOwned({
|
|
34176
|
+
outputRoot: this.outputRoot,
|
|
34177
|
+
relativeDirPath: dirPath,
|
|
34178
|
+
relativeFilePath: toRelativeFilePath(path)
|
|
34179
|
+
})));
|
|
34180
|
+
ownedFilePaths = subagentFilePaths.filter((_, index) => ownership[index]);
|
|
34181
|
+
}
|
|
33903
34182
|
if (forDeletion) {
|
|
33904
|
-
toolSubagents.push(...
|
|
34183
|
+
toolSubagents.push(...ownedFilePaths.map((path) => factory.class.forDeletion({
|
|
33905
34184
|
outputRoot: this.outputRoot,
|
|
33906
34185
|
relativeDirPath: dirPath,
|
|
33907
34186
|
relativeFilePath: toRelativeFilePath(path),
|
|
@@ -33909,7 +34188,7 @@ var SubagentsProcessor = class extends FeatureProcessor {
|
|
|
33909
34188
|
})).filter((subagent) => subagent.isDeletable()));
|
|
33910
34189
|
continue;
|
|
33911
34190
|
}
|
|
33912
|
-
const loaded = await Promise.all(
|
|
34191
|
+
const loaded = await Promise.all(ownedFilePaths.map((path) => factory.class.fromFile({
|
|
33913
34192
|
outputRoot: this.outputRoot,
|
|
33914
34193
|
relativeDirPath: dirPath,
|
|
33915
34194
|
relativeFilePath: toRelativeFilePath(path),
|
|
@@ -39728,11 +40007,51 @@ const GENERATION_STEP_GRAPH = [
|
|
|
39728
40007
|
}
|
|
39729
40008
|
];
|
|
39730
40009
|
/**
|
|
40010
|
+
* Warn when a rulesync skill and a rulesync subagent share a name for a tool
|
|
40011
|
+
* that emits both features into the same directory (e.g. Reasonix, where both
|
|
40012
|
+
* write `<name>/SKILL.md` under `.reasonix/skills/`). The colliding outputs
|
|
40013
|
+
* target the same on-disk file, so whichever generation step runs later
|
|
40014
|
+
* silently overwrites the other's file.
|
|
40015
|
+
*/
|
|
40016
|
+
async function warnSkillSubagentNameCollisions(params) {
|
|
40017
|
+
const { config, logger } = params;
|
|
40018
|
+
const global = config.getGlobal();
|
|
40019
|
+
for (const toolTarget of config.getTargets()) {
|
|
40020
|
+
const features = config.getFeatures(toolTarget);
|
|
40021
|
+
if (!features.includes("skills") || !features.includes("subagents")) continue;
|
|
40022
|
+
if (!SubagentsProcessor.getToolTargets({ global }).includes(toolTarget) || !SkillsProcessor.getToolTargets({ global }).includes(toolTarget)) continue;
|
|
40023
|
+
const subagentFactory = SubagentsProcessor.getFactory(toolTarget);
|
|
40024
|
+
const skillFactory = SkillsProcessor.getFactory(toolTarget);
|
|
40025
|
+
if (!subagentFactory || !skillFactory) continue;
|
|
40026
|
+
const subagentsDirPath = subagentFactory.class.getSettablePaths({ global }).relativeDirPath;
|
|
40027
|
+
if (subagentsDirPath !== skillFactory.class.getSettablePaths({ global }).relativeDirPath) continue;
|
|
40028
|
+
const subagentsProcessor = new SubagentsProcessor({
|
|
40029
|
+
inputRoot: config.getInputRoot(),
|
|
40030
|
+
toolTarget,
|
|
40031
|
+
global,
|
|
40032
|
+
logger
|
|
40033
|
+
});
|
|
40034
|
+
const subagentNames = new Set((await subagentsProcessor.loadRulesyncFiles()).filter((file) => file instanceof RulesyncSubagent).filter((file) => subagentFactory.class.isTargetedByRulesyncSubagent(file)).map((file) => (0, node_path.basename)(file.getRelativeFilePath(), (0, node_path.extname)(file.getRelativeFilePath()))));
|
|
40035
|
+
if (subagentNames.size === 0) continue;
|
|
40036
|
+
const skillNames = (await new SkillsProcessor({
|
|
40037
|
+
inputRoot: config.getInputRoot(),
|
|
40038
|
+
toolTarget,
|
|
40039
|
+
global,
|
|
40040
|
+
logger
|
|
40041
|
+
}).loadRulesyncDirs()).filter((dir) => dir instanceof RulesyncSkill).filter((skill) => skillFactory.class.isTargetedByRulesyncSkill(skill)).map((skill) => skill.getDirName());
|
|
40042
|
+
for (const name of skillNames) if (subagentNames.has(name)) logger.warn(`Skill "${name}" and subagent "${name}" both target '${toolTarget}' and write the same path '${(0, node_path.join)(subagentsDirPath, name)}'; the later generation step overwrites the other's output. Rename one of them or narrow their targets.`);
|
|
40043
|
+
}
|
|
40044
|
+
}
|
|
40045
|
+
/**
|
|
39731
40046
|
* Generate configuration files for AI tools.
|
|
39732
40047
|
* @throws Error if generation fails
|
|
39733
40048
|
*/
|
|
39734
40049
|
async function generate(params) {
|
|
39735
40050
|
const { config, logger } = params;
|
|
40051
|
+
await warnSkillSubagentNameCollisions({
|
|
40052
|
+
config,
|
|
40053
|
+
logger
|
|
40054
|
+
});
|
|
39736
40055
|
let skillsResult;
|
|
39737
40056
|
const runners = {
|
|
39738
40057
|
ignore: () => generateIgnoreCore({
|