rulesync 16.4.0 → 16.5.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 -0
- package/dist/cli/index.cjs +116 -68
- package/dist/cli/index.js +116 -68
- package/dist/cli/index.js.map +1 -1
- package/dist/{import-BwakMyyf.js → import-BlwypG9v.js} +1126 -286
- package/dist/import-BlwypG9v.js.map +1 -0
- package/dist/{import-C9wxpZey.cjs → import-D87qlRUj.cjs} +1141 -301
- package/dist/index.cjs +17 -15
- package/dist/index.d.cts +3 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -15
- package/dist/index.js.map +1 -1
- package/package.json +16 -16
- package/dist/import-BwakMyyf.js.map +0 -1
|
@@ -15,7 +15,7 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
15
|
}
|
|
16
16
|
return to;
|
|
17
17
|
};
|
|
18
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
18
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
|
|
19
19
|
value: mod,
|
|
20
20
|
enumerable: true
|
|
21
21
|
}) : target, mod));
|
|
@@ -134,27 +134,27 @@ const isFeatureValueEnabled = (value) => {
|
|
|
134
134
|
};
|
|
135
135
|
//#endregion
|
|
136
136
|
//#region src/constants/rulesync-paths.ts
|
|
137
|
-
const { join: join$
|
|
137
|
+
const { join: join$282 } = node_path.posix;
|
|
138
138
|
const RULESYNC_CONFIG_RELATIVE_FILE_PATH = "rulesync.jsonc";
|
|
139
139
|
const RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH = "rulesync.local.jsonc";
|
|
140
140
|
const RULESYNC_RELATIVE_DIR_PATH = ".rulesync";
|
|
141
|
-
const RULESYNC_RULES_RELATIVE_DIR_PATH = join$
|
|
142
|
-
const RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH = join$
|
|
143
|
-
const RULESYNC_COMMANDS_RELATIVE_DIR_PATH = join$
|
|
144
|
-
const RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH = join$
|
|
145
|
-
const RULESYNC_CHECKS_RELATIVE_DIR_PATH = join$
|
|
146
|
-
const RULESYNC_MCP_RELATIVE_FILE_PATH = join$
|
|
147
|
-
const RULESYNC_HOOKS_RELATIVE_FILE_PATH = join$
|
|
148
|
-
const RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH = join$
|
|
149
|
-
join$
|
|
150
|
-
const RULESYNC_HOOKS_LEGACY_RELATIVE_FILE_PATH = join$
|
|
151
|
-
const RULESYNC_PERMISSIONS_LEGACY_RELATIVE_FILE_PATH = join$
|
|
141
|
+
const RULESYNC_RULES_RELATIVE_DIR_PATH = join$282(RULESYNC_RELATIVE_DIR_PATH, "rules");
|
|
142
|
+
const RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH = join$282(RULESYNC_RULES_RELATIVE_DIR_PATH, ".curated");
|
|
143
|
+
const RULESYNC_COMMANDS_RELATIVE_DIR_PATH = join$282(RULESYNC_RELATIVE_DIR_PATH, "commands");
|
|
144
|
+
const RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH = join$282(RULESYNC_RELATIVE_DIR_PATH, "subagents");
|
|
145
|
+
const RULESYNC_CHECKS_RELATIVE_DIR_PATH = join$282(RULESYNC_RELATIVE_DIR_PATH, "checks");
|
|
146
|
+
const RULESYNC_MCP_RELATIVE_FILE_PATH = join$282(RULESYNC_RELATIVE_DIR_PATH, "mcp.jsonc");
|
|
147
|
+
const RULESYNC_HOOKS_RELATIVE_FILE_PATH = join$282(RULESYNC_RELATIVE_DIR_PATH, "hooks.jsonc");
|
|
148
|
+
const RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH = join$282(RULESYNC_RELATIVE_DIR_PATH, "permissions.jsonc");
|
|
149
|
+
join$282(RULESYNC_RELATIVE_DIR_PATH, "mcp.json");
|
|
150
|
+
const RULESYNC_HOOKS_LEGACY_RELATIVE_FILE_PATH = join$282(RULESYNC_RELATIVE_DIR_PATH, "hooks.json");
|
|
151
|
+
const RULESYNC_PERMISSIONS_LEGACY_RELATIVE_FILE_PATH = join$282(RULESYNC_RELATIVE_DIR_PATH, "permissions.json");
|
|
152
152
|
const RULESYNC_AIIGNORE_FILE_NAME = ".aiignore";
|
|
153
|
-
const RULESYNC_AIIGNORE_RELATIVE_FILE_PATH = join$
|
|
153
|
+
const RULESYNC_AIIGNORE_RELATIVE_FILE_PATH = join$282(RULESYNC_RELATIVE_DIR_PATH, ".aiignore");
|
|
154
154
|
const RULESYNC_IGNORE_RELATIVE_FILE_PATH = ".rulesyncignore";
|
|
155
155
|
const RULESYNC_OVERVIEW_FILE_NAME = "overview.md";
|
|
156
|
-
const RULESYNC_SKILLS_RELATIVE_DIR_PATH = join$
|
|
157
|
-
const RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH = join$
|
|
156
|
+
const RULESYNC_SKILLS_RELATIVE_DIR_PATH = join$282(RULESYNC_RELATIVE_DIR_PATH, "skills");
|
|
157
|
+
const RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH = join$282(RULESYNC_SKILLS_RELATIVE_DIR_PATH, ".curated");
|
|
158
158
|
const RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH = "rulesync.lock";
|
|
159
159
|
const RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH = "rulesync-npm.lock.json";
|
|
160
160
|
const RULESYNC_MCP_FILE_NAME = "mcp.jsonc";
|
|
@@ -166,7 +166,7 @@ const RULESYNC_PERMISSIONS_LEGACY_FILE_NAME = "permissions.json";
|
|
|
166
166
|
const RULESYNC_CONFIG_SCHEMA_URL = "https://github.com/dyoshikawa/rulesync/releases/latest/download/config-schema.json";
|
|
167
167
|
const RULESYNC_MCP_SCHEMA_URL = "https://github.com/dyoshikawa/rulesync/releases/latest/download/mcp-schema.json";
|
|
168
168
|
const RULESYNC_PERMISSIONS_SCHEMA_URL = "https://github.com/dyoshikawa/rulesync/releases/latest/download/permissions-schema.json";
|
|
169
|
-
const MAX_FILE_SIZE =
|
|
169
|
+
const MAX_FILE_SIZE = 10485760;
|
|
170
170
|
//#endregion
|
|
171
171
|
//#region src/types/tool-target-tuples.ts
|
|
172
172
|
const rulesProcessorToolTargetTuple = [
|
|
@@ -207,7 +207,8 @@ const rulesProcessorToolTargetTuple = [
|
|
|
207
207
|
"vibe",
|
|
208
208
|
"warp",
|
|
209
209
|
"devin",
|
|
210
|
-
"zed"
|
|
210
|
+
"zed",
|
|
211
|
+
"zoocode"
|
|
211
212
|
];
|
|
212
213
|
const ignoreProcessorToolTargetTuple = [
|
|
213
214
|
"aiassistant",
|
|
@@ -229,7 +230,8 @@ const ignoreProcessorToolTargetTuple = [
|
|
|
229
230
|
"devin",
|
|
230
231
|
"vibe",
|
|
231
232
|
"warp",
|
|
232
|
-
"zed"
|
|
233
|
+
"zed",
|
|
234
|
+
"zoocode"
|
|
233
235
|
];
|
|
234
236
|
const mcpProcessorToolTargetTuple = [
|
|
235
237
|
"aiassistant",
|
|
@@ -266,7 +268,8 @@ const mcpProcessorToolTargetTuple = [
|
|
|
266
268
|
"vibe",
|
|
267
269
|
"warp",
|
|
268
270
|
"devin",
|
|
269
|
-
"zed"
|
|
271
|
+
"zed",
|
|
272
|
+
"zoocode"
|
|
270
273
|
];
|
|
271
274
|
const commandsProcessorToolTargetTuple = [
|
|
272
275
|
"agentsmd",
|
|
@@ -294,6 +297,7 @@ const commandsProcessorToolTargetTuple = [
|
|
|
294
297
|
"qwencode",
|
|
295
298
|
"reasonix",
|
|
296
299
|
"roo",
|
|
300
|
+
"zoocode",
|
|
297
301
|
"rovodev",
|
|
298
302
|
"takt",
|
|
299
303
|
"devin",
|
|
@@ -329,6 +333,7 @@ const subagentsProcessorToolTargetTuple = [
|
|
|
329
333
|
"qwencode",
|
|
330
334
|
"reasonix",
|
|
331
335
|
"roo",
|
|
336
|
+
"zoocode",
|
|
332
337
|
"rovodev",
|
|
333
338
|
"takt",
|
|
334
339
|
"vibe"
|
|
@@ -372,7 +377,8 @@ const skillsProcessorToolTargetTuple = [
|
|
|
372
377
|
"vibe",
|
|
373
378
|
"warp",
|
|
374
379
|
"devin",
|
|
375
|
-
"zed"
|
|
380
|
+
"zed",
|
|
381
|
+
"zoocode"
|
|
376
382
|
];
|
|
377
383
|
const hooksProcessorToolTargetTuple = [
|
|
378
384
|
"amp",
|
|
@@ -685,7 +691,8 @@ async function findFilesByGlobs(globs, options = {}) {
|
|
|
685
691
|
onlyFiles: false,
|
|
686
692
|
onlyDirectories: false
|
|
687
693
|
};
|
|
688
|
-
const
|
|
694
|
+
const normalizedGlobs = Array.isArray(globs) ? globs.map((g) => g.replaceAll("\\", "/")) : globs.replaceAll("\\", "/");
|
|
695
|
+
const results = (0, globby.globbySync)(normalizedGlobs, {
|
|
689
696
|
absolute: true,
|
|
690
697
|
followSymbolicLinks,
|
|
691
698
|
...ignore ? { ignore: ignore.map((pattern) => pattern.replaceAll("\\", "/")) } : {},
|
|
@@ -796,7 +803,8 @@ function validateOutputRoot(outputRoot) {
|
|
|
796
803
|
function toKebabCaseFilename(filename) {
|
|
797
804
|
const lastDotIndex = filename.lastIndexOf(".");
|
|
798
805
|
const extension = lastDotIndex > 0 ? filename.slice(lastDotIndex) : "";
|
|
799
|
-
|
|
806
|
+
const nameWithoutExt = lastDotIndex > 0 ? filename.slice(0, lastDotIndex) : filename;
|
|
807
|
+
return (0, es_toolkit.kebabCase)(nameWithoutExt) + extension;
|
|
800
808
|
}
|
|
801
809
|
/**
|
|
802
810
|
* Create a temporary directory atomically and return its path.
|
|
@@ -1458,7 +1466,8 @@ const getDefaults = () => ({
|
|
|
1458
1466
|
});
|
|
1459
1467
|
const loadConfigFromFile = async (filePath) => {
|
|
1460
1468
|
if (!await fileExists(filePath)) return {};
|
|
1461
|
-
const
|
|
1469
|
+
const fileContent = await readFileContent(filePath);
|
|
1470
|
+
const jsonData = (0, jsonc_parser.parse)(fileContent);
|
|
1462
1471
|
const { $schema: _schema, ...configParams } = ConfigFileSchema.parse(jsonData);
|
|
1463
1472
|
assertTargetsFeaturesExclusive({
|
|
1464
1473
|
targets: configParams.targets,
|
|
@@ -1548,7 +1557,8 @@ var ConfigResolver = class {
|
|
|
1548
1557
|
if (inputRoot !== void 0) validateOutputRoot(inputRoot);
|
|
1549
1558
|
const validatedConfigPath = resolvePath(configPath, (0, node_path.resolve)(inputRoot ?? cwd));
|
|
1550
1559
|
const baseConfig = await loadConfigFromFile(validatedConfigPath);
|
|
1551
|
-
const
|
|
1560
|
+
const configDir = (0, node_path.dirname)(validatedConfigPath);
|
|
1561
|
+
const localConfigPath = (0, node_path.join)(configDir, RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH);
|
|
1552
1562
|
const localConfig = await loadConfigFromFile(localConfigPath);
|
|
1553
1563
|
const configByFile = mergeConfigs(baseConfig, localConfig);
|
|
1554
1564
|
if (inputRoot === void 0 && configByFile.inputRoot !== void 0) validateOutputRoot(configByFile.inputRoot);
|
|
@@ -1713,6 +1723,8 @@ const SHARED_USER_MANAGED_CONFIG_PATHS = [
|
|
|
1713
1723
|
".devin/config.json",
|
|
1714
1724
|
".factory/settings.json",
|
|
1715
1725
|
".grok/config.toml",
|
|
1726
|
+
".rovodev/config.yml",
|
|
1727
|
+
".rovodev/mcp.json",
|
|
1716
1728
|
".vibe/config.toml",
|
|
1717
1729
|
"reasonix.toml",
|
|
1718
1730
|
".vscode/settings.json",
|
|
@@ -1782,7 +1794,8 @@ var AiFile = class {
|
|
|
1782
1794
|
getFilePath() {
|
|
1783
1795
|
const fullPath = node_path.default.join(this.outputRoot, this.relativeDirPath, this.relativeFilePath);
|
|
1784
1796
|
const resolvedFull = (0, node_path.resolve)(fullPath);
|
|
1785
|
-
const
|
|
1797
|
+
const resolvedBase = (0, node_path.resolve)(this.outputRoot);
|
|
1798
|
+
const rel = (0, node_path.relative)(resolvedBase, resolvedFull);
|
|
1786
1799
|
if (rel.startsWith("..") || node_path.default.isAbsolute(rel)) throw new Error(`Path traversal detected: Final path escapes outputRoot. outputRoot="${this.outputRoot}", relativeDirPath="${this.relativeDirPath}", relativeFilePath="${this.relativeFilePath}"`);
|
|
1787
1800
|
return fullPath;
|
|
1788
1801
|
}
|
|
@@ -2350,6 +2363,7 @@ const OPENCODE_HOOK_EVENTS = [
|
|
|
2350
2363
|
"postToolUse",
|
|
2351
2364
|
"stop",
|
|
2352
2365
|
"afterFileEdit",
|
|
2366
|
+
"beforeShellExecution",
|
|
2353
2367
|
"afterShellExecution",
|
|
2354
2368
|
"permissionRequest",
|
|
2355
2369
|
"preCompact",
|
|
@@ -3055,7 +3069,6 @@ const CANONICAL_TO_OPENCODE_EVENT_NAMES = {
|
|
|
3055
3069
|
postToolUse: "tool.execute.after",
|
|
3056
3070
|
stop: "session.idle",
|
|
3057
3071
|
afterFileEdit: "file.edited",
|
|
3058
|
-
afterShellExecution: "command.executed",
|
|
3059
3072
|
permissionRequest: "permission.asked",
|
|
3060
3073
|
preCompact: "experimental.session.compacting",
|
|
3061
3074
|
postCompact: "session.compacted",
|
|
@@ -4122,10 +4135,22 @@ const ClaudecodePermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
4122
4135
|
* category; the shared `permission` block still drives the base permission and
|
|
4123
4136
|
* allow/deny lists. Keyed by canonical category (e.g. `bash`, `edit`).
|
|
4124
4137
|
*
|
|
4138
|
+
* Vibe's top-level `enabled_tools` is an **exclusive** allowlist — when set,
|
|
4139
|
+
* only the listed tools (name or pattern) are active and every other builtin
|
|
4140
|
+
* and MCP tool is off. That narrowing cannot be derived from canonical `allow`
|
|
4141
|
+
* rules (an allow grants one tool without revoking the rest), so the list is
|
|
4142
|
+
* only ever authored explicitly here, verbatim in Vibe's tool-name vocabulary,
|
|
4143
|
+
* and round-trips through this override on import.
|
|
4144
|
+
*
|
|
4125
4145
|
* @example
|
|
4126
4146
|
* { "permission": { "bash": { "sensitive_patterns": ["rm *", "sudo *"] } } }
|
|
4147
|
+
* @example
|
|
4148
|
+
* { "enabled_tools": ["bash", "read_file", "grep"] }
|
|
4127
4149
|
*/
|
|
4128
|
-
const VibePermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
4150
|
+
const VibePermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
4151
|
+
permission: zod_mini.z.optional(zod_mini.z.record(zod_mini.z.string(), zod_mini.z.looseObject({ sensitive_patterns: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())) }))),
|
|
4152
|
+
enabled_tools: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string()))
|
|
4153
|
+
});
|
|
4129
4154
|
/**
|
|
4130
4155
|
* Tool-scoped override block for Cursor CLI. Cursor's `cli.json` carries scalar
|
|
4131
4156
|
* autonomy settings with no canonical permission category — `approvalMode`
|
|
@@ -4197,7 +4222,10 @@ const QwencodePermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
4197
4222
|
const ReasonixPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
4198
4223
|
permission: zod_mini.z.optional(ToolScopedPermissionSchema),
|
|
4199
4224
|
sandbox: zod_mini.z.optional(zod_mini.z.looseObject({})),
|
|
4200
|
-
agent: zod_mini.z.optional(zod_mini.z.looseObject({}))
|
|
4225
|
+
agent: zod_mini.z.optional(zod_mini.z.looseObject({})),
|
|
4226
|
+
rawAllow: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
4227
|
+
rawAsk: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
4228
|
+
rawDeny: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string()))
|
|
4201
4229
|
});
|
|
4202
4230
|
/**
|
|
4203
4231
|
* Tool-scoped override block for Factory Droid. Factory Droid's `settings.json`
|
|
@@ -4228,12 +4256,69 @@ const FactorydroidPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
4228
4256
|
* `agent_mode_execute_readonly_commands` (a read-only auto-execution boolean).
|
|
4229
4257
|
* Fields placed here are merged into `[agents.profiles]` of Warp's global
|
|
4230
4258
|
* `settings.toml`, while the shared `permission` block continues to drive the
|
|
4231
|
-
*
|
|
4232
|
-
*
|
|
4259
|
+
* command regex arrays (legacy `agent_mode_command_execution_allowlist`/
|
|
4260
|
+
* `_denylist` plus the `default` execution profile's `command_allowlist`/
|
|
4261
|
+
* `command_denylist`). On migrated installs the `[agents.profiles]` keys are
|
|
4262
|
+
* inert; their execution-profile counterparts are authored via the nested
|
|
4263
|
+
* `execution_profile` block instead, which is merged into the `default` record
|
|
4264
|
+
* of `[agents.execution_profiles.<id>]`. Warp permissions are global-only.
|
|
4233
4265
|
*
|
|
4234
4266
|
* @example
|
|
4235
|
-
* {
|
|
4267
|
+
* {
|
|
4268
|
+
* "agent_mode_coding_permissions": "always_allow_reading",
|
|
4269
|
+
* "agent_mode_execute_readonly_commands": true,
|
|
4270
|
+
* "execution_profile": { "read_files": "always_allow", "mcp_denylist": ["untrusted-server"] }
|
|
4271
|
+
* }
|
|
4272
|
+
*/
|
|
4273
|
+
/**
|
|
4274
|
+
* Per-action autonomy value of Warp's execution profiles
|
|
4275
|
+
* (`FileActionPermission` in the Warp repository's
|
|
4276
|
+
* `app/src/ai/execution_profiles/config.rs`, serialized snake_case).
|
|
4236
4277
|
*/
|
|
4278
|
+
const WarpFileActionPermissionSchema = zod_mini.z.enum([
|
|
4279
|
+
"agent_decides",
|
|
4280
|
+
"always_allow",
|
|
4281
|
+
"always_ask"
|
|
4282
|
+
]);
|
|
4283
|
+
/**
|
|
4284
|
+
* Permission keys of the `default` record in Warp's
|
|
4285
|
+
* `[agents.execution_profiles.<id>]` collection (the surface runtime
|
|
4286
|
+
* enforcement reads on migrated installs). Loose so forward-compat keys pass
|
|
4287
|
+
* through verbatim. The rulesync-owned `command_allowlist`/`command_denylist`
|
|
4288
|
+
* are driven by the shared `permission.bash` block and always win over values
|
|
4289
|
+
* placed here.
|
|
4290
|
+
*
|
|
4291
|
+
* @see https://github.com/warpdotdev/warp/blob/main/app/src/ai/execution_profiles/config.rs (`ExecutionProfileFile`)
|
|
4292
|
+
*/
|
|
4293
|
+
const WarpExecutionProfileOverrideSchema = zod_mini.z.looseObject({
|
|
4294
|
+
read_files: zod_mini.z.optional(WarpFileActionPermissionSchema),
|
|
4295
|
+
apply_code_diffs: zod_mini.z.optional(WarpFileActionPermissionSchema),
|
|
4296
|
+
execute_commands: zod_mini.z.optional(WarpFileActionPermissionSchema),
|
|
4297
|
+
mcp_permissions: zod_mini.z.optional(WarpFileActionPermissionSchema),
|
|
4298
|
+
write_to_pty: zod_mini.z.optional(zod_mini.z.enum([
|
|
4299
|
+
"always_allow",
|
|
4300
|
+
"always_ask",
|
|
4301
|
+
"ask_on_first_write"
|
|
4302
|
+
])),
|
|
4303
|
+
ask_user_question: zod_mini.z.optional(zod_mini.z.enum([
|
|
4304
|
+
"never",
|
|
4305
|
+
"ask_except_in_auto_approve",
|
|
4306
|
+
"always_ask"
|
|
4307
|
+
])),
|
|
4308
|
+
run_agents: zod_mini.z.optional(zod_mini.z.enum([
|
|
4309
|
+
"never_allow",
|
|
4310
|
+
"always_allow",
|
|
4311
|
+
"always_ask"
|
|
4312
|
+
])),
|
|
4313
|
+
computer_use: zod_mini.z.optional(zod_mini.z.enum([
|
|
4314
|
+
"never",
|
|
4315
|
+
"always_ask",
|
|
4316
|
+
"always_allow"
|
|
4317
|
+
])),
|
|
4318
|
+
directory_allowlist: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
4319
|
+
mcp_allowlist: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
4320
|
+
mcp_denylist: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string()))
|
|
4321
|
+
});
|
|
4237
4322
|
const WarpPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
4238
4323
|
permission: zod_mini.z.optional(ToolScopedPermissionSchema),
|
|
4239
4324
|
agent_mode_coding_permissions: zod_mini.z.optional(zod_mini.z.enum([
|
|
@@ -4242,7 +4327,8 @@ const WarpPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
4242
4327
|
"allow_reading_specific_files"
|
|
4243
4328
|
])),
|
|
4244
4329
|
agent_mode_coding_file_read_allowlist: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
4245
|
-
agent_mode_execute_readonly_commands: zod_mini.z.optional(zod_mini.z.boolean())
|
|
4330
|
+
agent_mode_execute_readonly_commands: zod_mini.z.optional(zod_mini.z.boolean()),
|
|
4331
|
+
execution_profile: zod_mini.z.optional(WarpExecutionProfileOverrideSchema)
|
|
4246
4332
|
});
|
|
4247
4333
|
/**
|
|
4248
4334
|
* The actions Junie's allowlist accepts. Verified against the shipped Junie
|
|
@@ -4974,7 +5060,8 @@ var AiDir = class {
|
|
|
4974
5060
|
getDirPath() {
|
|
4975
5061
|
const fullPath = node_path.default.join(this.outputRoot, this.relativeDirPath, this.dirName);
|
|
4976
5062
|
const resolvedFull = (0, node_path.resolve)(fullPath);
|
|
4977
|
-
const
|
|
5063
|
+
const resolvedBase = (0, node_path.resolve)(this.outputRoot);
|
|
5064
|
+
const rel = (0, node_path.relative)(resolvedBase, resolvedFull);
|
|
4978
5065
|
if (rel.startsWith("..") || node_path.default.isAbsolute(rel)) throw new Error(`Path traversal detected: Final path escapes outputRoot. outputRoot="${this.outputRoot}", relativeDirPath="${this.relativeDirPath}", dirName="${this.dirName}"`);
|
|
4979
5066
|
return fullPath;
|
|
4980
5067
|
}
|
|
@@ -5257,6 +5344,9 @@ const RulesyncSubagentFrontmatterSchema = zod_mini.z.looseObject({
|
|
|
5257
5344
|
customInstructions: zod_mini.z.optional(zod_mini.z.string()),
|
|
5258
5345
|
groups: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.unknown()))
|
|
5259
5346
|
})),
|
|
5347
|
+
zoocode: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
5348
|
+
/** Per-mode MCP server allowlist (Zoo Code v3.60.0+); omitted = all. */
|
|
5349
|
+
allowedMcpServers: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())) })),
|
|
5260
5350
|
vibe: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
5261
5351
|
agent_type: zod_mini.z.optional(zod_mini.z.enum(["agent", "subagent"])),
|
|
5262
5352
|
display_name: zod_mini.z.optional(zod_mini.z.string()),
|
|
@@ -5396,7 +5486,6 @@ const ROVODEV_SKILLS_DIR_PATH = (0, node_path.join)(ROVODEV_DIR, "skills");
|
|
|
5396
5486
|
const ROVODEV_SUBAGENTS_DIR_PATH = (0, node_path.join)(ROVODEV_DIR, "subagents");
|
|
5397
5487
|
const ROVODEV_MODULAR_RULES_DIR_PATH = (0, node_path.join)(ROVODEV_DIR, ".rulesync", "modular-rules");
|
|
5398
5488
|
const ROVODEV_RULE_FILE_NAME = "AGENTS.md";
|
|
5399
|
-
const ROVODEV_LEGACY_RULE_FILE_NAME = "AGENTS.local.md";
|
|
5400
5489
|
const ROVODEV_MCP_FILE_NAME = "mcp.json";
|
|
5401
5490
|
const ROVODEV_CONFIG_FILE_NAME = "config.yml";
|
|
5402
5491
|
const ROVODEV_AGENTS_SKILLS_DIR_PATH = (0, node_path.join)(".agents", "skills");
|
|
@@ -6615,6 +6704,7 @@ const KIMI_CODE_CONFIG_SHARED_FILE_KEY = ".kimi-code/config.toml";
|
|
|
6615
6704
|
const KIMI_CODE_HOME_CONFIG_SHARED_FILE_KEY = "config.toml";
|
|
6616
6705
|
const REASONIX_PROJECT_CONFIG_SHARED_FILE_KEY = "reasonix.toml";
|
|
6617
6706
|
const REASONIX_GLOBAL_CONFIG_SHARED_FILE_KEY = ".reasonix/config.toml";
|
|
6707
|
+
const ROVODEV_CONFIG_SHARED_FILE_KEY = ".rovodev/config.yml";
|
|
6618
6708
|
/**
|
|
6619
6709
|
* Build the `SHARED_CONFIG_OWNERSHIP` lookup key from a tool's settable paths.
|
|
6620
6710
|
* Mirrors `sharedFileKey` in `src/lib/shared-file-derive.ts` (kept separate so
|
|
@@ -6954,6 +7044,10 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
6954
7044
|
permissions: {
|
|
6955
7045
|
kind: "replace-owned-keys",
|
|
6956
7046
|
ownedKeys: ["permission"]
|
|
7047
|
+
},
|
|
7048
|
+
rules: {
|
|
7049
|
+
kind: "replace-owned-keys",
|
|
7050
|
+
ownedKeys: ["instructions"]
|
|
6957
7051
|
}
|
|
6958
7052
|
}
|
|
6959
7053
|
},
|
|
@@ -7071,6 +7165,19 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
7071
7165
|
]
|
|
7072
7166
|
}
|
|
7073
7167
|
}
|
|
7168
|
+
},
|
|
7169
|
+
[ROVODEV_CONFIG_SHARED_FILE_KEY]: {
|
|
7170
|
+
format: "yaml",
|
|
7171
|
+
features: {
|
|
7172
|
+
mcp: {
|
|
7173
|
+
kind: "replace-owned-keys",
|
|
7174
|
+
ownedKeys: ["mcp"]
|
|
7175
|
+
},
|
|
7176
|
+
permissions: {
|
|
7177
|
+
kind: "replace-owned-keys",
|
|
7178
|
+
ownedKeys: ["toolPermissions"]
|
|
7179
|
+
}
|
|
7180
|
+
}
|
|
7074
7181
|
}
|
|
7075
7182
|
};
|
|
7076
7183
|
/**
|
|
@@ -7492,7 +7599,8 @@ const toolCheckFactories = /* @__PURE__ */ new Map([
|
|
|
7492
7599
|
class: CursorCheck,
|
|
7493
7600
|
meta: {
|
|
7494
7601
|
supportsGlobal: false,
|
|
7495
|
-
filePattern: CURSOR_BUGBOT_FILE_NAME
|
|
7602
|
+
filePattern: CURSOR_BUGBOT_FILE_NAME,
|
|
7603
|
+
committedOutput: true
|
|
7496
7604
|
}
|
|
7497
7605
|
}],
|
|
7498
7606
|
["hermesagent", {
|
|
@@ -7506,7 +7614,8 @@ const toolCheckFactories = /* @__PURE__ */ new Map([
|
|
|
7506
7614
|
class: RovodevCheck,
|
|
7507
7615
|
meta: {
|
|
7508
7616
|
supportsGlobal: false,
|
|
7509
|
-
filePattern: ROVODEV_REVIEW_AGENT_FILE_NAME
|
|
7617
|
+
filePattern: ROVODEV_REVIEW_AGENT_FILE_NAME,
|
|
7618
|
+
committedOutput: true
|
|
7510
7619
|
}
|
|
7511
7620
|
}],
|
|
7512
7621
|
["takt", {
|
|
@@ -9668,7 +9777,8 @@ var GrokcliCommand = class GrokcliCommand extends ToolCommand {
|
|
|
9668
9777
|
static async validateRulesyncCommands({ inputRoot, rulesyncCommands, logger }) {
|
|
9669
9778
|
const commandNames = new Set(rulesyncCommands.filter((command) => this.isTargetedByRulesyncCommand(command)).map((command) => (0, node_path.basename)(command.getRelativeFilePath(), ".md")));
|
|
9670
9779
|
if (commandNames.size === 0) return;
|
|
9671
|
-
const
|
|
9780
|
+
const skillsRoot = (0, node_path.join)(inputRoot, RULESYNC_SKILLS_RELATIVE_DIR_PATH);
|
|
9781
|
+
const shadowed = (await findFilesByGlobs((0, node_path.join)(skillsRoot, "**", "SKILL.md"))).map((filePath) => (0, node_path.basename)((0, node_path.dirname)(filePath))).filter((skillName) => commandNames.has(skillName));
|
|
9672
9782
|
if (shadowed.length > 0) logger.warn(`Grok CLI resolves skills before commands, so these skills shadow the same-named commands, which will never be reachable: ${[...new Set(shadowed)].toSorted().join(", ")}. Rename either side to make both invocable.`);
|
|
9673
9783
|
}
|
|
9674
9784
|
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
|
|
@@ -9703,6 +9813,20 @@ var GrokcliCommand = class GrokcliCommand extends ToolCommand {
|
|
|
9703
9813
|
function toolSkillSearchRoots(paths) {
|
|
9704
9814
|
return [paths.relativeDirPath, ...paths.alternativeSkillRoots ?? []];
|
|
9705
9815
|
}
|
|
9816
|
+
/**
|
|
9817
|
+
* The Agent Skills interoperability roots: project `.agents/skills/`, global
|
|
9818
|
+
* `~/.agents/skills/` (same relative path, home output root), and Amp's global
|
|
9819
|
+
* equivalent `~/.config/agents/skills/`. Foreign-authored — potentially
|
|
9820
|
+
* non-conformant — skills are the most likely to live here, so every tool that
|
|
9821
|
+
* scans one of these roots imports it leniently (skip-and-warn per skill), per
|
|
9822
|
+
* the Agent Skills client-implementation guide, while keeping fail-fast
|
|
9823
|
+
* behavior for its own native root.
|
|
9824
|
+
* https://agentskills.io/client-implementation/adding-skills-support
|
|
9825
|
+
*/
|
|
9826
|
+
const AGENT_SKILLS_INTEROP_ROOTS = /* @__PURE__ */ new Set([toPosixPath(AGENTSMD_SKILLS_DIR_PATH), toPosixPath(AMP_SKILLS_GLOBAL_DIR)]);
|
|
9827
|
+
function isAgentSkillsInteropRoot(relativeDirPath) {
|
|
9828
|
+
return AGENT_SKILLS_INTEROP_ROOTS.has(toPosixPath(relativeDirPath));
|
|
9829
|
+
}
|
|
9706
9830
|
/** Ordered import roots: managed roots first, followed by read-only discovery roots. */
|
|
9707
9831
|
function toolSkillImportRoots(paths) {
|
|
9708
9832
|
return [...toolSkillSearchRoots(paths), ...paths.importOnlySkillRoots ?? []];
|
|
@@ -11646,7 +11770,7 @@ var RooCommand = class RooCommand extends ToolCommand {
|
|
|
11646
11770
|
};
|
|
11647
11771
|
const body = rulesyncCommand.getBody();
|
|
11648
11772
|
const fileContent = stringifyFrontmatter(body, rooFrontmatter);
|
|
11649
|
-
return new
|
|
11773
|
+
return new this({
|
|
11650
11774
|
outputRoot,
|
|
11651
11775
|
frontmatter: rooFrontmatter,
|
|
11652
11776
|
body,
|
|
@@ -11683,7 +11807,7 @@ var RooCommand = class RooCommand extends ToolCommand {
|
|
|
11683
11807
|
const { frontmatter, body: content } = parseFrontmatter(fileContent, filePath);
|
|
11684
11808
|
const result = RooCommandFrontmatterSchema.safeParse(frontmatter);
|
|
11685
11809
|
if (!result.success) throw new Error(`Invalid frontmatter in ${filePath}: ${formatError(result.error)}`);
|
|
11686
|
-
return new
|
|
11810
|
+
return new this({
|
|
11687
11811
|
outputRoot,
|
|
11688
11812
|
relativeDirPath: RooCommand.getSettablePaths().relativeDirPath,
|
|
11689
11813
|
relativeFilePath,
|
|
@@ -11694,7 +11818,7 @@ var RooCommand = class RooCommand extends ToolCommand {
|
|
|
11694
11818
|
});
|
|
11695
11819
|
}
|
|
11696
11820
|
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
11697
|
-
return new
|
|
11821
|
+
return new this({
|
|
11698
11822
|
outputRoot,
|
|
11699
11823
|
relativeDirPath,
|
|
11700
11824
|
relativeFilePath,
|
|
@@ -12152,6 +12276,39 @@ var WarpCommand = class WarpCommand extends ToolCommand {
|
|
|
12152
12276
|
}
|
|
12153
12277
|
};
|
|
12154
12278
|
//#endregion
|
|
12279
|
+
//#region src/features/commands/zoocode-command.ts
|
|
12280
|
+
/**
|
|
12281
|
+
* Command generator for **Zoo Code** (the community continuation of Roo Code).
|
|
12282
|
+
* Zoo Code keeps Roo's `.roo/commands/` layout, so this target reuses
|
|
12283
|
+
* {@link RooCommand} verbatim and only narrows the targeting.
|
|
12284
|
+
*
|
|
12285
|
+
* @see https://docs.zoocode.dev
|
|
12286
|
+
*/
|
|
12287
|
+
var ZoocodeCommand = class extends RooCommand {
|
|
12288
|
+
static isTargetedByRulesyncCommand(rulesyncCommand) {
|
|
12289
|
+
return this.isTargetedByRulesyncCommandDefault({
|
|
12290
|
+
rulesyncCommand,
|
|
12291
|
+
toolTarget: "zoocode"
|
|
12292
|
+
});
|
|
12293
|
+
}
|
|
12294
|
+
toRulesyncCommand() {
|
|
12295
|
+
const command = super.toRulesyncCommand();
|
|
12296
|
+
const frontmatter = {
|
|
12297
|
+
...command.getFrontmatter(),
|
|
12298
|
+
targets: ["zoocode"]
|
|
12299
|
+
};
|
|
12300
|
+
return new RulesyncCommand({
|
|
12301
|
+
outputRoot: command.getOutputRoot(),
|
|
12302
|
+
relativeDirPath: command.getRelativeDirPath(),
|
|
12303
|
+
relativeFilePath: command.getRelativeFilePath(),
|
|
12304
|
+
frontmatter,
|
|
12305
|
+
body: command.getBody(),
|
|
12306
|
+
fileContent: stringifyFrontmatter(command.getBody(), frontmatter),
|
|
12307
|
+
validate: true
|
|
12308
|
+
});
|
|
12309
|
+
}
|
|
12310
|
+
};
|
|
12311
|
+
//#endregion
|
|
12155
12312
|
//#region src/features/commands/commands-processor.ts
|
|
12156
12313
|
const CommandsProcessorToolTargetSchema = zod_mini.z.enum(commandsProcessorToolTargetTuple);
|
|
12157
12314
|
/**
|
|
@@ -12411,6 +12568,16 @@ const toolCommandFactories = /* @__PURE__ */ new Map([
|
|
|
12411
12568
|
supportsSubdirectory: true
|
|
12412
12569
|
}
|
|
12413
12570
|
}],
|
|
12571
|
+
["zoocode", {
|
|
12572
|
+
class: ZoocodeCommand,
|
|
12573
|
+
meta: {
|
|
12574
|
+
extension: "md",
|
|
12575
|
+
supportsProject: true,
|
|
12576
|
+
supportsGlobal: true,
|
|
12577
|
+
isSimulated: false,
|
|
12578
|
+
supportsSubdirectory: true
|
|
12579
|
+
}
|
|
12580
|
+
}],
|
|
12414
12581
|
["rovodev", {
|
|
12415
12582
|
class: RovodevCommand,
|
|
12416
12583
|
meta: {
|
|
@@ -13790,16 +13957,17 @@ var ClaudecodeHooks = class extends ToolHooks {
|
|
|
13790
13957
|
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
13791
13958
|
const existingContent = await readFileContentOrNull(filePath) ?? JSON.stringify({}, null, 2);
|
|
13792
13959
|
const config = rulesyncHooks.getJson();
|
|
13960
|
+
const claudeHooks = canonicalToToolHooks({
|
|
13961
|
+
config,
|
|
13962
|
+
toolOverrideHooks: config.claudecode?.hooks,
|
|
13963
|
+
converterConfig: CLAUDE_CONVERTER_CONFIG,
|
|
13964
|
+
logger
|
|
13965
|
+
});
|
|
13793
13966
|
const fileContent = applySharedConfigPatch({
|
|
13794
13967
|
fileKey: CLAUDE_SETTINGS_SHARED_FILE_KEY,
|
|
13795
13968
|
feature: "hooks",
|
|
13796
13969
|
existingContent,
|
|
13797
|
-
patch: { hooks:
|
|
13798
|
-
config,
|
|
13799
|
-
toolOverrideHooks: config.claudecode?.hooks,
|
|
13800
|
-
converterConfig: CLAUDE_CONVERTER_CONFIG,
|
|
13801
|
-
logger
|
|
13802
|
-
}) },
|
|
13970
|
+
patch: { hooks: claudeHooks },
|
|
13803
13971
|
filePath
|
|
13804
13972
|
});
|
|
13805
13973
|
return new this({
|
|
@@ -15553,6 +15721,30 @@ const NAMED_HOOK_MATCHER_SUBJECTS = {
|
|
|
15553
15721
|
"tool.execute.after": "input.tool",
|
|
15554
15722
|
"experimental.session.compacting": null
|
|
15555
15723
|
};
|
|
15724
|
+
/**
|
|
15725
|
+
* OpenCode (and Kilo) have no shell-execution lifecycle event — the
|
|
15726
|
+
* `command.executed` event these canonical events were once mapped to is a
|
|
15727
|
+
* *slash-command* event, so a hook wired there never fired on bash commands
|
|
15728
|
+
* and fired on every slash command instead. Observing a shell invocation is
|
|
15729
|
+
* done with the named `tool.execute.before/after` hooks gated on the `bash`
|
|
15730
|
+
* tool (the `.env protection` pattern in the OpenCode plugins doc), so these
|
|
15731
|
+
* canonical events are emitted into those named hooks with an implicit
|
|
15732
|
+
* `input.tool === "bash"` gate. Matchers on them are dropped upstream with a
|
|
15733
|
+
* warning (`matcherEvents` covers only `preToolUse`/`postToolUse`) — the
|
|
15734
|
+
* named hooks expose no command text to match against.
|
|
15735
|
+
*
|
|
15736
|
+
* @see https://opencode.ai/docs/plugins/
|
|
15737
|
+
*/
|
|
15738
|
+
const SHELL_EVENT_TOOL_GATES = {
|
|
15739
|
+
beforeShellExecution: {
|
|
15740
|
+
toolEvent: "tool.execute.before",
|
|
15741
|
+
tool: "bash"
|
|
15742
|
+
},
|
|
15743
|
+
afterShellExecution: {
|
|
15744
|
+
toolEvent: "tool.execute.after",
|
|
15745
|
+
tool: "bash"
|
|
15746
|
+
}
|
|
15747
|
+
};
|
|
15556
15748
|
function escapeForTemplateLiteral(command) {
|
|
15557
15749
|
return command.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
|
|
15558
15750
|
}
|
|
@@ -15573,15 +15765,19 @@ function validateAndSanitizeMatcher(matcher) {
|
|
|
15573
15765
|
*/
|
|
15574
15766
|
function collectOpencodeStyleHandlers({ effectiveHooks, eventMap, namedEventHandlers, genericEventHandlers }) {
|
|
15575
15767
|
for (const [canonicalEvent, definitions] of Object.entries(effectiveHooks)) {
|
|
15576
|
-
const
|
|
15768
|
+
const shellGate = SHELL_EVENT_TOOL_GATES[canonicalEvent];
|
|
15769
|
+
const toolEvent = shellGate?.toolEvent ?? eventMap[canonicalEvent];
|
|
15577
15770
|
if (!toolEvent) continue;
|
|
15771
|
+
const matcherSupported = !shellGate && Object.hasOwn(NAMED_HOOK_MATCHER_SUBJECTS, toolEvent) && NAMED_HOOK_MATCHER_SUBJECTS[toolEvent] !== null;
|
|
15578
15772
|
const handlers = [];
|
|
15579
15773
|
for (const def of definitions) {
|
|
15580
15774
|
if ((def.type ?? "command") !== "command") continue;
|
|
15581
15775
|
if (!def.command) continue;
|
|
15776
|
+
if (def.matcher && !matcherSupported) continue;
|
|
15582
15777
|
handlers.push({
|
|
15583
15778
|
command: def.command,
|
|
15584
|
-
matcher: def.matcher ? def.matcher : void 0
|
|
15779
|
+
matcher: def.matcher ? def.matcher : void 0,
|
|
15780
|
+
...shellGate ? { toolGate: shellGate.tool } : {}
|
|
15585
15781
|
});
|
|
15586
15782
|
}
|
|
15587
15783
|
if (handlers.length > 0) {
|
|
@@ -15618,7 +15814,11 @@ function buildNamedEventBodyLines(namedEventHandlers) {
|
|
|
15618
15814
|
bodyLines.push(` "${eventName}": async (input) => {`);
|
|
15619
15815
|
for (const handler of handlers) {
|
|
15620
15816
|
const escapedCommand = escapeForTemplateLiteral(handler.command);
|
|
15621
|
-
if (handler.
|
|
15817
|
+
if (handler.toolGate) {
|
|
15818
|
+
bodyLines.push(` if (input.tool === "${handler.toolGate}") {`);
|
|
15819
|
+
bodyLines.push(` await $\`${escapedCommand}\`;`);
|
|
15820
|
+
bodyLines.push(" }");
|
|
15821
|
+
} else if (handler.matcher && matcherSubject !== null) {
|
|
15622
15822
|
const safeMatcher = validateAndSanitizeMatcher(handler.matcher);
|
|
15623
15823
|
bodyLines.push(" {");
|
|
15624
15824
|
bodyLines.push(` const __re = new RegExp("${safeMatcher}");`);
|
|
@@ -17348,7 +17548,8 @@ const toolHooksFactories = /* @__PURE__ */ new Map([
|
|
|
17348
17548
|
},
|
|
17349
17549
|
supportedEvents: KILO_HOOK_EVENTS,
|
|
17350
17550
|
supportedHookTypes: ["command"],
|
|
17351
|
-
supportsMatcher: true
|
|
17551
|
+
supportsMatcher: true,
|
|
17552
|
+
matcherEvents: ["preToolUse", "postToolUse"]
|
|
17352
17553
|
}],
|
|
17353
17554
|
["opencode", {
|
|
17354
17555
|
class: OpencodeHooks,
|
|
@@ -17359,7 +17560,8 @@ const toolHooksFactories = /* @__PURE__ */ new Map([
|
|
|
17359
17560
|
},
|
|
17360
17561
|
supportedEvents: OPENCODE_HOOK_EVENTS,
|
|
17361
17562
|
supportedHookTypes: ["command"],
|
|
17362
|
-
supportsMatcher: true
|
|
17563
|
+
supportsMatcher: true,
|
|
17564
|
+
matcherEvents: ["preToolUse", "postToolUse"]
|
|
17363
17565
|
}],
|
|
17364
17566
|
["pi", {
|
|
17365
17567
|
class: PiHooks,
|
|
@@ -19033,6 +19235,7 @@ const toolIgnoreFactories = /* @__PURE__ */ new Map([
|
|
|
19033
19235
|
["qwencode", { class: QwencodeIgnore }],
|
|
19034
19236
|
["reasonix", { class: ReasonixIgnore }],
|
|
19035
19237
|
["roo", { class: RooIgnore }],
|
|
19238
|
+
["zoocode", { class: RooIgnore }],
|
|
19036
19239
|
["devin", { class: DevinIgnore }],
|
|
19037
19240
|
["vibe", { class: VibeIgnore }],
|
|
19038
19241
|
["warp", { class: WarpIgnore }],
|
|
@@ -20402,7 +20605,6 @@ const resolveCopilotcliServerType = (server) => {
|
|
|
20402
20605
|
case "streamable-http": return "http";
|
|
20403
20606
|
case "stdio":
|
|
20404
20607
|
case "local": return declared;
|
|
20405
|
-
default: break;
|
|
20406
20608
|
}
|
|
20407
20609
|
if (server.command !== void 0) return "stdio";
|
|
20408
20610
|
return isRemoteMcpServer(server) ? "http" : "stdio";
|
|
@@ -22175,9 +22377,12 @@ var KiloMcp = class KiloMcp extends ToolMcp {
|
|
|
22175
22377
|
fileContent = await readFileContentOrNull(jsonPath);
|
|
22176
22378
|
if (fileContent) relativeFilePath = KILO_JSON_FILE_NAME;
|
|
22177
22379
|
}
|
|
22380
|
+
if (instructions.length === 0 && fileContent === null) return null;
|
|
22178
22381
|
const json = fileContent ? (0, jsonc_parser.parse)(fileContent) : {};
|
|
22179
22382
|
const existingInstructions = Array.isArray(json.instructions) ? json.instructions.filter((entry) => typeof entry === "string") : [];
|
|
22180
|
-
const
|
|
22383
|
+
const managedPrefix = `${toPosixPath((0, node_path.join)(KILO_DIR, KILO_RULES_DIR_NAME))}/`;
|
|
22384
|
+
const preservedInstructions = existingInstructions.filter((entry) => !toPosixPath(entry).replace(/^\.\//, "").startsWith(managedPrefix));
|
|
22385
|
+
const mergedInstructions = Array.from(/* @__PURE__ */ new Set([...preservedInstructions, ...instructions])).toSorted();
|
|
22181
22386
|
return new KiloMcp({
|
|
22182
22387
|
outputRoot,
|
|
22183
22388
|
relativeDirPath: basePaths.relativeDirPath,
|
|
@@ -22186,7 +22391,7 @@ var KiloMcp = class KiloMcp extends ToolMcp {
|
|
|
22186
22391
|
fileKey: sharedConfigFileKey(basePaths),
|
|
22187
22392
|
feature: "rules",
|
|
22188
22393
|
existingContent: fileContent ?? "",
|
|
22189
|
-
patch: { instructions: mergedInstructions },
|
|
22394
|
+
patch: { instructions: mergedInstructions.length > 0 ? mergedInstructions : void 0 },
|
|
22190
22395
|
filePath: (0, node_path.join)(jsonDir, relativeFilePath)
|
|
22191
22396
|
}),
|
|
22192
22397
|
validate
|
|
@@ -22908,6 +23113,8 @@ var OpencodeMcp = class OpencodeMcp extends ToolMcp {
|
|
|
22908
23113
|
static async fromInstructions({ outputRoot = process.cwd(), instructions, validate = true, global = false }) {
|
|
22909
23114
|
const basePaths = this.getSettablePaths({ global });
|
|
22910
23115
|
const jsonDir = (0, node_path.join)(outputRoot, basePaths.relativeDirPath);
|
|
23116
|
+
const configDirPrefix = `${toPosixPath(basePaths.relativeDirPath).replace(/\/+$/, "")}/`;
|
|
23117
|
+
const normalizedInstructions = instructions.map((path) => global && path.startsWith(configDirPrefix) ? path.slice(configDirPrefix.length) : path);
|
|
22911
23118
|
let fileContent = null;
|
|
22912
23119
|
let relativeFilePath = OPENCODE_JSONC_FILE_NAME;
|
|
22913
23120
|
const jsoncPath = (0, node_path.join)(jsonDir, OPENCODE_JSONC_FILE_NAME);
|
|
@@ -22917,9 +23124,15 @@ var OpencodeMcp = class OpencodeMcp extends ToolMcp {
|
|
|
22917
23124
|
fileContent = await readFileContentOrNull(jsonPath);
|
|
22918
23125
|
if (fileContent) relativeFilePath = OPENCODE_JSON_FILE_NAME;
|
|
22919
23126
|
}
|
|
23127
|
+
if (instructions.length === 0 && fileContent === null) return null;
|
|
22920
23128
|
const json = fileContent ? (0, jsonc_parser.parse)(fileContent) : {};
|
|
22921
23129
|
const existingInstructions = Array.isArray(json.instructions) ? json.instructions.filter((entry) => typeof entry === "string") : [];
|
|
22922
|
-
const
|
|
23130
|
+
const managedPrefixes = global ? ["memories/", `${configDirPrefix}memories/`] : [`${toPosixPath(OPENCODE_DIR)}/memories/`];
|
|
23131
|
+
const preservedInstructions = existingInstructions.filter((entry) => {
|
|
23132
|
+
const normalized = toPosixPath(entry).replace(/^\.\//, "");
|
|
23133
|
+
return !managedPrefixes.some((prefix) => normalized.startsWith(prefix));
|
|
23134
|
+
});
|
|
23135
|
+
const mergedInstructions = Array.from(/* @__PURE__ */ new Set([...preservedInstructions, ...normalizedInstructions])).toSorted();
|
|
22923
23136
|
return new OpencodeMcp({
|
|
22924
23137
|
outputRoot,
|
|
22925
23138
|
relativeDirPath: basePaths.relativeDirPath,
|
|
@@ -22928,7 +23141,7 @@ var OpencodeMcp = class OpencodeMcp extends ToolMcp {
|
|
|
22928
23141
|
fileKey: sharedConfigFileKey(basePaths),
|
|
22929
23142
|
feature: "rules",
|
|
22930
23143
|
existingContent: fileContent ?? "",
|
|
22931
|
-
patch: { instructions: mergedInstructions },
|
|
23144
|
+
patch: { instructions: mergedInstructions.length > 0 ? mergedInstructions : void 0 },
|
|
22932
23145
|
filePath: (0, node_path.join)(jsonDir, relativeFilePath)
|
|
22933
23146
|
}),
|
|
22934
23147
|
validate
|
|
@@ -23368,9 +23581,13 @@ function parseRovodevMcpJson(fileContent, relativeDirPath, relativeFilePath) {
|
|
|
23368
23581
|
return parsed;
|
|
23369
23582
|
}
|
|
23370
23583
|
/**
|
|
23371
|
-
* Rovodev MCP:
|
|
23372
|
-
*
|
|
23373
|
-
*
|
|
23584
|
+
* Rovodev MCP: `~/.rovodev/mcp.json` (global) and the repo-committed project
|
|
23585
|
+
* `.rovodev/mcp.json` documented by the Bitbucket Cloud Agentic Pipelines
|
|
23586
|
+
* guide ("Register your MCP server in `.rovodev/mcp.json`", referenced via
|
|
23587
|
+
* `mcp.mcpConfigPath`). Same shape as Cursor: { mcpServers: { ... } }.
|
|
23588
|
+
* A server the canonical config marks `disabled: true` is still written here
|
|
23589
|
+
* and switched off through `mcp.disabledMcpServers` in the sibling
|
|
23590
|
+
* `config.yml` — the file Rovo Dev actually consults for disabling.
|
|
23374
23591
|
*/
|
|
23375
23592
|
/**
|
|
23376
23593
|
* Rovo Dev documents the per-server transport key as `transport`, with the
|
|
@@ -23396,11 +23613,7 @@ function lookupTransport(map, key) {
|
|
|
23396
23613
|
return Object.hasOwn(map, key) ? map[key] : void 0;
|
|
23397
23614
|
}
|
|
23398
23615
|
function toRovodevServer(name, server, logger) {
|
|
23399
|
-
const { type, transport, disabled, ...rest } = server;
|
|
23400
|
-
if (disabled === true) {
|
|
23401
|
-
logger?.warn(`Rovo Dev MCP: skipping "${name}" because it is disabled and mcp.json has no disable flag.`);
|
|
23402
|
-
return null;
|
|
23403
|
-
}
|
|
23616
|
+
const { type, transport, disabled: _disabled, ...rest } = server;
|
|
23404
23617
|
const declared = typeof transport === "string" ? transport : typeof type === "string" ? type : void 0;
|
|
23405
23618
|
if (declared === void 0) return rest;
|
|
23406
23619
|
const mapped = lookupTransport(CANONICAL_TO_ROVODEV_TRANSPORT, declared);
|
|
@@ -23422,6 +23635,44 @@ function fromRovodevServer(server) {
|
|
|
23422
23635
|
type: mapped
|
|
23423
23636
|
};
|
|
23424
23637
|
}
|
|
23638
|
+
/**
|
|
23639
|
+
* Read the sibling `config.yml` (same scope root as `mcp.json`) and return the
|
|
23640
|
+
* parsed document, `null` when the file does not exist. A malformed file
|
|
23641
|
+
* **throws**: the disable toggle lives here, so pretending a broken file says
|
|
23642
|
+
* nothing would silently re-enable servers on import and leave disabled ones
|
|
23643
|
+
* running on generate — both flip a security toggle the wrong way.
|
|
23644
|
+
*/
|
|
23645
|
+
async function readRovodevConfigYaml({ outputRoot }) {
|
|
23646
|
+
const content = await readFileContentOrNull((0, node_path.join)(outputRoot, ROVODEV_DIR, ROVODEV_CONFIG_FILE_NAME));
|
|
23647
|
+
if (content === null) return null;
|
|
23648
|
+
return parseSharedConfig({
|
|
23649
|
+
format: "yaml",
|
|
23650
|
+
fileContent: content,
|
|
23651
|
+
filePath: (0, node_path.join)(ROVODEV_DIR, ROVODEV_CONFIG_FILE_NAME)
|
|
23652
|
+
});
|
|
23653
|
+
}
|
|
23654
|
+
function disabledNamesOf(config) {
|
|
23655
|
+
const mcpBlock = config && isRecord(config.mcp) ? config.mcp : {};
|
|
23656
|
+
return isStringArray$1(mcpBlock.disabledMcpServers) ? mcpBlock.disabledMcpServers : [];
|
|
23657
|
+
}
|
|
23658
|
+
/**
|
|
23659
|
+
* Auxiliary writer for the `mcp:` block of `.rovodev/config.yml` (project) /
|
|
23660
|
+
* `~/.rovodev/config.yml` (global). Carries `disabledMcpServers` — the key
|
|
23661
|
+
* Rovo Dev actually consults to switch a server off — recomputed from the
|
|
23662
|
+
* existing block so user keys (`mcpConfigPath`, `allowedMcpServers`, ...) and
|
|
23663
|
+
* disabled names for servers rulesync does not manage survive.
|
|
23664
|
+
*/
|
|
23665
|
+
var RovodevMcpConfigYaml = class extends ToolFile {
|
|
23666
|
+
isDeletable() {
|
|
23667
|
+
return false;
|
|
23668
|
+
}
|
|
23669
|
+
validate() {
|
|
23670
|
+
return {
|
|
23671
|
+
success: true,
|
|
23672
|
+
error: null
|
|
23673
|
+
};
|
|
23674
|
+
}
|
|
23675
|
+
};
|
|
23425
23676
|
var RovodevMcp = class RovodevMcp extends ToolMcp {
|
|
23426
23677
|
json;
|
|
23427
23678
|
constructor(params) {
|
|
@@ -23442,13 +23693,24 @@ var RovodevMcp = class RovodevMcp extends ToolMcp {
|
|
|
23442
23693
|
};
|
|
23443
23694
|
}
|
|
23444
23695
|
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
23445
|
-
if (!global) throw new Error("Rovodev MCP is global-only; use --global to sync ~/.rovodev/mcp.json");
|
|
23446
23696
|
const paths = this.getSettablePaths({ global });
|
|
23447
23697
|
const json = parseRovodevMcpJson(await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "{\"mcpServers\":{}}", paths.relativeDirPath, paths.relativeFilePath);
|
|
23448
23698
|
const newJson = {
|
|
23449
23699
|
...json,
|
|
23450
23700
|
mcpServers: json.mcpServers ?? {}
|
|
23451
23701
|
};
|
|
23702
|
+
const disabledNames = disabledNamesOf(await readRovodevConfigYaml({ outputRoot }));
|
|
23703
|
+
if (disabledNames.length > 0 && isMcpServers(newJson.mcpServers)) {
|
|
23704
|
+
const servers = newJson.mcpServers;
|
|
23705
|
+
for (const name of disabledNames) {
|
|
23706
|
+
if (!Object.hasOwn(servers, name)) continue;
|
|
23707
|
+
const server = servers[name];
|
|
23708
|
+
if (isPlainObject$1(server)) servers[name] = {
|
|
23709
|
+
...server,
|
|
23710
|
+
disabled: true
|
|
23711
|
+
};
|
|
23712
|
+
}
|
|
23713
|
+
}
|
|
23452
23714
|
return new RovodevMcp({
|
|
23453
23715
|
outputRoot,
|
|
23454
23716
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -23459,11 +23721,21 @@ var RovodevMcp = class RovodevMcp extends ToolMcp {
|
|
|
23459
23721
|
});
|
|
23460
23722
|
}
|
|
23461
23723
|
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false, logger }) {
|
|
23462
|
-
if (!global) throw new Error("Rovodev MCP is global-only; use --global to sync ~/.rovodev/mcp.json");
|
|
23463
23724
|
const paths = this.getSettablePaths({ global });
|
|
23464
23725
|
const json = parseRovodevMcpJson(await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? JSON.stringify({ mcpServers: {} }, null, 2), paths.relativeDirPath, paths.relativeFilePath);
|
|
23726
|
+
let canWriteDisableToggle = true;
|
|
23727
|
+
try {
|
|
23728
|
+
await readRovodevConfigYaml({ outputRoot });
|
|
23729
|
+
} catch {
|
|
23730
|
+
canWriteDisableToggle = false;
|
|
23731
|
+
}
|
|
23465
23732
|
const mcpServers = Object.fromEntries(Object.entries(rulesyncMcp.getMcpServers()).map(([name, server]) => {
|
|
23466
|
-
const
|
|
23733
|
+
const record = server;
|
|
23734
|
+
if (record.disabled === true && !canWriteDisableToggle) {
|
|
23735
|
+
logger?.warn(`Rovo Dev MCP: skipping disabled server "${name}" because config.yml cannot be parsed, so mcp.disabledMcpServers cannot be written to switch it off.`);
|
|
23736
|
+
return null;
|
|
23737
|
+
}
|
|
23738
|
+
const converted = toRovodevServer(name, record, logger);
|
|
23467
23739
|
return converted === null ? null : [name, converted];
|
|
23468
23740
|
}).filter((entry) => entry !== null));
|
|
23469
23741
|
const rovodevConfig = {
|
|
@@ -23479,6 +23751,64 @@ var RovodevMcp = class RovodevMcp extends ToolMcp {
|
|
|
23479
23751
|
global
|
|
23480
23752
|
});
|
|
23481
23753
|
}
|
|
23754
|
+
/**
|
|
23755
|
+
* `mcp.disabledMcpServers` lives in the shared `config.yml` the permissions
|
|
23756
|
+
* feature also writes. Declared here so the write-order derivation sees this
|
|
23757
|
+
* feature as one of that file's writers — it is not a settable path, since
|
|
23758
|
+
* the servers themselves live in `mcp.json`.
|
|
23759
|
+
*/
|
|
23760
|
+
static getExtraSharedWritePaths() {
|
|
23761
|
+
return [{
|
|
23762
|
+
relativeDirPath: ROVODEV_DIR,
|
|
23763
|
+
relativeFilePath: ROVODEV_CONFIG_FILE_NAME
|
|
23764
|
+
}];
|
|
23765
|
+
}
|
|
23766
|
+
static async getAuxiliaryFiles({ outputRoot = process.cwd(), global = false, rulesyncMcp, logger }) {
|
|
23767
|
+
const servers = rulesyncMcp.forTarget({
|
|
23768
|
+
toolTarget: "rovodev",
|
|
23769
|
+
logger
|
|
23770
|
+
}).getMcpServers();
|
|
23771
|
+
const managedNames = Object.keys(servers).filter((name) => toRovodevServer(name, servers[name]) !== null);
|
|
23772
|
+
const disabledNames = managedNames.filter((name) => {
|
|
23773
|
+
const server = servers[name];
|
|
23774
|
+
return isRecord(server) && server.disabled === true;
|
|
23775
|
+
});
|
|
23776
|
+
const existingContent = await readFileContentOrNull((0, node_path.join)(outputRoot, ROVODEV_DIR, ROVODEV_CONFIG_FILE_NAME)) ?? "";
|
|
23777
|
+
let existingParsed;
|
|
23778
|
+
try {
|
|
23779
|
+
existingParsed = parseSharedConfig({
|
|
23780
|
+
format: "yaml",
|
|
23781
|
+
fileContent: existingContent,
|
|
23782
|
+
filePath: (0, node_path.join)(ROVODEV_DIR, ROVODEV_CONFIG_FILE_NAME)
|
|
23783
|
+
});
|
|
23784
|
+
} catch (error) {
|
|
23785
|
+
logger?.warn(`Skipping the Rovo Dev mcp.disabledMcpServers update: ${formatError(error)}`);
|
|
23786
|
+
return [];
|
|
23787
|
+
}
|
|
23788
|
+
const existingMcp = isRecord(existingParsed.mcp) ? { ...existingParsed.mcp } : {};
|
|
23789
|
+
const existingDisabled = isStringArray$1(existingMcp.disabledMcpServers) ? existingMcp.disabledMcpServers : [];
|
|
23790
|
+
const managedNameSet = new Set(managedNames);
|
|
23791
|
+
const reEnabled = existingDisabled.filter((name) => managedNameSet.has(name) && !disabledNames.includes(name));
|
|
23792
|
+
if (reEnabled.length > 0) logger?.warn(`Rovo Dev MCP: re-enabling ${reEnabled.join(", ")} — the canonical config does not mark ${reEnabled.length === 1 ? "it" : "them"} disabled. Set "disabled": true in .rulesync/mcp.jsonc to keep a managed server off.`);
|
|
23793
|
+
const mergedDisabled = [...existingDisabled.filter((name) => !managedNameSet.has(name)), ...disabledNames].toSorted();
|
|
23794
|
+
if (mergedDisabled.length > 0) existingMcp.disabledMcpServers = mergedDisabled;
|
|
23795
|
+
else delete existingMcp.disabledMcpServers;
|
|
23796
|
+
if (mergedDisabled.length === 0 && existingContent.trim() === "") return [];
|
|
23797
|
+
const fileContent = applySharedConfigPatch({
|
|
23798
|
+
fileKey: ROVODEV_CONFIG_SHARED_FILE_KEY,
|
|
23799
|
+
feature: "mcp",
|
|
23800
|
+
existingContent,
|
|
23801
|
+
patch: { mcp: Object.keys(existingMcp).length > 0 ? existingMcp : void 0 },
|
|
23802
|
+
filePath: (0, node_path.join)(ROVODEV_DIR, ROVODEV_CONFIG_FILE_NAME)
|
|
23803
|
+
});
|
|
23804
|
+
return [new RovodevMcpConfigYaml({
|
|
23805
|
+
outputRoot,
|
|
23806
|
+
relativeDirPath: ROVODEV_DIR,
|
|
23807
|
+
relativeFilePath: ROVODEV_CONFIG_FILE_NAME,
|
|
23808
|
+
fileContent,
|
|
23809
|
+
global
|
|
23810
|
+
})];
|
|
23811
|
+
}
|
|
23482
23812
|
toRulesyncMcp() {
|
|
23483
23813
|
const rawServers = isMcpServers(this.json.mcpServers) ? this.json.mcpServers : {};
|
|
23484
23814
|
const mcpServers = Object.fromEntries(Object.entries(rawServers).filter(([, server]) => isPlainObject$1(server)).map(([name, server]) => [name, fromRovodevServer(server)]));
|
|
@@ -23627,7 +23957,6 @@ function transportOf(server) {
|
|
|
23627
23957
|
case "http":
|
|
23628
23958
|
case "streamable-http":
|
|
23629
23959
|
case "ws": return "http";
|
|
23630
|
-
default: break;
|
|
23631
23960
|
}
|
|
23632
23961
|
if (server.command !== void 0) return "stdio";
|
|
23633
23962
|
if (server.url !== void 0 || server.httpUrl !== void 0) return "http";
|
|
@@ -23655,6 +23984,20 @@ function deriveTransportAllowlist(servers) {
|
|
|
23655
23984
|
return allowlist;
|
|
23656
23985
|
}
|
|
23657
23986
|
//#endregion
|
|
23987
|
+
//#region src/features/shared/vibe-config-scope.ts
|
|
23988
|
+
/**
|
|
23989
|
+
* Vibe selects exactly **one** persistence TOML and does not merge scopes: the
|
|
23990
|
+
* trusted project `.vibe/config.toml` when one is discovered, otherwise
|
|
23991
|
+
* `~/.vibe/config.toml` (single code path since v2.22.0's ConfigOrchestrator
|
|
23992
|
+
* migration). A `--global` run that writes the home file is therefore inert in
|
|
23993
|
+
* any project that has its own config — worth a heads-up, since the other Vibe
|
|
23994
|
+
* surfaces (rules, hooks, agents, skills) genuinely combine scopes.
|
|
23995
|
+
*/
|
|
23996
|
+
async function warnIfGlobalVibeConfigIsShadowed(logger) {
|
|
23997
|
+
if (!await fileExists((0, node_path.join)(process.cwd(), ".vibe", "config.toml"))) return;
|
|
23998
|
+
logger?.warn("Vibe reads exactly one config.toml (project .vibe/config.toml when present, otherwise ~/.vibe/config.toml — a fallback, not a merge). This project has .vibe/config.toml, so the global file written by --global is ignored here.");
|
|
23999
|
+
}
|
|
24000
|
+
//#endregion
|
|
23658
24001
|
//#region src/features/mcp/vibe-mcp.ts
|
|
23659
24002
|
const VIBE_MCP_SERVER_FIELDS = [
|
|
23660
24003
|
"transport",
|
|
@@ -23723,8 +24066,9 @@ var VibeMcp = class VibeMcp extends ToolMcp {
|
|
|
23723
24066
|
global
|
|
23724
24067
|
});
|
|
23725
24068
|
}
|
|
23726
|
-
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false }) {
|
|
24069
|
+
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, logger, global = false }) {
|
|
23727
24070
|
const paths = this.getSettablePaths({ global });
|
|
24071
|
+
if (global) await warnIfGlobalVibeConfigIsShadowed(logger);
|
|
23728
24072
|
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
23729
24073
|
const existingContent = await readFileContentOrNull(filePath) ?? "";
|
|
23730
24074
|
const existingServers = normalizeMcpServersArray(parseSharedConfig({
|
|
@@ -23733,7 +24077,10 @@ var VibeMcp = class VibeMcp extends ToolMcp {
|
|
|
23733
24077
|
filePath
|
|
23734
24078
|
}).mcp_servers);
|
|
23735
24079
|
const existingByName = new Map(existingServers.map((server) => [server.name, server]));
|
|
23736
|
-
const
|
|
24080
|
+
const rulesyncServers = rulesyncMcp.getMcpServers();
|
|
24081
|
+
const managedNames = new Set(Object.keys(rulesyncServers));
|
|
24082
|
+
const mcpServers = Object.entries(rulesyncServers).map(([name, server]) => rulesyncMcpServerToVibe(name, server, existingByName.get(name)));
|
|
24083
|
+
const unmanagedServers = existingServers.filter((server) => !managedNames.has(server.name));
|
|
23737
24084
|
return new VibeMcp({
|
|
23738
24085
|
outputRoot,
|
|
23739
24086
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -23742,7 +24089,7 @@ var VibeMcp = class VibeMcp extends ToolMcp {
|
|
|
23742
24089
|
fileKey: sharedConfigFileKey(paths),
|
|
23743
24090
|
feature: "mcp",
|
|
23744
24091
|
existingContent,
|
|
23745
|
-
patch: { mcp_servers: mcpServers },
|
|
24092
|
+
patch: { mcp_servers: [...mcpServers, ...unmanagedServers] },
|
|
23746
24093
|
filePath
|
|
23747
24094
|
}),
|
|
23748
24095
|
validate,
|
|
@@ -24444,10 +24791,19 @@ const toolMcpFactories = /* @__PURE__ */ new Map([
|
|
|
24444
24791
|
supportsDisabledTools: false
|
|
24445
24792
|
}
|
|
24446
24793
|
}],
|
|
24794
|
+
["zoocode", {
|
|
24795
|
+
class: RooMcp,
|
|
24796
|
+
meta: {
|
|
24797
|
+
supportsProject: true,
|
|
24798
|
+
supportsGlobal: false,
|
|
24799
|
+
supportsEnabledTools: false,
|
|
24800
|
+
supportsDisabledTools: false
|
|
24801
|
+
}
|
|
24802
|
+
}],
|
|
24447
24803
|
["rovodev", {
|
|
24448
24804
|
class: RovodevMcp,
|
|
24449
24805
|
meta: {
|
|
24450
|
-
supportsProject:
|
|
24806
|
+
supportsProject: true,
|
|
24451
24807
|
supportsGlobal: true,
|
|
24452
24808
|
supportsEnabledTools: false,
|
|
24453
24809
|
supportsDisabledTools: false
|
|
@@ -25313,9 +25669,7 @@ function convertRulesyncToAntigravityCliPermissions(config) {
|
|
|
25313
25669
|
case "ask":
|
|
25314
25670
|
ask.push(entry);
|
|
25315
25671
|
break;
|
|
25316
|
-
case "deny":
|
|
25317
|
-
deny.push(entry);
|
|
25318
|
-
break;
|
|
25672
|
+
case "deny": deny.push(entry);
|
|
25319
25673
|
}
|
|
25320
25674
|
}
|
|
25321
25675
|
}
|
|
@@ -25526,9 +25880,7 @@ function convertRulesyncToAntigravityIdePermissions(config) {
|
|
|
25526
25880
|
case "ask":
|
|
25527
25881
|
ask.push(entry);
|
|
25528
25882
|
break;
|
|
25529
|
-
case "deny":
|
|
25530
|
-
deny.push(entry);
|
|
25531
|
-
break;
|
|
25883
|
+
case "deny": deny.push(entry);
|
|
25532
25884
|
}
|
|
25533
25885
|
}
|
|
25534
25886
|
}
|
|
@@ -26260,9 +26612,7 @@ function convertRulesyncToClaudePermissions({ config, logger }) {
|
|
|
26260
26612
|
case "ask":
|
|
26261
26613
|
ask.push(entry);
|
|
26262
26614
|
break;
|
|
26263
|
-
case "deny":
|
|
26264
|
-
deny.push(entry);
|
|
26265
|
-
break;
|
|
26615
|
+
case "deny": deny.push(entry);
|
|
26266
26616
|
}
|
|
26267
26617
|
}
|
|
26268
26618
|
}
|
|
@@ -27512,9 +27862,7 @@ function convertRulesyncToCursorPermissions(config, logger) {
|
|
|
27512
27862
|
case "ask":
|
|
27513
27863
|
logger?.warn(`Cursor CLI permissions do not support the "ask" action. Skipping ${category} rule: ${pattern}`);
|
|
27514
27864
|
break;
|
|
27515
|
-
case "deny":
|
|
27516
|
-
deny.push(entry);
|
|
27517
|
-
break;
|
|
27865
|
+
case "deny": deny.push(entry);
|
|
27518
27866
|
}
|
|
27519
27867
|
}
|
|
27520
27868
|
}
|
|
@@ -27751,9 +28099,7 @@ function convertRulesyncToDevinPermissions(config) {
|
|
|
27751
28099
|
case "ask":
|
|
27752
28100
|
ask.push(entry);
|
|
27753
28101
|
break;
|
|
27754
|
-
case "deny":
|
|
27755
|
-
deny.push(entry);
|
|
27756
|
-
break;
|
|
28102
|
+
case "deny": deny.push(entry);
|
|
27757
28103
|
}
|
|
27758
28104
|
}
|
|
27759
28105
|
}
|
|
@@ -27941,10 +28287,7 @@ function convertRulesyncToFactorydroidPermissions({ config, logger }) {
|
|
|
27941
28287
|
case "allow":
|
|
27942
28288
|
allow.push(pattern);
|
|
27943
28289
|
break;
|
|
27944
|
-
case "deny":
|
|
27945
|
-
deny.push(pattern);
|
|
27946
|
-
break;
|
|
27947
|
-
case "ask": break;
|
|
28290
|
+
case "deny": deny.push(pattern);
|
|
27948
28291
|
}
|
|
27949
28292
|
}
|
|
27950
28293
|
return {
|
|
@@ -28063,10 +28406,11 @@ var GoosePermissions = class GoosePermissions extends ToolPermissions {
|
|
|
28063
28406
|
throw new Error(`Failed to parse existing Goose permission.yaml at ${filePath}: ${formatError(error)}`, { cause: error });
|
|
28064
28407
|
}
|
|
28065
28408
|
const config = isRecord(parsed) ? { ...parsed } : {};
|
|
28066
|
-
|
|
28409
|
+
const userPermission = convertRulesyncToGoosePermissionConfig({
|
|
28067
28410
|
config: rulesyncPermissions.getJson(),
|
|
28068
28411
|
logger
|
|
28069
28412
|
});
|
|
28413
|
+
config[GOOSE_USER_KEY] = userPermission;
|
|
28070
28414
|
return new GoosePermissions({
|
|
28071
28415
|
outputRoot,
|
|
28072
28416
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -30284,9 +30628,7 @@ function convertRulesyncToQwenPermissions(config) {
|
|
|
30284
30628
|
case "ask":
|
|
30285
30629
|
ask.push(entry);
|
|
30286
30630
|
break;
|
|
30287
|
-
case "deny":
|
|
30288
|
-
deny.push(entry);
|
|
30289
|
-
break;
|
|
30631
|
+
case "deny": deny.push(entry);
|
|
30290
30632
|
}
|
|
30291
30633
|
}
|
|
30292
30634
|
}
|
|
@@ -30359,6 +30701,20 @@ function toCanonicalToolName$2(reasonixName) {
|
|
|
30359
30701
|
return REASONIX_TO_CANONICAL_TOOL_NAMES[reasonixName] ?? reasonixName;
|
|
30360
30702
|
}
|
|
30361
30703
|
/**
|
|
30704
|
+
* Whether an entry uses the first-class `Tool=<literal>` exact-command form
|
|
30705
|
+
* (SPEC §3.7, v1.18.0): metacharacters in the literal are ordinary characters
|
|
30706
|
+
* and only the identical complete command matches. These entries have no
|
|
30707
|
+
* canonical tool→pattern→action equivalent — glob-style `Tool(...)` matches
|
|
30708
|
+
* differently by design — so they are preserved verbatim and round-trip
|
|
30709
|
+
* through the `reasonix` override's rawAllow/rawAsk/rawDeny arrays.
|
|
30710
|
+
*/
|
|
30711
|
+
function isReasonixExactCommandEntry(entry) {
|
|
30712
|
+
const eqIndex = entry.indexOf("=");
|
|
30713
|
+
if (eqIndex <= 0) return false;
|
|
30714
|
+
const parenIndex = entry.indexOf("(");
|
|
30715
|
+
return parenIndex === -1 || eqIndex < parenIndex;
|
|
30716
|
+
}
|
|
30717
|
+
/**
|
|
30362
30718
|
* Parse a Reasonix permission entry like "Bash(npm run *)" into tool name and pattern.
|
|
30363
30719
|
* If no parentheses, returns the tool name with "*" as the pattern.
|
|
30364
30720
|
*/
|
|
@@ -30447,10 +30803,22 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
30447
30803
|
const config = rulesyncPermissions.getJson();
|
|
30448
30804
|
const { allow, ask, deny } = convertRulesyncToReasonixPermissions(config);
|
|
30449
30805
|
const managedToolNames = new Set(Object.keys(config.permission).map((category) => toReasonixToolName(category)));
|
|
30806
|
+
const override = config.reasonix;
|
|
30807
|
+
const rawAllow = toReasonixStringArray(override?.rawAllow);
|
|
30808
|
+
const rawAsk = toReasonixStringArray(override?.rawAsk);
|
|
30809
|
+
const rawDeny = toReasonixStringArray(override?.rawDeny);
|
|
30810
|
+
const rawOwnedEntries = /* @__PURE__ */ new Set([
|
|
30811
|
+
...rawAllow,
|
|
30812
|
+
...rawAsk,
|
|
30813
|
+
...rawDeny
|
|
30814
|
+
]);
|
|
30450
30815
|
const existingPermissions = toReasonixTable(parsed.permissions);
|
|
30451
|
-
const preservedAllow
|
|
30452
|
-
|
|
30453
|
-
|
|
30816
|
+
const { allow: preservedAllow, ask: preservedAsk, deny: preservedDeny } = preserveExistingEntries({
|
|
30817
|
+
existingPermissions,
|
|
30818
|
+
managedToolNames,
|
|
30819
|
+
rawOwnedEntries,
|
|
30820
|
+
logger
|
|
30821
|
+
});
|
|
30454
30822
|
if (logger && managedToolNames.has("Read")) {
|
|
30455
30823
|
const droppedReadDenyEntries = toReasonixStringArray(existingPermissions.deny).filter((entry) => {
|
|
30456
30824
|
const { toolName } = parseReasonixPermissionEntry(entry);
|
|
@@ -30459,17 +30827,22 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
30459
30827
|
if (droppedReadDenyEntries.length > 0) logger.warn(`Permissions feature manages 'Read' tool and will overwrite ${droppedReadDenyEntries.length} existing Read deny entries (possibly from ignore feature). Permissions take precedence.`);
|
|
30460
30828
|
}
|
|
30461
30829
|
const mergedPermissions = { ...existingPermissions };
|
|
30462
|
-
|
|
30463
|
-
|
|
30464
|
-
|
|
30465
|
-
|
|
30466
|
-
|
|
30467
|
-
|
|
30468
|
-
|
|
30469
|
-
|
|
30470
|
-
|
|
30830
|
+
setOrDeleteEntries(mergedPermissions, "allow", [
|
|
30831
|
+
...preservedAllow,
|
|
30832
|
+
...allow,
|
|
30833
|
+
...rawAllow
|
|
30834
|
+
]);
|
|
30835
|
+
setOrDeleteEntries(mergedPermissions, "ask", [
|
|
30836
|
+
...preservedAsk,
|
|
30837
|
+
...ask,
|
|
30838
|
+
...rawAsk
|
|
30839
|
+
]);
|
|
30840
|
+
setOrDeleteEntries(mergedPermissions, "deny", [
|
|
30841
|
+
...preservedDeny,
|
|
30842
|
+
...deny,
|
|
30843
|
+
...rawDeny
|
|
30844
|
+
]);
|
|
30471
30845
|
const patch = { permissions: mergedPermissions };
|
|
30472
|
-
const override = config.reasonix;
|
|
30473
30846
|
if (override?.sandbox !== void 0) patch.sandbox = {
|
|
30474
30847
|
...asReasonixRecord(parsed.sandbox),
|
|
30475
30848
|
...asReasonixRecord(override.sandbox)
|
|
@@ -30500,16 +30873,29 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
30500
30873
|
}
|
|
30501
30874
|
toRulesyncPermissions() {
|
|
30502
30875
|
const permissions = toReasonixTable(this.toml.permissions);
|
|
30876
|
+
const splitExact = (entries) => {
|
|
30877
|
+
const exact = entries.filter((entry) => isReasonixExactCommandEntry(entry));
|
|
30878
|
+
return {
|
|
30879
|
+
translated: entries.filter((entry) => !isReasonixExactCommandEntry(entry)),
|
|
30880
|
+
exact
|
|
30881
|
+
};
|
|
30882
|
+
};
|
|
30883
|
+
const allowSplit = splitExact(toReasonixStringArray(permissions.allow));
|
|
30884
|
+
const askSplit = splitExact(toReasonixStringArray(permissions.ask));
|
|
30885
|
+
const denySplit = splitExact(toReasonixStringArray(permissions.deny));
|
|
30503
30886
|
const config = convertReasonixToRulesyncPermissions({
|
|
30504
|
-
allow:
|
|
30505
|
-
ask:
|
|
30506
|
-
deny:
|
|
30887
|
+
allow: allowSplit.translated,
|
|
30888
|
+
ask: askSplit.translated,
|
|
30889
|
+
deny: denySplit.translated
|
|
30507
30890
|
});
|
|
30508
30891
|
const sandbox = asReasonixRecord(this.toml.sandbox);
|
|
30509
30892
|
const agentPlanMode = pickReasonixKeys(this.toml.agent, [...REASONIX_OVERRIDE_AGENT_KEYS, ...REASONIX_RETIRED_AGENT_KEYS]);
|
|
30510
30893
|
const reasonixOverride = {};
|
|
30511
30894
|
if (Object.keys(sandbox).length > 0) reasonixOverride.sandbox = sandbox;
|
|
30512
30895
|
if (Object.keys(agentPlanMode).length > 0) reasonixOverride.agent = agentPlanMode;
|
|
30896
|
+
if (allowSplit.exact.length > 0) reasonixOverride.rawAllow = allowSplit.exact;
|
|
30897
|
+
if (askSplit.exact.length > 0) reasonixOverride.rawAsk = askSplit.exact;
|
|
30898
|
+
if (denySplit.exact.length > 0) reasonixOverride.rawDeny = denySplit.exact;
|
|
30513
30899
|
const result = { ...config };
|
|
30514
30900
|
if (Object.keys(reasonixOverride).length > 0) result.reasonix = reasonixOverride;
|
|
30515
30901
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
|
|
@@ -30539,6 +30925,41 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
30539
30925
|
}
|
|
30540
30926
|
};
|
|
30541
30927
|
/**
|
|
30928
|
+
* Preserve existing permission entries rulesync does not manage: entries for
|
|
30929
|
+
* tools NOT in the permissions config, plus exact `Tool=<literal>` entries not
|
|
30930
|
+
* owned by the override's raw arrays — Reasonix writes those itself as
|
|
30931
|
+
* remembered approvals, and dropping them would revoke grants the canonical
|
|
30932
|
+
* glob rules cannot re-express. Remove one by deleting it from `reasonix.toml`
|
|
30933
|
+
* (or by naming it in a raw array, which takes ownership). Preserved exact
|
|
30934
|
+
* entries are logged so the kept-alive grants stay visible.
|
|
30935
|
+
*/
|
|
30936
|
+
function preserveExistingEntries({ existingPermissions, managedToolNames, rawOwnedEntries, logger }) {
|
|
30937
|
+
const isPreserved = (entry) => {
|
|
30938
|
+
if (isReasonixExactCommandEntry(entry)) return !rawOwnedEntries.has(entry);
|
|
30939
|
+
return !managedToolNames.has(parseReasonixPermissionEntry(entry).toolName);
|
|
30940
|
+
};
|
|
30941
|
+
const allow = toReasonixStringArray(existingPermissions.allow).filter(isPreserved);
|
|
30942
|
+
const ask = toReasonixStringArray(existingPermissions.ask).filter(isPreserved);
|
|
30943
|
+
const deny = toReasonixStringArray(existingPermissions.deny).filter(isPreserved);
|
|
30944
|
+
const preservedExact = [
|
|
30945
|
+
...allow,
|
|
30946
|
+
...ask,
|
|
30947
|
+
...deny
|
|
30948
|
+
].filter((entry) => isReasonixExactCommandEntry(entry));
|
|
30949
|
+
if (preservedExact.length > 0) logger?.info(`Preserving ${preservedExact.length} exact Reasonix permission entries (remembered approvals): ${preservedExact.join(", ")}`);
|
|
30950
|
+
return {
|
|
30951
|
+
allow,
|
|
30952
|
+
ask,
|
|
30953
|
+
deny
|
|
30954
|
+
};
|
|
30955
|
+
}
|
|
30956
|
+
/** Sort, dedupe and set a permissions array key, deleting it when empty. */
|
|
30957
|
+
function setOrDeleteEntries(table, key, entries) {
|
|
30958
|
+
const merged = (0, es_toolkit.uniq)(entries.toSorted());
|
|
30959
|
+
if (merged.length > 0) table[key] = merged;
|
|
30960
|
+
else delete table[key];
|
|
30961
|
+
}
|
|
30962
|
+
/**
|
|
30542
30963
|
* Convert rulesync permissions config to Reasonix allow/ask/deny arrays.
|
|
30543
30964
|
*/
|
|
30544
30965
|
function convertRulesyncToReasonixPermissions(config) {
|
|
@@ -30556,9 +30977,7 @@ function convertRulesyncToReasonixPermissions(config) {
|
|
|
30556
30977
|
case "ask":
|
|
30557
30978
|
ask.push(entry);
|
|
30558
30979
|
break;
|
|
30559
|
-
case "deny":
|
|
30560
|
-
deny.push(entry);
|
|
30561
|
-
break;
|
|
30980
|
+
case "deny": deny.push(entry);
|
|
30562
30981
|
}
|
|
30563
30982
|
}
|
|
30564
30983
|
}
|
|
@@ -30588,7 +31007,6 @@ function convertReasonixToRulesyncPermissions(params) {
|
|
|
30588
31007
|
}
|
|
30589
31008
|
//#endregion
|
|
30590
31009
|
//#region src/features/permissions/rovodev-permissions.ts
|
|
30591
|
-
const ROVODEV_GLOBAL_ONLY_MESSAGE = "Rovodev permissions are global-only; use --global to sync ~/.rovodev/config.yml";
|
|
30592
31010
|
const CATCH_ALL_PATTERN$1 = "*";
|
|
30593
31011
|
const CATEGORY_TO_TOOL_KEYS = {
|
|
30594
31012
|
read: [
|
|
@@ -30648,9 +31066,12 @@ const OWNED_TOOL_PERMISSION_KEYS = [
|
|
|
30648
31066
|
/**
|
|
30649
31067
|
* Permissions adapter for Rovo Dev CLI.
|
|
30650
31068
|
*
|
|
30651
|
-
* Rovo Dev reads tool permissions from the `toolPermissions` block of
|
|
30652
|
-
*
|
|
30653
|
-
*
|
|
31069
|
+
* Rovo Dev reads tool permissions from the `toolPermissions` block of
|
|
31070
|
+
* `config.yml` — the global `~/.rovodev/config.yml`, and since the Bitbucket
|
|
31071
|
+
* Cloud Agentic Pipelines docs, also the repo-committed project
|
|
31072
|
+
* `.rovodev/config.yml` (referenced from `bitbucket-pipelines.yml` via
|
|
31073
|
+
* `config.path`, or the `--config-file` CLI flag). Both scopes use the
|
|
31074
|
+
* same relative path, resolved against the project root or home directory.
|
|
30654
31075
|
*
|
|
30655
31076
|
* Rovo Dev's three levels (`allow`/`ask`/`deny`) are an exact 1:1 with rulesync's
|
|
30656
31077
|
* canonical action enum, so action values pass through verbatim.
|
|
@@ -30697,7 +31118,6 @@ var RovodevPermissions = class RovodevPermissions extends ToolPermissions {
|
|
|
30697
31118
|
};
|
|
30698
31119
|
}
|
|
30699
31120
|
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
30700
|
-
if (!global) throw new Error(ROVODEV_GLOBAL_ONLY_MESSAGE);
|
|
30701
31121
|
const paths = RovodevPermissions.getSettablePaths({ global });
|
|
30702
31122
|
const fileContent = await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "";
|
|
30703
31123
|
return new RovodevPermissions({
|
|
@@ -30706,11 +31126,10 @@ var RovodevPermissions = class RovodevPermissions extends ToolPermissions {
|
|
|
30706
31126
|
relativeFilePath: paths.relativeFilePath,
|
|
30707
31127
|
fileContent,
|
|
30708
31128
|
validate,
|
|
30709
|
-
global
|
|
31129
|
+
global
|
|
30710
31130
|
});
|
|
30711
31131
|
}
|
|
30712
31132
|
static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, logger, global = false }) {
|
|
30713
|
-
if (!global) throw new Error(ROVODEV_GLOBAL_ONLY_MESSAGE);
|
|
30714
31133
|
const paths = RovodevPermissions.getSettablePaths({ global });
|
|
30715
31134
|
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
30716
31135
|
const existingContent = await readFileContentOrNull(filePath) ?? "";
|
|
@@ -30733,14 +31152,20 @@ var RovodevPermissions = class RovodevPermissions extends ToolPermissions {
|
|
|
30733
31152
|
filePath,
|
|
30734
31153
|
logger
|
|
30735
31154
|
});
|
|
30736
|
-
|
|
31155
|
+
const fileContent = resolvedToolPermissions !== void 0 ? applySharedConfigPatch({
|
|
31156
|
+
fileKey: ROVODEV_CONFIG_SHARED_FILE_KEY,
|
|
31157
|
+
feature: "permissions",
|
|
31158
|
+
existingContent,
|
|
31159
|
+
patch: { toolPermissions: resolvedToolPermissions },
|
|
31160
|
+
filePath
|
|
31161
|
+
}) : (0, js_yaml.dump)(config);
|
|
30737
31162
|
return new RovodevPermissions({
|
|
30738
31163
|
outputRoot,
|
|
30739
31164
|
relativeDirPath: paths.relativeDirPath,
|
|
30740
31165
|
relativeFilePath: paths.relativeFilePath,
|
|
30741
|
-
fileContent
|
|
31166
|
+
fileContent,
|
|
30742
31167
|
validate: true,
|
|
30743
|
-
global
|
|
31168
|
+
global
|
|
30744
31169
|
});
|
|
30745
31170
|
}
|
|
30746
31171
|
toRulesyncPermissions() {
|
|
@@ -31277,6 +31702,7 @@ const CANONICAL_TO_VIBE_TOOL_NAMES = {
|
|
|
31277
31702
|
write: "write_file",
|
|
31278
31703
|
webfetch: "web_fetch",
|
|
31279
31704
|
websearch: "web_search",
|
|
31705
|
+
grep: "grep",
|
|
31280
31706
|
agent: "task"
|
|
31281
31707
|
};
|
|
31282
31708
|
const VIBE_TO_CANONICAL_TOOL_NAMES = {
|
|
@@ -31286,6 +31712,7 @@ const VIBE_TO_CANONICAL_TOOL_NAMES = {
|
|
|
31286
31712
|
write_file: "write",
|
|
31287
31713
|
web_fetch: "webfetch",
|
|
31288
31714
|
web_search: "websearch",
|
|
31715
|
+
grep: "grep",
|
|
31289
31716
|
task: "agent"
|
|
31290
31717
|
};
|
|
31291
31718
|
var VibePermissions = class VibePermissions extends ToolPermissions {
|
|
@@ -31323,6 +31750,7 @@ var VibePermissions = class VibePermissions extends ToolPermissions {
|
|
|
31323
31750
|
}
|
|
31324
31751
|
static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, validate = true, logger, global = false }) {
|
|
31325
31752
|
const paths = this.getSettablePaths({ global });
|
|
31753
|
+
if (global) await warnIfGlobalVibeConfigIsShadowed(logger);
|
|
31326
31754
|
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
31327
31755
|
const existingContent = await readFileContentOrNull(filePath) ?? "";
|
|
31328
31756
|
const config = parseVibeConfig(existingContent);
|
|
@@ -31330,46 +31758,31 @@ var VibePermissions = class VibePermissions extends ToolPermissions {
|
|
|
31330
31758
|
const tools = toVibeToolsRecord(config.tools);
|
|
31331
31759
|
const enabledTools = new Set(toStringArray(config.enabled_tools));
|
|
31332
31760
|
const disabledTools = new Set(toStringArray(config.disabled_tools));
|
|
31761
|
+
const removedEnabledTools = [];
|
|
31333
31762
|
for (const category of Object.keys(permission)) {
|
|
31763
|
+
if (!hasVibeToolName(category)) continue;
|
|
31334
31764
|
const vibeToolName = toVibeToolName(category);
|
|
31335
|
-
enabledTools.delete(vibeToolName);
|
|
31765
|
+
if (enabledTools.delete(vibeToolName)) removedEnabledTools.push(vibeToolName);
|
|
31336
31766
|
disabledTools.delete(vibeToolName);
|
|
31337
31767
|
}
|
|
31338
31768
|
for (const [category, rules] of Object.entries(permission)) {
|
|
31339
|
-
|
|
31340
|
-
|
|
31341
|
-
|
|
31342
|
-
|
|
31343
|
-
const deny = new Set(toStringArray(existingTool.deny ?? existingTool.denylist));
|
|
31344
|
-
for (const [pattern, action] of Object.entries(rules)) {
|
|
31345
|
-
if (pattern === "*") {
|
|
31346
|
-
applyWildcardPermission({
|
|
31347
|
-
action,
|
|
31348
|
-
toolConfig: nextTool
|
|
31349
|
-
});
|
|
31350
|
-
if (action === "deny") {
|
|
31351
|
-
disabledTools.add(vibeToolName);
|
|
31352
|
-
enabledTools.delete(vibeToolName);
|
|
31353
|
-
} else if (action === "allow") {
|
|
31354
|
-
enabledTools.add(vibeToolName);
|
|
31355
|
-
disabledTools.delete(vibeToolName);
|
|
31356
|
-
}
|
|
31357
|
-
continue;
|
|
31358
|
-
}
|
|
31359
|
-
if (action === "ask") {
|
|
31360
|
-
logger?.warn(`Vibe permissions do not support pattern-level "ask" rules. Skipping ${category}: ${pattern}`);
|
|
31361
|
-
continue;
|
|
31362
|
-
}
|
|
31363
|
-
if (action === "allow") allow.add(pattern);
|
|
31364
|
-
else deny.add(pattern);
|
|
31769
|
+
if (!hasVibeToolName(category)) {
|
|
31770
|
+
const ruleCount = Object.keys(rules).length;
|
|
31771
|
+
if (ruleCount > 0) logger?.warn(`Vibe has no builtin tool for the '${category}' category; skipping ${ruleCount} rule(s) instead of emitting an inert [tools.${category}] table.`);
|
|
31772
|
+
continue;
|
|
31365
31773
|
}
|
|
31366
|
-
|
|
31367
|
-
|
|
31368
|
-
|
|
31369
|
-
|
|
31370
|
-
|
|
31774
|
+
applyCategoryRules({
|
|
31775
|
+
category,
|
|
31776
|
+
rules,
|
|
31777
|
+
tools,
|
|
31778
|
+
enabledTools,
|
|
31779
|
+
disabledTools,
|
|
31780
|
+
logger
|
|
31781
|
+
});
|
|
31371
31782
|
}
|
|
31372
|
-
|
|
31783
|
+
const vibeOverride = rulesyncPermissions.getJson().vibe;
|
|
31784
|
+
applyVibeSensitivePatterns(tools, vibeOverride, logger);
|
|
31785
|
+
if (removedEnabledTools.length > 0 && vibeOverride?.enabled_tools === void 0 && logger) logger.warn(`Removed ${removedEnabledTools.join(", ")} from Vibe's exclusive enabled_tools list (rulesync owns the tools it configures and no longer expresses allows through that key). If the exclusive narrowing was intentional, declare the full list explicitly via vibe.enabled_tools in .rulesync/permissions.jsonc.`);
|
|
31373
31786
|
return new VibePermissions({
|
|
31374
31787
|
outputRoot,
|
|
31375
31788
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -31380,7 +31793,10 @@ var VibePermissions = class VibePermissions extends ToolPermissions {
|
|
|
31380
31793
|
existingContent,
|
|
31381
31794
|
patch: {
|
|
31382
31795
|
tools,
|
|
31383
|
-
enabled_tools:
|
|
31796
|
+
enabled_tools: resolveEnabledToolsPatch({
|
|
31797
|
+
vibeOverride,
|
|
31798
|
+
enabledTools
|
|
31799
|
+
}),
|
|
31384
31800
|
disabled_tools: disabledTools.size > 0 ? [...disabledTools].toSorted() : void 0
|
|
31385
31801
|
},
|
|
31386
31802
|
filePath
|
|
@@ -31392,7 +31808,7 @@ var VibePermissions = class VibePermissions extends ToolPermissions {
|
|
|
31392
31808
|
toRulesyncPermissions() {
|
|
31393
31809
|
const permission = {};
|
|
31394
31810
|
const vibeOverridePermission = {};
|
|
31395
|
-
|
|
31811
|
+
const enabledToolsList = toStringArray(this.toml.enabled_tools);
|
|
31396
31812
|
for (const tool of toStringArray(this.toml.disabled_tools)) ensurePermission(permission, toCanonicalToolName$1(tool))["*"] = "deny";
|
|
31397
31813
|
for (const [vibeToolName, toolConfig] of Object.entries(toVibeToolsRecord(this.toml.tools))) {
|
|
31398
31814
|
const category = toCanonicalToolName$1(vibeToolName);
|
|
@@ -31405,9 +31821,12 @@ var VibePermissions = class VibePermissions extends ToolPermissions {
|
|
|
31405
31821
|
if (sensitivePatterns.length > 0) vibeOverridePermission[category] = { sensitive_patterns: sensitivePatterns };
|
|
31406
31822
|
}
|
|
31407
31823
|
for (const [category, rules] of Object.entries(permission)) if (Object.keys(rules).length === 0) delete permission[category];
|
|
31408
|
-
const
|
|
31824
|
+
const vibeOverride = {};
|
|
31825
|
+
if (Object.keys(vibeOverridePermission).length > 0) vibeOverride.permission = vibeOverridePermission;
|
|
31826
|
+
if (enabledToolsList.length > 0) vibeOverride.enabled_tools = enabledToolsList;
|
|
31827
|
+
const json = Object.keys(vibeOverride).length > 0 ? {
|
|
31409
31828
|
permission,
|
|
31410
|
-
vibe:
|
|
31829
|
+
vibe: vibeOverride
|
|
31411
31830
|
} : { permission };
|
|
31412
31831
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(json, null, 2) });
|
|
31413
31832
|
}
|
|
@@ -31442,8 +31861,12 @@ var VibePermissions = class VibePermissions extends ToolPermissions {
|
|
|
31442
31861
|
* list for any category the override names: a present list is set, an empty
|
|
31443
31862
|
* one clears it. Categories not named keep whatever the existing file had.
|
|
31444
31863
|
*/
|
|
31445
|
-
function applyVibeSensitivePatterns(tools, vibeOverride) {
|
|
31864
|
+
function applyVibeSensitivePatterns(tools, vibeOverride, logger) {
|
|
31446
31865
|
for (const [category, toolOverride] of Object.entries(vibeOverride?.permission ?? {})) {
|
|
31866
|
+
if (!hasVibeToolName(category)) {
|
|
31867
|
+
logger?.warn(`Vibe has no builtin tool for the '${category}' category; skipping its vibe.permission.${category}.sensitive_patterns override.`);
|
|
31868
|
+
continue;
|
|
31869
|
+
}
|
|
31447
31870
|
const vibeToolName = toVibeToolName(category);
|
|
31448
31871
|
const nextTool = toVibeToolConfig(tools[vibeToolName]);
|
|
31449
31872
|
const patterns = toStringArray(toolOverride.sensitive_patterns);
|
|
@@ -31452,6 +31875,57 @@ function applyVibeSensitivePatterns(tools, vibeOverride) {
|
|
|
31452
31875
|
tools[vibeToolName] = nextTool;
|
|
31453
31876
|
}
|
|
31454
31877
|
}
|
|
31878
|
+
/**
|
|
31879
|
+
* Translate one canonical category's rules into the tool's `[tools.<name>]`
|
|
31880
|
+
* table and the top-level `disabled_tools` filter, mutating `tools` and the
|
|
31881
|
+
* filter sets in place.
|
|
31882
|
+
*/
|
|
31883
|
+
function applyCategoryRules({ category, rules, tools, enabledTools, disabledTools, logger }) {
|
|
31884
|
+
const vibeToolName = toVibeToolName(category);
|
|
31885
|
+
const existingTool = toVibeToolConfig(tools[vibeToolName]);
|
|
31886
|
+
const nextTool = { ...existingTool };
|
|
31887
|
+
const allow = new Set(toStringArray(existingTool.allow ?? existingTool.allowlist));
|
|
31888
|
+
const deny = new Set(toStringArray(existingTool.deny ?? existingTool.denylist));
|
|
31889
|
+
for (const [pattern, action] of Object.entries(rules)) {
|
|
31890
|
+
if (pattern === "*") {
|
|
31891
|
+
applyWildcardPermission({
|
|
31892
|
+
action,
|
|
31893
|
+
toolConfig: nextTool
|
|
31894
|
+
});
|
|
31895
|
+
if (action === "deny") {
|
|
31896
|
+
disabledTools.add(vibeToolName);
|
|
31897
|
+
enabledTools.delete(vibeToolName);
|
|
31898
|
+
} else if (action === "allow") disabledTools.delete(vibeToolName);
|
|
31899
|
+
continue;
|
|
31900
|
+
}
|
|
31901
|
+
if (action === "ask") {
|
|
31902
|
+
logger?.warn(`Vibe permissions do not support pattern-level "ask" rules. Skipping ${category}: ${pattern}`);
|
|
31903
|
+
continue;
|
|
31904
|
+
}
|
|
31905
|
+
if (action === "allow") allow.add(pattern);
|
|
31906
|
+
else deny.add(pattern);
|
|
31907
|
+
}
|
|
31908
|
+
delete nextTool.allow;
|
|
31909
|
+
delete nextTool.deny;
|
|
31910
|
+
if (allow.size > 0) nextTool.allowlist = [...allow].toSorted();
|
|
31911
|
+
if (deny.size > 0) nextTool.denylist = [...deny].toSorted();
|
|
31912
|
+
tools[vibeToolName] = nextTool;
|
|
31913
|
+
}
|
|
31914
|
+
/**
|
|
31915
|
+
* `enabled_tools` is an exclusive allowlist and therefore only ever
|
|
31916
|
+
* rulesync-authored explicitly, through `vibe.enabled_tools`. When the
|
|
31917
|
+
* override declares it (even empty), rulesync owns the whole list; when it
|
|
31918
|
+
* does not, the on-disk list survives minus the entries the caller's cleanup
|
|
31919
|
+
* loop removed for rulesync-configured tools (which also migrates away the
|
|
31920
|
+
* exclusive entries earlier rulesync versions wrote for allow rules).
|
|
31921
|
+
*/
|
|
31922
|
+
function resolveEnabledToolsPatch({ vibeOverride, enabledTools }) {
|
|
31923
|
+
if (vibeOverride?.enabled_tools !== void 0) {
|
|
31924
|
+
const declared = [...new Set(toStringArray(vibeOverride.enabled_tools))];
|
|
31925
|
+
return declared.length > 0 ? declared.toSorted() : void 0;
|
|
31926
|
+
}
|
|
31927
|
+
return enabledTools.size > 0 ? [...enabledTools].toSorted() : void 0;
|
|
31928
|
+
}
|
|
31455
31929
|
function parseVibeConfig(fileContent) {
|
|
31456
31930
|
const parsed = smol_toml.parse(fileContent || smol_toml.stringify({}));
|
|
31457
31931
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
@@ -31460,6 +31934,17 @@ function parseVibeConfig(fileContent) {
|
|
|
31460
31934
|
function toVibeToolName(category) {
|
|
31461
31935
|
return CANONICAL_TO_VIBE_TOOL_NAMES[category] ?? category;
|
|
31462
31936
|
}
|
|
31937
|
+
/**
|
|
31938
|
+
* Whether a canonical category has a Vibe builtin tool to land on. Categories
|
|
31939
|
+
* without one (e.g. `glob`, `notebookedit`) must not emit a
|
|
31940
|
+
* `[tools.<category>]` table: Vibe has no such tool, so a `deny` authored
|
|
31941
|
+
* there would look applied while being silently inert — the dangerous
|
|
31942
|
+
* direction. Unlike `agent` → `task` there is no correct name to rename to,
|
|
31943
|
+
* so the category is skipped with a warning (the grokcli adapter's pattern).
|
|
31944
|
+
*/
|
|
31945
|
+
function hasVibeToolName(category) {
|
|
31946
|
+
return Object.hasOwn(CANONICAL_TO_VIBE_TOOL_NAMES, category);
|
|
31947
|
+
}
|
|
31463
31948
|
function toCanonicalToolName$1(vibeToolName) {
|
|
31464
31949
|
return VIBE_TO_CANONICAL_TOOL_NAMES[vibeToolName] ?? vibeToolName;
|
|
31465
31950
|
}
|
|
@@ -31477,7 +31962,7 @@ function toStringArray(value) {
|
|
|
31477
31962
|
function applyWildcardPermission({ action, toolConfig }) {
|
|
31478
31963
|
if (action === "allow") toolConfig.permission = "always";
|
|
31479
31964
|
else if (action === "ask") toolConfig.permission = "ask";
|
|
31480
|
-
else
|
|
31965
|
+
else toolConfig.permission = "never";
|
|
31481
31966
|
}
|
|
31482
31967
|
function fromVibePermission(value) {
|
|
31483
31968
|
if (value === "always" || value === "allow") return "allow";
|
|
@@ -31505,6 +31990,20 @@ const WARP_OVERRIDE_KEYS = [
|
|
|
31505
31990
|
"agent_mode_coding_file_read_allowlist",
|
|
31506
31991
|
"agent_mode_execute_readonly_commands"
|
|
31507
31992
|
];
|
|
31993
|
+
const WARP_EXECUTION_PROFILE_OVERRIDE_KEY = "execution_profile";
|
|
31994
|
+
const WARP_EXECUTION_PROFILE_KEYS = [
|
|
31995
|
+
"read_files",
|
|
31996
|
+
"apply_code_diffs",
|
|
31997
|
+
"execute_commands",
|
|
31998
|
+
"mcp_permissions",
|
|
31999
|
+
"write_to_pty",
|
|
32000
|
+
"ask_user_question",
|
|
32001
|
+
"run_agents",
|
|
32002
|
+
"computer_use",
|
|
32003
|
+
"directory_allowlist",
|
|
32004
|
+
"mcp_allowlist",
|
|
32005
|
+
"mcp_denylist"
|
|
32006
|
+
];
|
|
31508
32007
|
/**
|
|
31509
32008
|
* Warp's `settings.toml` lives in a different directory per platform (Stable
|
|
31510
32009
|
* channel). The home directory is resolved by the processor through
|
|
@@ -31559,7 +32058,14 @@ function warpSettingsDir() {
|
|
|
31559
32058
|
* through the `warp` override namespace (see `WarpPermissionsOverrideSchema`):
|
|
31560
32059
|
* on **import** they are lifted from `settings.toml` into the override, and on
|
|
31561
32060
|
* **export** they are merged back into `[agents.profiles]` (the override wins).
|
|
31562
|
-
*
|
|
32061
|
+
*
|
|
32062
|
+
* On migrated installs those legacy keys are inert; their execution-profile
|
|
32063
|
+
* counterparts (`read_files` / `apply_code_diffs` / `execute_commands`
|
|
32064
|
+
* action permissions, `directory_allowlist`, `mcp_allowlist` /
|
|
32065
|
+
* `mcp_denylist`) are authored through the nested `warp.execution_profile`
|
|
32066
|
+
* override, merged into the `default` record of
|
|
32067
|
+
* `[agents.execution_profiles.<id>]` under the same collection-exists guard as
|
|
32068
|
+
* the command lists, and lifted back from the `default` profile on import.
|
|
31563
32069
|
*
|
|
31564
32070
|
* The `settings.toml` file holds all of Warp's settings, so the
|
|
31565
32071
|
* `[agents.profiles]` block is merged in place and the file is never deleted.
|
|
@@ -31612,7 +32118,11 @@ var WarpPermissions = class WarpPermissions extends ToolPermissions {
|
|
|
31612
32118
|
const agents = isRecord(settings.agents) ? { ...settings.agents } : {};
|
|
31613
32119
|
const profiles = isRecord(agents.profiles) ? { ...agents.profiles } : {};
|
|
31614
32120
|
const override = config.warp;
|
|
31615
|
-
|
|
32121
|
+
const executionProfileOverride = isRecord(override) && isRecord(override[WARP_EXECUTION_PROFILE_OVERRIDE_KEY]) ? override[WARP_EXECUTION_PROFILE_OVERRIDE_KEY] : void 0;
|
|
32122
|
+
if (isRecord(override)) {
|
|
32123
|
+
const { [WARP_EXECUTION_PROFILE_OVERRIDE_KEY]: _executionProfile, ...legacyOverride } = override;
|
|
32124
|
+
Object.assign(profiles, legacyOverride);
|
|
32125
|
+
}
|
|
31616
32126
|
const mergedAllow = (0, es_toolkit.uniq)(allow.toSorted());
|
|
31617
32127
|
const mergedDeny = (0, es_toolkit.uniq)(deny.toSorted());
|
|
31618
32128
|
if (mergedAllow.length > 0) profiles[ALLOWLIST_KEY] = mergedAllow;
|
|
@@ -31620,18 +32130,13 @@ var WarpPermissions = class WarpPermissions extends ToolPermissions {
|
|
|
31620
32130
|
if (mergedDeny.length > 0) profiles[DENYLIST_KEY] = mergedDeny;
|
|
31621
32131
|
else delete profiles[DENYLIST_KEY];
|
|
31622
32132
|
agents.profiles = profiles;
|
|
31623
|
-
|
|
31624
|
-
|
|
31625
|
-
|
|
31626
|
-
|
|
31627
|
-
|
|
31628
|
-
|
|
31629
|
-
|
|
31630
|
-
executionProfiles[DEFAULT_PROFILE_KEY] = defaultProfile;
|
|
31631
|
-
agents[EXECUTION_PROFILES_KEY] = executionProfiles;
|
|
31632
|
-
const otherProfileIds = Object.keys(executionProfiles).filter((id) => id !== DEFAULT_PROFILE_KEY);
|
|
31633
|
-
if (mergedDeny.length > 0 && otherProfileIds.length > 0 && logger) logger.warn(`Warp command deny rules were written to the 'default' execution profile only; they are not enforced while another profile (${otherProfileIds.join(", ")}) is active.`);
|
|
31634
|
-
}
|
|
32133
|
+
mergeIntoDefaultExecutionProfile({
|
|
32134
|
+
agents,
|
|
32135
|
+
mergedAllow,
|
|
32136
|
+
mergedDeny,
|
|
32137
|
+
executionProfileOverride,
|
|
32138
|
+
logger
|
|
32139
|
+
});
|
|
31635
32140
|
settings.agents = agents;
|
|
31636
32141
|
return new WarpPermissions({
|
|
31637
32142
|
outputRoot,
|
|
@@ -31659,6 +32164,11 @@ var WarpPermissions = class WarpPermissions extends ToolPermissions {
|
|
|
31659
32164
|
});
|
|
31660
32165
|
const warpOverride = {};
|
|
31661
32166
|
for (const key of WARP_OVERRIDE_KEYS) if (profiles[key] !== void 0) warpOverride[key] = profiles[key];
|
|
32167
|
+
if (defaultProfile) {
|
|
32168
|
+
const executionProfileOverride = {};
|
|
32169
|
+
for (const key of WARP_EXECUTION_PROFILE_KEYS) if (defaultProfile[key] !== void 0) executionProfileOverride[key] = defaultProfile[key];
|
|
32170
|
+
if (Object.keys(executionProfileOverride).length > 0) warpOverride[WARP_EXECUTION_PROFILE_OVERRIDE_KEY] = executionProfileOverride;
|
|
32171
|
+
}
|
|
31662
32172
|
const result = { ...config };
|
|
31663
32173
|
if (Object.keys(warpOverride).length > 0) result.warp = warpOverride;
|
|
31664
32174
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
|
|
@@ -31681,6 +32191,36 @@ var WarpPermissions = class WarpPermissions extends ToolPermissions {
|
|
|
31681
32191
|
}
|
|
31682
32192
|
};
|
|
31683
32193
|
/**
|
|
32194
|
+
* On migrated installs the legacy `[agents.profiles]` keys are inert: runtime
|
|
32195
|
+
* enforcement reads the active `[agents.execution_profiles.<id>]` record.
|
|
32196
|
+
* Merge the command lists (and the `warp.execution_profile` override's
|
|
32197
|
+
* autonomy keys) into the `default` profile in place so every other profile
|
|
32198
|
+
* key and profile ID survives. When the collection does not exist yet the
|
|
32199
|
+
* install is un-migrated — the legacy keys are still live there, and creating
|
|
32200
|
+
* the collection ourselves would mark Warp's one-shot migration complete early
|
|
32201
|
+
* and strand the user's other legacy settings (file-read allowlist, preferred
|
|
32202
|
+
* model), so it is deliberately left unset and the override is skipped with a
|
|
32203
|
+
* warning instead.
|
|
32204
|
+
*/
|
|
32205
|
+
function mergeIntoDefaultExecutionProfile({ agents, mergedAllow, mergedDeny, executionProfileOverride, logger }) {
|
|
32206
|
+
const hasOverrideKeys = executionProfileOverride !== void 0 && Object.keys(executionProfileOverride).length > 0;
|
|
32207
|
+
if (!isRecord(agents[EXECUTION_PROFILES_KEY])) {
|
|
32208
|
+
if (hasOverrideKeys && logger) logger.warn("The warp.execution_profile permissions override was skipped: settings.toml has no [agents.execution_profiles] collection yet (un-migrated install). Open Warp once to run its settings migration, then re-run rulesync generate.");
|
|
32209
|
+
return;
|
|
32210
|
+
}
|
|
32211
|
+
const executionProfiles = { ...agents[EXECUTION_PROFILES_KEY] };
|
|
32212
|
+
const defaultProfile = isRecord(executionProfiles[DEFAULT_PROFILE_KEY]) ? { ...executionProfiles[DEFAULT_PROFILE_KEY] } : {};
|
|
32213
|
+
if (executionProfileOverride) Object.assign(defaultProfile, executionProfileOverride);
|
|
32214
|
+
if (mergedAllow.length > 0) defaultProfile[PROFILE_ALLOWLIST_KEY] = mergedAllow;
|
|
32215
|
+
else delete defaultProfile[PROFILE_ALLOWLIST_KEY];
|
|
32216
|
+
if (mergedDeny.length > 0) defaultProfile[PROFILE_DENYLIST_KEY] = mergedDeny;
|
|
32217
|
+
else delete defaultProfile[PROFILE_DENYLIST_KEY];
|
|
32218
|
+
executionProfiles[DEFAULT_PROFILE_KEY] = defaultProfile;
|
|
32219
|
+
agents[EXECUTION_PROFILES_KEY] = executionProfiles;
|
|
32220
|
+
const otherProfileIds = Object.keys(executionProfiles).filter((id) => id !== DEFAULT_PROFILE_KEY);
|
|
32221
|
+
if ((mergedDeny.length > 0 || hasOverrideKeys) && otherProfileIds.length > 0 && logger) logger.warn(`Warp command deny rules and execution_profile override keys were written to the 'default' execution profile only; they are not enforced while another profile (${otherProfileIds.join(", ")}) is active.`);
|
|
32222
|
+
}
|
|
32223
|
+
/**
|
|
31684
32224
|
* Convert rulesync permissions config to Warp command allow/deny regex lists.
|
|
31685
32225
|
* Only the `bash` category maps; `ask` rules and non-`bash` categories are
|
|
31686
32226
|
* dropped (the latter with a warning when they carry `deny` rules).
|
|
@@ -31697,10 +32237,7 @@ function convertRulesyncToWarpPermissions({ config, logger }) {
|
|
|
31697
32237
|
case "allow":
|
|
31698
32238
|
allow.push(pattern);
|
|
31699
32239
|
break;
|
|
31700
|
-
case "deny":
|
|
31701
|
-
deny.push(pattern);
|
|
31702
|
-
break;
|
|
31703
|
-
case "ask": break;
|
|
32240
|
+
case "deny": deny.push(pattern);
|
|
31704
32241
|
}
|
|
31705
32242
|
}
|
|
31706
32243
|
return {
|
|
@@ -32155,7 +32692,7 @@ const toolPermissionsFactories = /* @__PURE__ */ new Map([
|
|
|
32155
32692
|
["rovodev", {
|
|
32156
32693
|
class: RovodevPermissions,
|
|
32157
32694
|
meta: {
|
|
32158
|
-
supportsProject:
|
|
32695
|
+
supportsProject: true,
|
|
32159
32696
|
supportsGlobal: true,
|
|
32160
32697
|
supportsImport: true
|
|
32161
32698
|
}
|
|
@@ -33352,6 +33889,38 @@ var AugmentcodeSkill = class AugmentcodeSkill extends ToolSkill {
|
|
|
33352
33889
|
}
|
|
33353
33890
|
};
|
|
33354
33891
|
//#endregion
|
|
33892
|
+
//#region src/features/rules/nested-scan-exclusions.ts
|
|
33893
|
+
/**
|
|
33894
|
+
* Shared exclusion lists for the nested rule-file scans (`AGENTS.md`,
|
|
33895
|
+
* `REASONIX.md`), which walk the whole project tree on import rather than a
|
|
33896
|
+
* rulesync-owned directory.
|
|
33897
|
+
*/
|
|
33898
|
+
/**
|
|
33899
|
+
* Dependency trees never scanned for nested rule files, at any depth. A rule
|
|
33900
|
+
* file there describes somebody else's project, and neither name is ever a
|
|
33901
|
+
* package name. Hidden directories are excluded separately, because a rule
|
|
33902
|
+
* file inside one is another tool's generated output (rulesync writes several
|
|
33903
|
+
* itself).
|
|
33904
|
+
*/
|
|
33905
|
+
const NESTED_SCAN_EXCLUDED_DIRS_ANY_DEPTH = ["node_modules", "__pycache__"];
|
|
33906
|
+
/**
|
|
33907
|
+
* Build, vendoring and scratch directories, excluded at the **project root
|
|
33908
|
+
* only**. A top-level `build/` is a build directory; `packages/build/` is a
|
|
33909
|
+
* package, and dropping it silently would lose a real subproject.
|
|
33910
|
+
*/
|
|
33911
|
+
const NESTED_SCAN_EXCLUDED_ROOT_DIRS = [
|
|
33912
|
+
"vendor",
|
|
33913
|
+
"third_party",
|
|
33914
|
+
"dist",
|
|
33915
|
+
"build",
|
|
33916
|
+
"out",
|
|
33917
|
+
"target",
|
|
33918
|
+
"coverage",
|
|
33919
|
+
"tmp",
|
|
33920
|
+
"temp",
|
|
33921
|
+
"venv"
|
|
33922
|
+
];
|
|
33923
|
+
//#endregion
|
|
33355
33924
|
//#region src/features/skills/claudecode-skill.ts
|
|
33356
33925
|
const ClaudecodeSkillFrontmatterSchema = zod_mini.z.looseObject({
|
|
33357
33926
|
name: zod_mini.z.string(),
|
|
@@ -33437,6 +34006,41 @@ var ClaudecodeSkill = class extends ToolSkill {
|
|
|
33437
34006
|
alternativeSkillRoots: [CLAUDECODE_SCHEDULED_TASKS_DIR_PATH]
|
|
33438
34007
|
};
|
|
33439
34008
|
}
|
|
34009
|
+
/**
|
|
34010
|
+
* Claude Code v2.1.178+ also loads skills from **nested** `.claude/skills/`
|
|
34011
|
+
* directories below the working directory (a skill in
|
|
34012
|
+
* `apps/web/.claude/skills/` becomes available when working on files
|
|
34013
|
+
* there). Discover those directories so `rulesync import` sees them —
|
|
34014
|
+
* import-only and lenient, like every configured root; the project-root
|
|
34015
|
+
* `.claude/skills/` stays the sole generation target. The scan reuses the
|
|
34016
|
+
* nested-rule-file exclusions (dependency trees at any depth, build/vendor
|
|
34017
|
+
* dirs at the root, hidden dirs other than the `.claude` being matched) and
|
|
34018
|
+
* never runs in global mode. On a name clash with a root skill, the root
|
|
34019
|
+
* one wins the import (Claude Code itself keeps both under a
|
|
34020
|
+
* directory-qualified name, which rulesync's flat skill namespace cannot
|
|
34021
|
+
* express).
|
|
34022
|
+
*
|
|
34023
|
+
* @see https://code.claude.com/docs/en/skills
|
|
34024
|
+
*/
|
|
34025
|
+
static async getConfiguredImportRoots({ outputRoot, global = false }) {
|
|
34026
|
+
if (global) return [];
|
|
34027
|
+
const root = toPosixPath(outputRoot);
|
|
34028
|
+
return filterOutPathsInGitIgnoredDirectories({
|
|
34029
|
+
rootDir: outputRoot,
|
|
34030
|
+
filePaths: await findFilesByGlobs([`${root}/*/**/${toPosixPath(CLAUDECODE_SKILLS_DIR_PATH)}`], {
|
|
34031
|
+
type: "dir",
|
|
34032
|
+
followSymbolicLinks: false,
|
|
34033
|
+
ignore: [
|
|
34034
|
+
`${root}/**/.*/**/${toPosixPath(CLAUDECODE_SKILLS_DIR_PATH)}`,
|
|
34035
|
+
...NESTED_SCAN_EXCLUDED_DIRS_ANY_DEPTH.map((dir) => `${root}/**/${dir}/**`),
|
|
34036
|
+
...NESTED_SCAN_EXCLUDED_ROOT_DIRS.map((dir) => `${root}/${dir}/**`)
|
|
34037
|
+
]
|
|
34038
|
+
})
|
|
34039
|
+
}).toSorted().map((dirPath) => ({
|
|
34040
|
+
outputRoot,
|
|
34041
|
+
relativeDirPath: (0, node_path.relative)(outputRoot, dirPath)
|
|
34042
|
+
}));
|
|
34043
|
+
}
|
|
33440
34044
|
getFrontmatter() {
|
|
33441
34045
|
return ClaudecodeSkillFrontmatterSchema.parse(this.requireMainFileFrontmatter());
|
|
33442
34046
|
}
|
|
@@ -36835,7 +37439,8 @@ var TaktSkill = class TaktSkill extends ToolSkill {
|
|
|
36835
37439
|
getDirPath() {
|
|
36836
37440
|
const fullPath = (0, node_path.join)(this.outputRoot, this.relativeDirPath);
|
|
36837
37441
|
const resolvedFull = (0, node_path.resolve)(fullPath);
|
|
36838
|
-
const
|
|
37442
|
+
const resolvedBase = (0, node_path.resolve)(this.outputRoot);
|
|
37443
|
+
const rel = (0, node_path.relative)(resolvedBase, resolvedFull);
|
|
36839
37444
|
if (rel.startsWith("..") || node_path.default.isAbsolute(rel)) throw new Error(`Path traversal detected: Final path escapes outputRoot. outputRoot="${this.outputRoot}", relativeDirPath="${this.relativeDirPath}"`);
|
|
36840
37445
|
return fullPath;
|
|
36841
37446
|
}
|
|
@@ -37385,6 +37990,23 @@ var ZedSkill = class ZedSkill extends ToolSkill {
|
|
|
37385
37990
|
}
|
|
37386
37991
|
};
|
|
37387
37992
|
//#endregion
|
|
37993
|
+
//#region src/features/skills/zoocode-skill.ts
|
|
37994
|
+
/**
|
|
37995
|
+
* Skill generator for **Zoo Code** (the community continuation of Roo Code).
|
|
37996
|
+
* Zoo Code keeps Roo's skills layout (`.roo/skills/` project,
|
|
37997
|
+
* `~/.roo/skills/` global) and the `roo:` frontmatter section (`modeSlugs`),
|
|
37998
|
+
* so this target reuses {@link RooSkill} verbatim and only narrows the
|
|
37999
|
+
* targeting.
|
|
38000
|
+
*
|
|
38001
|
+
* @see https://docs.zoocode.dev/features/skills
|
|
38002
|
+
*/
|
|
38003
|
+
var ZoocodeSkill = class extends RooSkill {
|
|
38004
|
+
static isTargetedByRulesyncSkill(rulesyncSkill) {
|
|
38005
|
+
const targets = rulesyncSkill.getFrontmatter().targets;
|
|
38006
|
+
return targets.includes("*") || targets.includes("zoocode");
|
|
38007
|
+
}
|
|
38008
|
+
};
|
|
38009
|
+
//#endregion
|
|
37388
38010
|
//#region src/features/skills/skills-processor.ts
|
|
37389
38011
|
const SkillsProcessorToolTargetSchema = zod_mini.z.enum(skillsProcessorToolTargetTuple);
|
|
37390
38012
|
/**
|
|
@@ -37657,6 +38279,14 @@ const toolSkillFactories = /* @__PURE__ */ new Map([
|
|
|
37657
38279
|
supportsGlobal: true
|
|
37658
38280
|
}
|
|
37659
38281
|
}],
|
|
38282
|
+
["zoocode", {
|
|
38283
|
+
class: ZoocodeSkill,
|
|
38284
|
+
meta: {
|
|
38285
|
+
supportsProject: true,
|
|
38286
|
+
supportsSimulated: false,
|
|
38287
|
+
supportsGlobal: true
|
|
38288
|
+
}
|
|
38289
|
+
}],
|
|
37660
38290
|
["rovodev", {
|
|
37661
38291
|
class: RovodevSkill,
|
|
37662
38292
|
meta: {
|
|
@@ -37819,7 +38449,7 @@ var SkillsProcessor = class extends DirFeatureProcessor {
|
|
|
37819
38449
|
for (const root of roots) {
|
|
37820
38450
|
const rootOutputRoot = typeof root === "string" ? this.outputRoot : root.outputRoot;
|
|
37821
38451
|
const relativeDirPath = typeof root === "string" ? root : root.relativeDirPath;
|
|
37822
|
-
const
|
|
38452
|
+
const isLenientRoot = configuredRootPaths.has(relativeDirPath) || factory.meta.lenientImport === true || isAgentSkillsInteropRoot(relativeDirPath);
|
|
37823
38453
|
const skillsDirPath = (0, node_path.join)(rootOutputRoot, relativeDirPath);
|
|
37824
38454
|
if (!await directoryExists(skillsDirPath)) continue;
|
|
37825
38455
|
const dirPaths = await findFilesByGlobs((0, node_path.join)(skillsDirPath, "*"), { type: "dir" });
|
|
@@ -37843,7 +38473,7 @@ var SkillsProcessor = class extends DirFeatureProcessor {
|
|
|
37843
38473
|
global: this.global
|
|
37844
38474
|
});
|
|
37845
38475
|
} catch (error) {
|
|
37846
|
-
if (!
|
|
38476
|
+
if (!isLenientRoot) throw error;
|
|
37847
38477
|
this.logger.warn(`Skipping ${(0, node_path.join)(relativeDirPath, dirName)}: ${formatError(error)}`);
|
|
37848
38478
|
return null;
|
|
37849
38479
|
}
|
|
@@ -37858,12 +38488,20 @@ var SkillsProcessor = class extends DirFeatureProcessor {
|
|
|
37858
38488
|
const fromFlatFile = factory.class.fromFlatFile;
|
|
37859
38489
|
const directoryStems = new Set(ownedDirNames);
|
|
37860
38490
|
const flatFilePaths = (await findFilesByGlobs((0, node_path.join)(skillsDirPath, "*.md"), { type: "file" })).filter((filePath) => !directoryStems.has((0, node_path.basename)(filePath, ".md")));
|
|
37861
|
-
const flatSkills = await Promise.all(flatFilePaths.map((filePath) =>
|
|
37862
|
-
|
|
37863
|
-
|
|
37864
|
-
|
|
37865
|
-
|
|
37866
|
-
|
|
38491
|
+
const flatSkills = (await Promise.all(flatFilePaths.map(async (filePath) => {
|
|
38492
|
+
try {
|
|
38493
|
+
return await fromFlatFile({
|
|
38494
|
+
outputRoot: rootOutputRoot,
|
|
38495
|
+
relativeDirPath,
|
|
38496
|
+
relativeFilePath: (0, node_path.basename)(filePath),
|
|
38497
|
+
global: this.global
|
|
38498
|
+
});
|
|
38499
|
+
} catch (error) {
|
|
38500
|
+
if (!isLenientRoot) throw error;
|
|
38501
|
+
this.logger.warn(`Skipping ${(0, node_path.join)(relativeDirPath, (0, node_path.basename)(filePath))}: ${formatError(error)}`);
|
|
38502
|
+
return null;
|
|
38503
|
+
}
|
|
38504
|
+
}))).filter((skill) => skill !== null);
|
|
37867
38505
|
for (const skill of flatSkills) {
|
|
37868
38506
|
const skillName = skill.getImportIdentity();
|
|
37869
38507
|
if (seenSkillNames.has(skillName)) continue;
|
|
@@ -37936,14 +38574,15 @@ var SkillsProcessor = class extends DirFeatureProcessor {
|
|
|
37936
38574
|
*/
|
|
37937
38575
|
static getSimulatedConventionSection({ skillList }) {
|
|
37938
38576
|
if (!skillList || skillList.length === 0) return "";
|
|
38577
|
+
const skillListWithAtPrefix = skillList.map((skill) => ({
|
|
38578
|
+
...skill,
|
|
38579
|
+
path: `@${skill.path}`
|
|
38580
|
+
}));
|
|
37939
38581
|
return `## Simulated Skills
|
|
37940
38582
|
|
|
37941
38583
|
Simulated skills are specialized capabilities that can be invoked to handle specific types of tasks. When you determine that a skill would be helpful for the current task, read the corresponding SKILL.md file and execute its instructions.
|
|
37942
38584
|
|
|
37943
|
-
${(0, _toon_format_toon.encode)({ skillList:
|
|
37944
|
-
...skill,
|
|
37945
|
-
path: `@${skill.path}`
|
|
37946
|
-
})) })}`;
|
|
38585
|
+
${(0, _toon_format_toon.encode)({ skillList: skillListWithAtPrefix })}`;
|
|
37947
38586
|
}
|
|
37948
38587
|
/**
|
|
37949
38588
|
* Return the tool targets that this processor supports in global mode
|
|
@@ -40547,7 +41186,8 @@ var HermesagentSubagent = class HermesagentSubagent extends ToolSubagent {
|
|
|
40547
41186
|
return getHermesagentSharedConfigWritePaths();
|
|
40548
41187
|
}
|
|
40549
41188
|
static getSettablePathsForRulesyncSubagent(rulesyncSubagent) {
|
|
40550
|
-
|
|
41189
|
+
const slug = subagentSlug(rulesyncSubagent.getRelativePathFromCwd());
|
|
41190
|
+
return [(0, node_path.join)(HERMESAGENT_RULESYNC_SUBAGENTS_DIR_PATH, `${slug}.json`)];
|
|
40551
41191
|
}
|
|
40552
41192
|
toRulesyncSubagent() {
|
|
40553
41193
|
const slug = (0, node_path.basename)(this.getRelativeFilePath(), ".json");
|
|
@@ -41782,7 +42422,7 @@ function stringifyRooModes(modes) {
|
|
|
41782
42422
|
*
|
|
41783
42423
|
* @see https://roocodeinc.github.io/Roo-Code/features/custom-modes
|
|
41784
42424
|
*/
|
|
41785
|
-
var RooSubagent = class
|
|
42425
|
+
var RooSubagent = class extends ToolSubagent {
|
|
41786
42426
|
modes;
|
|
41787
42427
|
constructor({ modes, ...rest }) {
|
|
41788
42428
|
if (rest.validate !== false) {
|
|
@@ -41838,7 +42478,7 @@ var RooSubagent = class RooSubagent extends ToolSubagent {
|
|
|
41838
42478
|
const mode = this.toRooMode(rulesyncSubagent);
|
|
41839
42479
|
bySlug.set(mode.slug, mode);
|
|
41840
42480
|
}
|
|
41841
|
-
return new
|
|
42481
|
+
return new this({
|
|
41842
42482
|
outputRoot,
|
|
41843
42483
|
relativeDirPath: this.getSettablePaths().relativeDirPath,
|
|
41844
42484
|
relativeFilePath: ROO_MODES_FILE_NAME,
|
|
@@ -41917,7 +42557,7 @@ var RooSubagent = class RooSubagent extends ToolSubagent {
|
|
|
41917
42557
|
}
|
|
41918
42558
|
const result = RooModesFileSchema.safeParse(parsed ?? { customModes: [] });
|
|
41919
42559
|
if (!result.success) throw new Error(`Invalid .roomodes in ${filePath}: ${formatError(result.error)}`);
|
|
41920
|
-
return new
|
|
42560
|
+
return new this({
|
|
41921
42561
|
outputRoot,
|
|
41922
42562
|
relativeDirPath: paths.relativeDirPath,
|
|
41923
42563
|
relativeFilePath: (0, node_path.basename)(relativeFilePath),
|
|
@@ -41926,7 +42566,7 @@ var RooSubagent = class RooSubagent extends ToolSubagent {
|
|
|
41926
42566
|
});
|
|
41927
42567
|
}
|
|
41928
42568
|
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
41929
|
-
return new
|
|
42569
|
+
return new this({
|
|
41930
42570
|
outputRoot,
|
|
41931
42571
|
relativeDirPath,
|
|
41932
42572
|
relativeFilePath,
|
|
@@ -42172,6 +42812,60 @@ var VibeSubagent = class VibeSubagent extends ToolSubagent {
|
|
|
42172
42812
|
}
|
|
42173
42813
|
};
|
|
42174
42814
|
//#endregion
|
|
42815
|
+
//#region src/features/subagents/zoocode-subagent.ts
|
|
42816
|
+
/**
|
|
42817
|
+
* Subagent (custom-mode) generator for **Zoo Code**, the community
|
|
42818
|
+
* continuation of Roo Code. Zoo Code keeps the aggregated `.roomodes` file and
|
|
42819
|
+
* Roo's mode schema, and the shared mode fields keep riding the `roo:`
|
|
42820
|
+
* frontmatter section — one rulesync source must not produce two different
|
|
42821
|
+
* spellings of the same field.
|
|
42822
|
+
*
|
|
42823
|
+
* The post-fork divergence is carried by the `zoocode:` section:
|
|
42824
|
+
* `allowedMcpServers` (Zoo Code v3.60.0+) — a per-mode MCP server allowlist.
|
|
42825
|
+
* "When omitted, all servers are available. When set, only the listed servers
|
|
42826
|
+
* are injected." On import the key is lifted back into `zoocode:` so it
|
|
42827
|
+
* round-trips (and never leaks into the `roo:` section, where the archived
|
|
42828
|
+
* Roo Code would not understand it).
|
|
42829
|
+
*
|
|
42830
|
+
* @see https://github.com/Zoo-Code-Org/Zoo-Code
|
|
42831
|
+
* @see https://docs.zoocode.dev/features/custom-modes
|
|
42832
|
+
*/
|
|
42833
|
+
var ZoocodeSubagent = class extends RooSubagent {
|
|
42834
|
+
static isTargetedByRulesyncSubagent(rulesyncSubagent) {
|
|
42835
|
+
return this.isTargetedByRulesyncSubagentDefault({
|
|
42836
|
+
rulesyncSubagent,
|
|
42837
|
+
toolTarget: "zoocode"
|
|
42838
|
+
});
|
|
42839
|
+
}
|
|
42840
|
+
static toRooMode(rulesyncSubagent) {
|
|
42841
|
+
const mode = super.toRooMode(rulesyncSubagent);
|
|
42842
|
+
const frontmatter = rulesyncSubagent.getFrontmatter();
|
|
42843
|
+
const zoocodeSection = isRecord(frontmatter.zoocode) ? frontmatter.zoocode : {};
|
|
42844
|
+
if (isStringArray$1(zoocodeSection.allowedMcpServers)) mode.allowedMcpServers = zoocodeSection.allowedMcpServers;
|
|
42845
|
+
return mode;
|
|
42846
|
+
}
|
|
42847
|
+
toRulesyncSubagents() {
|
|
42848
|
+
return super.toRulesyncSubagents().map((subagent) => {
|
|
42849
|
+
const frontmatter = subagent.getFrontmatter();
|
|
42850
|
+
const { allowedMcpServers, ...restRooSection } = isRecord(frontmatter.roo) ? { ...frontmatter.roo } : {};
|
|
42851
|
+
const rebuilt = {
|
|
42852
|
+
...frontmatter,
|
|
42853
|
+
targets: ["zoocode"],
|
|
42854
|
+
roo: restRooSection
|
|
42855
|
+
};
|
|
42856
|
+
if (isStringArray$1(allowedMcpServers)) rebuilt.zoocode = { allowedMcpServers };
|
|
42857
|
+
return new RulesyncSubagent({
|
|
42858
|
+
outputRoot: ".",
|
|
42859
|
+
frontmatter: rebuilt,
|
|
42860
|
+
body: subagent.getBody(),
|
|
42861
|
+
relativeDirPath: subagent.getRelativeDirPath(),
|
|
42862
|
+
relativeFilePath: subagent.getRelativeFilePath(),
|
|
42863
|
+
validate: true
|
|
42864
|
+
});
|
|
42865
|
+
});
|
|
42866
|
+
}
|
|
42867
|
+
};
|
|
42868
|
+
//#endregion
|
|
42175
42869
|
//#region src/features/subagents/subagents-processor.ts
|
|
42176
42870
|
const SubagentsProcessorToolTargetSchema = zod_mini.z.enum(subagentsProcessorToolTargetTuple);
|
|
42177
42871
|
/**
|
|
@@ -42411,6 +43105,14 @@ const toolSubagentFactories = /* @__PURE__ */ new Map([
|
|
|
42411
43105
|
filePattern: ".roomodes"
|
|
42412
43106
|
}
|
|
42413
43107
|
}],
|
|
43108
|
+
["zoocode", {
|
|
43109
|
+
class: ZoocodeSubagent,
|
|
43110
|
+
meta: {
|
|
43111
|
+
supportsSimulated: false,
|
|
43112
|
+
supportsGlobal: false,
|
|
43113
|
+
filePattern: ".roomodes"
|
|
43114
|
+
}
|
|
43115
|
+
}],
|
|
42414
43116
|
["rovodev", {
|
|
42415
43117
|
class: RovodevSubagent,
|
|
42416
43118
|
meta: {
|
|
@@ -42671,11 +43373,13 @@ For example, if the user instructs \`Call planner subagent to plan the refactori
|
|
|
42671
43373
|
//#region src/features/rules/tool-rule.ts
|
|
42672
43374
|
var ToolRule = class extends ToolFile {
|
|
42673
43375
|
root;
|
|
43376
|
+
localRoot;
|
|
42674
43377
|
description;
|
|
42675
43378
|
globs;
|
|
42676
|
-
constructor({ root = false, description, globs, ...rest }) {
|
|
43379
|
+
constructor({ root = false, localRoot = false, description, globs, ...rest }) {
|
|
42677
43380
|
super(rest);
|
|
42678
43381
|
this.root = root;
|
|
43382
|
+
this.localRoot = localRoot;
|
|
42679
43383
|
this.description = description;
|
|
42680
43384
|
this.globs = globs;
|
|
42681
43385
|
}
|
|
@@ -42758,6 +43462,38 @@ var ToolRule = class extends ToolFile {
|
|
|
42758
43462
|
isRoot() {
|
|
42759
43463
|
return this.root;
|
|
42760
43464
|
}
|
|
43465
|
+
isLocalRoot() {
|
|
43466
|
+
return this.localRoot;
|
|
43467
|
+
}
|
|
43468
|
+
/**
|
|
43469
|
+
* Convert a tool's separate personal local-root file (see
|
|
43470
|
+
* {@link ToolRuleParams.localRoot}) back to a canonical `localRoot: true`
|
|
43471
|
+
* rulesync rule. The rulesync file keeps the tool-side basename
|
|
43472
|
+
* (`CLAUDE.local.md`, `AGENTS.local.md`, ...), which the derived `.gitignore`
|
|
43473
|
+
* already covers via `.rulesync/rules/*.local.md`, so personal content stays
|
|
43474
|
+
* untracked after import.
|
|
43475
|
+
*
|
|
43476
|
+
* `targets` is scoped to the importing tool rather than `"*"`. A wildcard
|
|
43477
|
+
* would spread the personal content to every tool on the next generate —
|
|
43478
|
+
* including being appended into the committed root file of tools without a
|
|
43479
|
+
* separate local file — and a multi-target import would produce several
|
|
43480
|
+
* wildcard localRoot rules, which the per-target validation rejects.
|
|
43481
|
+
*/
|
|
43482
|
+
toLocalRootRulesyncRule({ targets }) {
|
|
43483
|
+
return new RulesyncRule({
|
|
43484
|
+
outputRoot: this.getOutputRoot(),
|
|
43485
|
+
relativeDirPath: RULESYNC_RULES_RELATIVE_DIR_PATH,
|
|
43486
|
+
relativeFilePath: this.getRelativeFilePath(),
|
|
43487
|
+
frontmatter: {
|
|
43488
|
+
root: false,
|
|
43489
|
+
localRoot: true,
|
|
43490
|
+
targets,
|
|
43491
|
+
globs: []
|
|
43492
|
+
},
|
|
43493
|
+
body: this.getFileContent(),
|
|
43494
|
+
validate: true
|
|
43495
|
+
});
|
|
43496
|
+
}
|
|
42761
43497
|
/**
|
|
42762
43498
|
* Whether this rule must be left out of the root rule's reference/MCP
|
|
42763
43499
|
* instruction listings even though it is a non-root survivor. Used by files
|
|
@@ -42790,31 +43526,6 @@ function buildToolPath(toolDir, subDir, excludeToolDir) {
|
|
|
42790
43526
|
}
|
|
42791
43527
|
//#endregion
|
|
42792
43528
|
//#region src/features/rules/agentsmd-rule.ts
|
|
42793
|
-
/**
|
|
42794
|
-
* Dependency trees never scanned for nested `AGENTS.md` files, at any depth. An
|
|
42795
|
-
* `AGENTS.md` there describes somebody else's project, and neither name is ever
|
|
42796
|
-
* a package name. Hidden directories are excluded separately, because an
|
|
42797
|
-
* `AGENTS.md` inside one is another tool's generated output (rulesync writes
|
|
42798
|
-
* several itself).
|
|
42799
|
-
*/
|
|
42800
|
-
const NESTED_SCAN_EXCLUDED_DIRS_ANY_DEPTH = ["node_modules", "__pycache__"];
|
|
42801
|
-
/**
|
|
42802
|
-
* Build, vendoring and scratch directories, excluded at the **project root
|
|
42803
|
-
* only**. A top-level `build/` is a build directory; `packages/build/` is a
|
|
42804
|
-
* package, and dropping it silently would lose a real subproject.
|
|
42805
|
-
*/
|
|
42806
|
-
const NESTED_SCAN_EXCLUDED_ROOT_DIRS = [
|
|
42807
|
-
"vendor",
|
|
42808
|
-
"third_party",
|
|
42809
|
-
"dist",
|
|
42810
|
-
"build",
|
|
42811
|
-
"out",
|
|
42812
|
-
"target",
|
|
42813
|
-
"coverage",
|
|
42814
|
-
"tmp",
|
|
42815
|
-
"temp",
|
|
42816
|
-
"venv"
|
|
42817
|
-
];
|
|
42818
43529
|
var AgentsMdRule = class AgentsMdRule extends ToolRule {
|
|
42819
43530
|
constructor({ fileContent, root, ...rest }) {
|
|
42820
43531
|
super({
|
|
@@ -42871,7 +43582,8 @@ var AgentsMdRule = class AgentsMdRule extends ToolRule {
|
|
|
42871
43582
|
const normalizedDirPath = relativeDirPath === void 0 ? "." : toPosixPath(relativeDirPath);
|
|
42872
43583
|
const isNested = relativeFilePath === "AGENTS.md" && normalizedDirPath !== "." && normalizedDirPath !== "" && !normalizedDirPath.startsWith(".");
|
|
42873
43584
|
const isRoot = !isNested && relativeFilePath === "AGENTS.md" && (normalizedDirPath === "." || normalizedDirPath === "");
|
|
42874
|
-
const
|
|
43585
|
+
const relativePath = isNested ? (0, node_path.join)(normalizedDirPath, relativeFilePath) : isRoot ? AGENTSMD_RULE_FILE_NAME : (0, node_path.join)(AGENTSMD_MEMORIES_DIR_PATH, relativeFilePath);
|
|
43586
|
+
const fileContent = await readFileContent((0, node_path.join)(outputRoot, relativePath));
|
|
42875
43587
|
return new AgentsMdRule({
|
|
42876
43588
|
outputRoot,
|
|
42877
43589
|
relativeDirPath: isNested ? (0, node_path.join)(normalizedDirPath) : isRoot ? this.getSettablePaths().root.relativeDirPath : this.getSettablePaths().nonRoot.relativeDirPath,
|
|
@@ -43178,7 +43890,8 @@ var AntigravityCliRule = class AntigravityCliRule extends ToolRule {
|
|
|
43178
43890
|
});
|
|
43179
43891
|
}
|
|
43180
43892
|
if (!paths.nonRoot) throw new Error(`nonRoot path is not set for ${relativeFilePath}`);
|
|
43181
|
-
const
|
|
43893
|
+
const relativePath = (0, node_path.join)(paths.nonRoot.relativeDirPath, relativeFilePath);
|
|
43894
|
+
const fileContent = await readFileContent((0, node_path.join)(outputRoot, relativePath));
|
|
43182
43895
|
return new AntigravityCliRule({
|
|
43183
43896
|
outputRoot,
|
|
43184
43897
|
relativeDirPath: paths.nonRoot.relativeDirPath,
|
|
@@ -43639,7 +44352,8 @@ var AugmentcodeLegacyRule = class AugmentcodeLegacyRule extends ToolRule {
|
|
|
43639
44352
|
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true }) {
|
|
43640
44353
|
const settablePaths = this.getSettablePaths();
|
|
43641
44354
|
const isRoot = relativeFilePath === settablePaths.root.relativeFilePath;
|
|
43642
|
-
const
|
|
44355
|
+
const relativePath = isRoot ? settablePaths.root.relativeFilePath : (0, node_path.join)(settablePaths.nonRoot.relativeDirPath, relativeFilePath);
|
|
44356
|
+
const fileContent = await readFileContent((0, node_path.join)(outputRoot, relativePath));
|
|
43643
44357
|
return new AugmentcodeLegacyRule({
|
|
43644
44358
|
outputRoot,
|
|
43645
44359
|
relativeDirPath: isRoot ? settablePaths.root.relativeDirPath : settablePaths.nonRoot.relativeDirPath,
|
|
@@ -43863,7 +44577,8 @@ var ClaudecodeLegacyRule = class ClaudecodeLegacyRule extends ToolRule {
|
|
|
43863
44577
|
});
|
|
43864
44578
|
}
|
|
43865
44579
|
if (!paths.nonRoot) throw new Error(`nonRoot path is not set for ${relativeFilePath}`);
|
|
43866
|
-
const
|
|
44580
|
+
const relativePath = (0, node_path.join)(paths.nonRoot.relativeDirPath, relativeFilePath);
|
|
44581
|
+
const fileContent = await readFileContent((0, node_path.join)(outputRoot, relativePath));
|
|
43867
44582
|
return new ClaudecodeLegacyRule({
|
|
43868
44583
|
outputRoot,
|
|
43869
44584
|
relativeDirPath: paths.nonRoot.relativeDirPath,
|
|
@@ -43984,7 +44699,8 @@ var ClaudecodeRule = class ClaudecodeRule extends ToolRule {
|
|
|
43984
44699
|
});
|
|
43985
44700
|
}
|
|
43986
44701
|
if (!paths.nonRoot) throw new Error(`nonRoot path is not set for ${relativeFilePath}`);
|
|
43987
|
-
const
|
|
44702
|
+
const relativePath = (0, node_path.join)(paths.nonRoot.relativeDirPath, relativeFilePath);
|
|
44703
|
+
const filePath = (0, node_path.join)(outputRoot, relativePath);
|
|
43988
44704
|
const { frontmatter, body: content } = parseFrontmatter(await readFileContent(filePath), filePath);
|
|
43989
44705
|
const result = ClaudecodeRuleFrontmatterSchema.safeParse(frontmatter);
|
|
43990
44706
|
if (!result.success) throw new Error(`Invalid frontmatter in ${filePath}: ${formatError(result.error)}`);
|
|
@@ -44371,7 +45087,8 @@ var CodexcliRule = class CodexcliRule extends ToolRule {
|
|
|
44371
45087
|
}
|
|
44372
45088
|
static async fromFile({ outputRoot = process.cwd(), relativeFilePath: _relativeFilePath, validate = true, global = false }) {
|
|
44373
45089
|
const { root } = this.getSettablePaths({ global });
|
|
44374
|
-
const
|
|
45090
|
+
const relativePath = (0, node_path.join)(root.relativeDirPath, root.relativeFilePath);
|
|
45091
|
+
const fileContent = await readFileContent((0, node_path.join)(outputRoot, relativePath));
|
|
44375
45092
|
return new CodexcliRule({
|
|
44376
45093
|
outputRoot,
|
|
44377
45094
|
relativeDirPath: root.relativeDirPath,
|
|
@@ -44540,7 +45257,8 @@ var CopilotRule = class CopilotRule extends ToolRule {
|
|
|
44540
45257
|
}) : relativeFilePath === paths.root.relativeFilePath;
|
|
44541
45258
|
const resolvedRelativeDirPath = relativeDirPath ?? (isRoot ? paths.root.relativeDirPath : paths.nonRoot?.relativeDirPath ?? paths.root.relativeDirPath);
|
|
44542
45259
|
if (isRoot) {
|
|
44543
|
-
const
|
|
45260
|
+
const relativePath = (0, node_path.join)(paths.root.relativeDirPath, paths.root.relativeFilePath);
|
|
45261
|
+
const fileContent = await readFileContent((0, node_path.join)(outputRoot, relativePath));
|
|
44544
45262
|
return new CopilotRule({
|
|
44545
45263
|
outputRoot,
|
|
44546
45264
|
relativeDirPath: paths.root.relativeDirPath,
|
|
@@ -44552,7 +45270,8 @@ var CopilotRule = class CopilotRule extends ToolRule {
|
|
|
44552
45270
|
});
|
|
44553
45271
|
}
|
|
44554
45272
|
if (!paths.nonRoot) throw new Error(`nonRoot path is not set for ${relativeFilePath}`);
|
|
44555
|
-
const
|
|
45273
|
+
const relativePath = (0, node_path.join)(resolvedRelativeDirPath, relativeFilePath);
|
|
45274
|
+
const filePath = (0, node_path.join)(outputRoot, relativePath);
|
|
44556
45275
|
const { frontmatter, body: content } = parseFrontmatter(await readFileContent(filePath), filePath);
|
|
44557
45276
|
const result = CopilotRuleFrontmatterSchema.safeParse(frontmatter);
|
|
44558
45277
|
if (!result.success) throw new Error(`Invalid frontmatter in ${filePath}: ${formatError(result.error)}`);
|
|
@@ -44824,7 +45543,8 @@ var DeepagentsRule = class DeepagentsRule extends ToolRule {
|
|
|
44824
45543
|
}
|
|
44825
45544
|
static async fromFile({ outputRoot = process.cwd(), relativeFilePath: _relativeFilePath, validate = true, global = false }) {
|
|
44826
45545
|
const settablePaths = this.getSettablePaths({ global });
|
|
44827
|
-
const
|
|
45546
|
+
const relativePath = (0, node_path.join)(settablePaths.root.relativeDirPath, settablePaths.root.relativeFilePath);
|
|
45547
|
+
const fileContent = await readFileContent((0, node_path.join)(outputRoot, relativePath));
|
|
44828
45548
|
return new DeepagentsRule({
|
|
44829
45549
|
outputRoot,
|
|
44830
45550
|
relativeDirPath: settablePaths.root.relativeDirPath,
|
|
@@ -45251,7 +45971,8 @@ var FactorydroidRule = class FactorydroidRule extends ToolRule {
|
|
|
45251
45971
|
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
|
|
45252
45972
|
const paths = this.getSettablePaths({ global });
|
|
45253
45973
|
if (relativeFilePath === paths.root.relativeFilePath) {
|
|
45254
|
-
const
|
|
45974
|
+
const relativePath = (0, node_path.join)(paths.root.relativeDirPath, paths.root.relativeFilePath);
|
|
45975
|
+
const fileContent = await readFileContent((0, node_path.join)(outputRoot, relativePath));
|
|
45255
45976
|
return new FactorydroidRule({
|
|
45256
45977
|
outputRoot,
|
|
45257
45978
|
relativeDirPath: paths.root.relativeDirPath,
|
|
@@ -45262,7 +45983,8 @@ var FactorydroidRule = class FactorydroidRule extends ToolRule {
|
|
|
45262
45983
|
});
|
|
45263
45984
|
}
|
|
45264
45985
|
if (!paths.nonRoot) throw new Error(`nonRoot path is not set for ${relativeFilePath}`);
|
|
45265
|
-
const
|
|
45986
|
+
const relativePath = (0, node_path.join)(paths.nonRoot.relativeDirPath, relativeFilePath);
|
|
45987
|
+
const fileContent = await readFileContent((0, node_path.join)(outputRoot, relativePath));
|
|
45266
45988
|
return new FactorydroidRule({
|
|
45267
45989
|
outputRoot,
|
|
45268
45990
|
relativeDirPath: paths.nonRoot.relativeDirPath,
|
|
@@ -45492,7 +46214,8 @@ var HermesagentRule = class HermesagentRule extends ToolRule {
|
|
|
45492
46214
|
}
|
|
45493
46215
|
static async fromFile({ outputRoot = process.cwd(), relativeFilePath: _relativeFilePath, validate = true }) {
|
|
45494
46216
|
const { root } = this.getSettablePaths();
|
|
45495
|
-
const
|
|
46217
|
+
const relativePath = (0, node_path.join)(root.relativeDirPath, root.relativeFilePath);
|
|
46218
|
+
const fileContent = await readFileContent((0, node_path.join)(outputRoot, relativePath));
|
|
45496
46219
|
return new HermesagentRule({
|
|
45497
46220
|
outputRoot,
|
|
45498
46221
|
relativeDirPath: root.relativeDirPath,
|
|
@@ -45603,7 +46326,8 @@ var JunieRule = class JunieRule extends ToolRule {
|
|
|
45603
46326
|
});
|
|
45604
46327
|
}
|
|
45605
46328
|
const relativeDirPath = this.getSettablePaths().root.relativeDirPath;
|
|
45606
|
-
const
|
|
46329
|
+
const relativePath = (0, node_path.join)(relativeDirPath, relativeFilePath);
|
|
46330
|
+
const fileContent = await readFileContent((0, node_path.join)(outputRoot, relativePath));
|
|
45607
46331
|
return new JunieRule({
|
|
45608
46332
|
outputRoot,
|
|
45609
46333
|
relativeDirPath,
|
|
@@ -45703,7 +46427,8 @@ var KiloRule = class KiloRule extends ToolRule {
|
|
|
45703
46427
|
});
|
|
45704
46428
|
}
|
|
45705
46429
|
if (!paths.nonRoot) throw new Error(`nonRoot path is not set for ${relativeFilePath}`);
|
|
45706
|
-
const
|
|
46430
|
+
const relativePath = (0, node_path.join)(paths.nonRoot.relativeDirPath, relativeFilePath);
|
|
46431
|
+
const fileContent = await readFileContent((0, node_path.join)(outputRoot, relativePath));
|
|
45707
46432
|
return new KiloRule({
|
|
45708
46433
|
outputRoot,
|
|
45709
46434
|
relativeDirPath: paths.nonRoot.relativeDirPath,
|
|
@@ -46040,10 +46765,13 @@ var KiroIdeRule = class extends KiroRule {
|
|
|
46040
46765
|
//#region src/features/rules/opencode-rule.ts
|
|
46041
46766
|
var OpenCodeRule = class OpenCodeRule extends ToolRule {
|
|
46042
46767
|
static getSettablePaths({ global, excludeToolDir } = {}) {
|
|
46043
|
-
if (global) return {
|
|
46044
|
-
|
|
46045
|
-
|
|
46046
|
-
|
|
46768
|
+
if (global) return {
|
|
46769
|
+
root: {
|
|
46770
|
+
relativeDirPath: buildToolPath(OPENCODE_GLOBAL_DIR, ".", excludeToolDir),
|
|
46771
|
+
relativeFilePath: OPENCODE_RULE_FILE_NAME
|
|
46772
|
+
},
|
|
46773
|
+
nonRoot: { relativeDirPath: buildToolPath(OPENCODE_GLOBAL_DIR, "memories", excludeToolDir) }
|
|
46774
|
+
};
|
|
46047
46775
|
return {
|
|
46048
46776
|
root: {
|
|
46049
46777
|
relativeDirPath: ".",
|
|
@@ -46053,7 +46781,7 @@ var OpenCodeRule = class OpenCodeRule extends ToolRule {
|
|
|
46053
46781
|
};
|
|
46054
46782
|
}
|
|
46055
46783
|
static getExtraSharedWritePaths({ global = false } = {}) {
|
|
46056
|
-
return
|
|
46784
|
+
return [OpencodeMcp.getSettablePaths({ global })];
|
|
46057
46785
|
}
|
|
46058
46786
|
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
|
|
46059
46787
|
const paths = this.getSettablePaths({ global });
|
|
@@ -46070,7 +46798,8 @@ var OpenCodeRule = class OpenCodeRule extends ToolRule {
|
|
|
46070
46798
|
});
|
|
46071
46799
|
}
|
|
46072
46800
|
if (!paths.nonRoot) throw new Error(`nonRoot path is not set for ${relativeFilePath}`);
|
|
46073
|
-
const
|
|
46801
|
+
const relativePath = (0, node_path.join)(paths.nonRoot.relativeDirPath, relativeFilePath);
|
|
46802
|
+
const fileContent = await readFileContent((0, node_path.join)(outputRoot, relativePath));
|
|
46074
46803
|
return new OpenCodeRule({
|
|
46075
46804
|
outputRoot,
|
|
46076
46805
|
relativeDirPath: paths.nonRoot.relativeDirPath,
|
|
@@ -46185,7 +46914,8 @@ var PiRule = class PiRule extends ToolRule {
|
|
|
46185
46914
|
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
|
|
46186
46915
|
const { root, appendSystemPrompt } = this.getSettablePaths({ global });
|
|
46187
46916
|
if (relativeFilePath === "APPEND_SYSTEM.md") {
|
|
46188
|
-
const
|
|
46917
|
+
const relativePath = (0, node_path.join)(appendSystemPrompt.relativeDirPath, appendSystemPrompt.relativeFilePath);
|
|
46918
|
+
const fileContent = await readFileContent((0, node_path.join)(outputRoot, relativePath));
|
|
46189
46919
|
return new PiRule({
|
|
46190
46920
|
outputRoot,
|
|
46191
46921
|
relativeDirPath: appendSystemPrompt.relativeDirPath,
|
|
@@ -46196,7 +46926,8 @@ var PiRule = class PiRule extends ToolRule {
|
|
|
46196
46926
|
appendSystemPrompt: true
|
|
46197
46927
|
});
|
|
46198
46928
|
}
|
|
46199
|
-
const
|
|
46929
|
+
const relativePath = (0, node_path.join)(root.relativeDirPath, root.relativeFilePath);
|
|
46930
|
+
const fileContent = await readFileContent((0, node_path.join)(outputRoot, relativePath));
|
|
46200
46931
|
return new PiRule({
|
|
46201
46932
|
outputRoot,
|
|
46202
46933
|
relativeDirPath: root.relativeDirPath,
|
|
@@ -46496,7 +47227,7 @@ var QwencodeRule = class QwencodeRule extends ToolRule {
|
|
|
46496
47227
|
* The personal local context file lives under `.qwen/`, not at the project
|
|
46497
47228
|
* root where the settable root path points, so override the deletion glob.
|
|
46498
47229
|
*/
|
|
46499
|
-
static
|
|
47230
|
+
static getLocalRootFileGlob({ outputRoot, fileName }) {
|
|
46500
47231
|
return (0, node_path.join)(outputRoot, QWENCODE_DIR, fileName);
|
|
46501
47232
|
}
|
|
46502
47233
|
};
|
|
@@ -46516,9 +47247,51 @@ var ReasonixRule = class ReasonixRule extends ToolRule {
|
|
|
46516
47247
|
relativeFilePath: REASONIX_RULE_FILE_NAME
|
|
46517
47248
|
} };
|
|
46518
47249
|
}
|
|
46519
|
-
|
|
47250
|
+
/**
|
|
47251
|
+
* Context Engine v2 (v1.18.0) walks from the workspace root to the target
|
|
47252
|
+
* path loading per-directory instruction files, so nested `REASONIX.md`
|
|
47253
|
+
* files are a real scoping surface: "Deeper directories beat broader
|
|
47254
|
+
* directories." The scan mirrors the AGENTS.md standard's nested discovery
|
|
47255
|
+
* (same exclusions, import-only, project scope).
|
|
47256
|
+
* @see https://github.com/esengine/DeepSeek-Reasonix/blob/v1.18.0/docs/SESSION_MEMORY_RETRIEVAL.md
|
|
47257
|
+
*/
|
|
47258
|
+
static getNestedFilePatterns({ outputRoot }) {
|
|
47259
|
+
const root = toPosixPath(outputRoot);
|
|
47260
|
+
return {
|
|
47261
|
+
include: [`${root}/**/${REASONIX_RULE_FILE_NAME}`],
|
|
47262
|
+
ignore: [
|
|
47263
|
+
`${root}/${REASONIX_RULE_FILE_NAME}`,
|
|
47264
|
+
`${root}/**/.*/**`,
|
|
47265
|
+
...NESTED_SCAN_EXCLUDED_DIRS_ANY_DEPTH.map((dir) => `${root}/**/${dir}/**`),
|
|
47266
|
+
...NESTED_SCAN_EXCLUDED_ROOT_DIRS.map((dir) => `${root}/${dir}/**`)
|
|
47267
|
+
]
|
|
47268
|
+
};
|
|
47269
|
+
}
|
|
47270
|
+
/**
|
|
47271
|
+
* The subproject directory this rule scopes, or `undefined` for the root
|
|
47272
|
+
* file (project or global).
|
|
47273
|
+
*/
|
|
47274
|
+
getSubprojectPath() {
|
|
47275
|
+
if (this.isRoot()) return;
|
|
47276
|
+
const relativeDirPath = toPosixPath(this.getRelativeDirPath());
|
|
47277
|
+
if (relativeDirPath === "." || relativeDirPath === "" || relativeDirPath.startsWith(".")) return;
|
|
47278
|
+
return relativeDirPath;
|
|
47279
|
+
}
|
|
47280
|
+
static async fromFile({ outputRoot = process.cwd(), relativeFilePath: _relativeFilePath, relativeDirPath: overrideDirPath, validate = true, global = false }) {
|
|
46520
47281
|
const { root } = this.getSettablePaths({ global });
|
|
46521
|
-
|
|
47282
|
+
if (overrideDirPath !== void 0 && overrideDirPath !== root.relativeDirPath && overrideDirPath !== ".") {
|
|
47283
|
+
const fileContent = await readFileContent((0, node_path.join)(outputRoot, overrideDirPath, REASONIX_RULE_FILE_NAME));
|
|
47284
|
+
return new ReasonixRule({
|
|
47285
|
+
outputRoot,
|
|
47286
|
+
relativeDirPath: overrideDirPath,
|
|
47287
|
+
relativeFilePath: REASONIX_RULE_FILE_NAME,
|
|
47288
|
+
fileContent,
|
|
47289
|
+
validate,
|
|
47290
|
+
root: false
|
|
47291
|
+
});
|
|
47292
|
+
}
|
|
47293
|
+
const relativePath = (0, node_path.join)(root.relativeDirPath, root.relativeFilePath);
|
|
47294
|
+
const fileContent = await readFileContent((0, node_path.join)(outputRoot, relativePath));
|
|
46522
47295
|
return new ReasonixRule({
|
|
46523
47296
|
outputRoot,
|
|
46524
47297
|
relativeDirPath: root.relativeDirPath,
|
|
@@ -46530,7 +47303,17 @@ var ReasonixRule = class ReasonixRule extends ToolRule {
|
|
|
46530
47303
|
}
|
|
46531
47304
|
static fromRulesyncRule({ outputRoot = process.cwd(), rulesyncRule, validate = true, global = false }) {
|
|
46532
47305
|
const { root } = this.getSettablePaths({ global });
|
|
46533
|
-
const
|
|
47306
|
+
const frontmatter = rulesyncRule.getFrontmatter();
|
|
47307
|
+
const isRoot = frontmatter.root ?? false;
|
|
47308
|
+
const subprojectPath = frontmatter.agentsmd?.subprojectPath;
|
|
47309
|
+
if (!global && !isRoot && subprojectPath) return new ReasonixRule({
|
|
47310
|
+
outputRoot,
|
|
47311
|
+
relativeDirPath: (0, node_path.join)(subprojectPath),
|
|
47312
|
+
relativeFilePath: REASONIX_RULE_FILE_NAME,
|
|
47313
|
+
fileContent: rulesyncRule.getBody(),
|
|
47314
|
+
validate,
|
|
47315
|
+
root: false
|
|
47316
|
+
});
|
|
46534
47317
|
return new ReasonixRule({
|
|
46535
47318
|
outputRoot,
|
|
46536
47319
|
relativeDirPath: root.relativeDirPath,
|
|
@@ -46541,7 +47324,22 @@ var ReasonixRule = class ReasonixRule extends ToolRule {
|
|
|
46541
47324
|
});
|
|
46542
47325
|
}
|
|
46543
47326
|
toRulesyncRule() {
|
|
46544
|
-
|
|
47327
|
+
const subprojectPath = this.getSubprojectPath();
|
|
47328
|
+
if (subprojectPath === void 0) return this.toRulesyncRuleDefault();
|
|
47329
|
+
const slug = subprojectPath.replaceAll("/", "-");
|
|
47330
|
+
return new RulesyncRule({
|
|
47331
|
+
outputRoot: this.getOutputRoot(),
|
|
47332
|
+
relativeDirPath: RULESYNC_RULES_RELATIVE_DIR_PATH,
|
|
47333
|
+
relativeFilePath: `${slug}-reasonix.md`,
|
|
47334
|
+
frontmatter: {
|
|
47335
|
+
targets: ["reasonix"],
|
|
47336
|
+
root: false,
|
|
47337
|
+
globs: [`${subprojectPath}/**/*`],
|
|
47338
|
+
agentsmd: { subprojectPath }
|
|
47339
|
+
},
|
|
47340
|
+
body: this.getFileContent(),
|
|
47341
|
+
validate: true
|
|
47342
|
+
});
|
|
46545
47343
|
}
|
|
46546
47344
|
validate() {
|
|
46547
47345
|
return {
|
|
@@ -46711,7 +47509,7 @@ var RooRule = class RooRule extends ToolRule {
|
|
|
46711
47509
|
* Glob for the `separate-local-file` deletion; Roo reads `AGENTS.local.md`
|
|
46712
47510
|
* at the project root, not under `.roo/` (mirrors rovodev).
|
|
46713
47511
|
*/
|
|
46714
|
-
static
|
|
47512
|
+
static getLocalRootFileGlob({ outputRoot, fileName }) {
|
|
46715
47513
|
return (0, node_path.join)(outputRoot, fileName);
|
|
46716
47514
|
}
|
|
46717
47515
|
};
|
|
@@ -46843,19 +47641,6 @@ var RovodevRule = class RovodevRule extends ToolRule {
|
|
|
46843
47641
|
}));
|
|
46844
47642
|
}
|
|
46845
47643
|
toRulesyncRule() {
|
|
46846
|
-
if (this.getRelativeFilePath() === "AGENTS.local.md") return new RulesyncRule({
|
|
46847
|
-
outputRoot: this.getOutputRoot(),
|
|
46848
|
-
relativeDirPath: RULESYNC_RULES_RELATIVE_DIR_PATH,
|
|
46849
|
-
relativeFilePath: ROVODEV_LEGACY_RULE_FILE_NAME,
|
|
46850
|
-
frontmatter: {
|
|
46851
|
-
targets: ["rovodev"],
|
|
46852
|
-
root: false,
|
|
46853
|
-
localRoot: true,
|
|
46854
|
-
globs: []
|
|
46855
|
-
},
|
|
46856
|
-
body: this.getFileContent(),
|
|
46857
|
-
validate: true
|
|
46858
|
-
});
|
|
46859
47644
|
if (!this.isRoot()) return new RulesyncRule({
|
|
46860
47645
|
outputRoot: this.getOutputRoot(),
|
|
46861
47646
|
relativeDirPath: RULESYNC_RULES_RELATIVE_DIR_PATH,
|
|
@@ -46912,7 +47697,7 @@ var RovodevRule = class RovodevRule extends ToolRule {
|
|
|
46912
47697
|
};
|
|
46913
47698
|
}
|
|
46914
47699
|
/** Glob for the `separate-local-file` deletion; rovodev writes it at project root, not under `.rovodev/`. */
|
|
46915
|
-
static
|
|
47700
|
+
static getLocalRootFileGlob({ outputRoot, fileName }) {
|
|
46916
47701
|
return (0, node_path.join)(outputRoot, fileName);
|
|
46917
47702
|
}
|
|
46918
47703
|
};
|
|
@@ -47114,7 +47899,8 @@ var WarpRule = class WarpRule extends ToolRule {
|
|
|
47114
47899
|
}
|
|
47115
47900
|
static async fromFile({ outputRoot = process.cwd(), relativeFilePath: _relativeFilePath, validate = true, global = false }) {
|
|
47116
47901
|
const { root } = this.getSettablePaths({ global });
|
|
47117
|
-
const
|
|
47902
|
+
const relativePath = (0, node_path.join)(root.relativeDirPath, root.relativeFilePath);
|
|
47903
|
+
const fileContent = await readFileContent((0, node_path.join)(outputRoot, relativePath));
|
|
47118
47904
|
return new WarpRule({
|
|
47119
47905
|
outputRoot,
|
|
47120
47906
|
relativeDirPath: root.relativeDirPath,
|
|
@@ -47239,6 +48025,27 @@ var ZedRule = class ZedRule extends ToolRule {
|
|
|
47239
48025
|
}
|
|
47240
48026
|
};
|
|
47241
48027
|
//#endregion
|
|
48028
|
+
//#region src/features/rules/zoocode-rule.ts
|
|
48029
|
+
/**
|
|
48030
|
+
* Rule generator for **Zoo Code**, the community continuation of Roo Code
|
|
48031
|
+
* (named by the Roo shutdown notice; releases continue Roo's numbering from
|
|
48032
|
+
* v3.54.0). Zoo Code still resolves `~/.roo` and the project `.roo/` layout —
|
|
48033
|
+
* the `.zoo` renaming is confined to provider/auth code — so this target
|
|
48034
|
+
* reuses {@link RooRule}'s behavior verbatim and only narrows the targeting to
|
|
48035
|
+
* the `zoocode` tool name.
|
|
48036
|
+
*
|
|
48037
|
+
* @see https://github.com/Zoo-Code-Org/Zoo-Code
|
|
48038
|
+
* @see https://docs.zoocode.dev
|
|
48039
|
+
*/
|
|
48040
|
+
var ZoocodeRule = class extends RooRule {
|
|
48041
|
+
static isTargetedByRulesyncRule(rulesyncRule) {
|
|
48042
|
+
return this.isTargetedByRulesyncRuleDefault({
|
|
48043
|
+
rulesyncRule,
|
|
48044
|
+
toolTarget: "zoocode"
|
|
48045
|
+
});
|
|
48046
|
+
}
|
|
48047
|
+
};
|
|
48048
|
+
//#endregion
|
|
47242
48049
|
//#region src/features/rules/rules-processor.ts
|
|
47243
48050
|
const RulesProcessorToolTargetSchema = zod_mini.z.enum(rulesProcessorToolTargetTuple);
|
|
47244
48051
|
const formatRulePaths = (rules) => rules.map((r) => (0, node_path.join)(r.getRelativeDirPath(), r.getRelativeFilePath())).join(", ");
|
|
@@ -47501,6 +48308,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
47501
48308
|
supportsGlobal: true,
|
|
47502
48309
|
ruleDiscoveryMode: "toon",
|
|
47503
48310
|
mcpInstructionsRegistrar: OpencodeMcp,
|
|
48311
|
+
mcpInstructionsRegistrarGlobal: true,
|
|
47504
48312
|
collisionPolicy: "compose"
|
|
47505
48313
|
}
|
|
47506
48314
|
}],
|
|
@@ -47565,6 +48373,16 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
47565
48373
|
localRootFileName: "AGENTS.local.md"
|
|
47566
48374
|
}
|
|
47567
48375
|
}],
|
|
48376
|
+
["zoocode", {
|
|
48377
|
+
class: ZoocodeRule,
|
|
48378
|
+
meta: {
|
|
48379
|
+
extension: "md",
|
|
48380
|
+
supportsGlobal: true,
|
|
48381
|
+
ruleDiscoveryMode: "auto",
|
|
48382
|
+
localRootMode: "separate-local-file",
|
|
48383
|
+
localRootFileName: "AGENTS.local.md"
|
|
48384
|
+
}
|
|
48385
|
+
}],
|
|
47568
48386
|
["takt", {
|
|
47569
48387
|
class: TaktRule,
|
|
47570
48388
|
meta: {
|
|
@@ -47737,18 +48555,19 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
47737
48555
|
/**
|
|
47738
48556
|
* Non-root rules of some tools are not auto-loaded; the tool's MCP feature
|
|
47739
48557
|
* registers them in its shared config's `instructions` key. The root rule is
|
|
47740
|
-
* auto-loaded and never registered.
|
|
48558
|
+
* auto-loaded and never registered. Global scope opt-in via
|
|
48559
|
+
* `mcpInstructionsRegistrarGlobal`.
|
|
47741
48560
|
*/
|
|
47742
48561
|
async buildMcpInstructionFiles({ toolRules, meta }) {
|
|
47743
|
-
if (!meta.mcpInstructionsRegistrar || this.global) return [];
|
|
48562
|
+
if (!meta.mcpInstructionsRegistrar || this.global && !meta.mcpInstructionsRegistrarGlobal) return [];
|
|
47744
48563
|
const instructionPaths = toolRules.filter((rule) => !rule.isRoot() && !rule.isExcludedFromRootReferences()).map((rule) => toPosixPath((0, node_path.join)(rule.getRelativeDirPath(), rule.getRelativeFilePath())));
|
|
47745
|
-
|
|
47746
|
-
return [await meta.mcpInstructionsRegistrar.fromInstructions({
|
|
48564
|
+
const registered = await meta.mcpInstructionsRegistrar.fromInstructions({
|
|
47747
48565
|
outputRoot: this.outputRoot,
|
|
47748
48566
|
instructions: instructionPaths,
|
|
47749
48567
|
validate: true,
|
|
47750
48568
|
global: this.global
|
|
47751
|
-
})
|
|
48569
|
+
});
|
|
48570
|
+
return registered ? [registered] : [];
|
|
47752
48571
|
}
|
|
47753
48572
|
/**
|
|
47754
48573
|
* For tools that don't create a separate conventions rule, prepend the
|
|
@@ -47879,53 +48698,58 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
47879
48698
|
const rootRule = toolRules.find((rule) => rule.isRoot());
|
|
47880
48699
|
if (rootRule) rootRule.setFileContent(rootRule.getFileContent() + "\n\n" + localRootBody);
|
|
47881
48700
|
}
|
|
47882
|
-
buildLocalRootFile({ factory, fileName, body }) {
|
|
48701
|
+
buildLocalRootFile({ factory, fileName, body, relativeDirPath, localRoot = false }) {
|
|
47883
48702
|
if (factory.class === ClaudecodeRule) {
|
|
47884
48703
|
const paths = ClaudecodeRule.getSettablePaths({ global: this.global });
|
|
47885
48704
|
return new ClaudecodeRule({
|
|
47886
48705
|
outputRoot: this.outputRoot,
|
|
47887
|
-
relativeDirPath: paths.root.relativeDirPath,
|
|
48706
|
+
relativeDirPath: relativeDirPath ?? paths.root.relativeDirPath,
|
|
47888
48707
|
relativeFilePath: fileName,
|
|
47889
48708
|
frontmatter: {},
|
|
47890
48709
|
body,
|
|
47891
48710
|
validate: true,
|
|
47892
|
-
root: true
|
|
48711
|
+
root: true,
|
|
48712
|
+
localRoot
|
|
47893
48713
|
});
|
|
47894
48714
|
}
|
|
47895
48715
|
if (factory.class === ClaudecodeLegacyRule) {
|
|
47896
48716
|
const paths = ClaudecodeLegacyRule.getSettablePaths({ global: this.global });
|
|
47897
48717
|
return new ClaudecodeLegacyRule({
|
|
47898
48718
|
outputRoot: this.outputRoot,
|
|
47899
|
-
relativeDirPath: paths.root.relativeDirPath,
|
|
48719
|
+
relativeDirPath: relativeDirPath ?? paths.root.relativeDirPath,
|
|
47900
48720
|
relativeFilePath: fileName,
|
|
47901
48721
|
fileContent: body,
|
|
47902
48722
|
validate: true,
|
|
47903
|
-
root: true
|
|
48723
|
+
root: true,
|
|
48724
|
+
localRoot
|
|
47904
48725
|
});
|
|
47905
48726
|
}
|
|
47906
48727
|
if (factory.class === RovodevRule) return new RovodevRule({
|
|
47907
48728
|
outputRoot: this.outputRoot,
|
|
47908
|
-
relativeDirPath: ".",
|
|
48729
|
+
relativeDirPath: relativeDirPath ?? ".",
|
|
47909
48730
|
relativeFilePath: fileName,
|
|
47910
48731
|
fileContent: body,
|
|
47911
48732
|
validate: true,
|
|
47912
|
-
root: true
|
|
48733
|
+
root: true,
|
|
48734
|
+
localRoot
|
|
47913
48735
|
});
|
|
47914
48736
|
if (factory.class === RooRule) return new RooRule({
|
|
47915
48737
|
outputRoot: this.outputRoot,
|
|
47916
|
-
relativeDirPath: ".",
|
|
48738
|
+
relativeDirPath: relativeDirPath ?? ".",
|
|
47917
48739
|
relativeFilePath: fileName,
|
|
47918
48740
|
fileContent: body,
|
|
47919
48741
|
validate: true,
|
|
47920
|
-
root: true
|
|
48742
|
+
root: true,
|
|
48743
|
+
localRoot
|
|
47921
48744
|
});
|
|
47922
48745
|
if (factory.class === QwencodeRule) return new QwencodeRule({
|
|
47923
48746
|
outputRoot: this.outputRoot,
|
|
47924
|
-
relativeDirPath:
|
|
48747
|
+
relativeDirPath: relativeDirPath ?? ".qwen",
|
|
47925
48748
|
relativeFilePath: fileName,
|
|
47926
48749
|
fileContent: body,
|
|
47927
48750
|
validate: true,
|
|
47928
|
-
root: true
|
|
48751
|
+
root: true,
|
|
48752
|
+
localRoot
|
|
47929
48753
|
});
|
|
47930
48754
|
return null;
|
|
47931
48755
|
}
|
|
@@ -47963,6 +48787,7 @@ As this project's AI coding tool, you must follow the additional conventions bel
|
|
|
47963
48787
|
async convertToolFilesToRulesyncFiles(toolFiles) {
|
|
47964
48788
|
const toolRules = toolFiles.filter((file) => file instanceof ToolRule);
|
|
47965
48789
|
const rulesyncRules = toolRules.map((toolRule) => {
|
|
48790
|
+
if (toolRule.isLocalRoot()) return toolRule.toLocalRootRulesyncRule({ targets: [this.toolTarget] });
|
|
47966
48791
|
return toolRule.toRulesyncRule();
|
|
47967
48792
|
});
|
|
47968
48793
|
const claimedBy = /* @__PURE__ */ new Map();
|
|
@@ -48096,20 +48921,34 @@ As this project's AI coding tool, you must follow the additional conventions bel
|
|
|
48096
48921
|
})();
|
|
48097
48922
|
this.logger.debug(`Found ${rootToolRules.length} root tool rule files`);
|
|
48098
48923
|
const localRootToolRules = await (async () => {
|
|
48099
|
-
if (
|
|
48924
|
+
if (this.global || factory.meta.localRootMode !== "separate-local-file" || !factory.meta.localRootFileName) return [];
|
|
48100
48925
|
const fileName = factory.meta.localRootFileName;
|
|
48101
|
-
|
|
48102
|
-
|
|
48926
|
+
const filePaths = await (async () => {
|
|
48927
|
+
if (factory.class.getLocalRootFileGlob) return await findFilesByGlobs(factory.class.getLocalRootFileGlob({
|
|
48103
48928
|
outputRoot: this.outputRoot,
|
|
48104
48929
|
fileName
|
|
48105
48930
|
}));
|
|
48106
|
-
|
|
48107
|
-
|
|
48108
|
-
|
|
48109
|
-
|
|
48110
|
-
return
|
|
48931
|
+
if (!settablePaths.root) return [];
|
|
48932
|
+
return await findFilesWithFallback((0, node_path.join)(this.outputRoot, settablePaths.root.relativeDirPath ?? ".", fileName), settablePaths.alternativeRoots, (alt) => (0, node_path.join)(this.outputRoot, alt.relativeDirPath, fileName));
|
|
48933
|
+
})();
|
|
48934
|
+
if (forDeletion) return buildDeletionRulesFromPaths(filePaths);
|
|
48935
|
+
return (await Promise.all(filePaths.map(async (filePath) => {
|
|
48936
|
+
const relativeDirPath = resolveRelativeDirPath(filePath);
|
|
48937
|
+
checkPathTraversal({
|
|
48938
|
+
relativePath: relativeDirPath,
|
|
48939
|
+
intendedRootDir: this.outputRoot
|
|
48940
|
+
});
|
|
48941
|
+
const body = await readFileContent(filePath);
|
|
48942
|
+
return this.buildLocalRootFile({
|
|
48943
|
+
factory,
|
|
48944
|
+
fileName: (0, node_path.basename)(filePath),
|
|
48945
|
+
body,
|
|
48946
|
+
relativeDirPath,
|
|
48947
|
+
localRoot: true
|
|
48948
|
+
});
|
|
48949
|
+
}))).filter((rule) => rule !== null);
|
|
48111
48950
|
})();
|
|
48112
|
-
this.logger.debug(`Found ${localRootToolRules.length} local root tool rule files
|
|
48951
|
+
this.logger.debug(`Found ${localRootToolRules.length} local root tool rule files`);
|
|
48113
48952
|
const rootMirrorDeletionRules = await (async () => {
|
|
48114
48953
|
const rootMirror = factory.class.getRootMirror?.();
|
|
48115
48954
|
if (!forDeletion || this.global || !rootMirror) return [];
|
|
@@ -48235,13 +49074,14 @@ As this project's AI coding tool, you must follow the additional conventions bel
|
|
|
48235
49074
|
const lines = [];
|
|
48236
49075
|
lines.push("Please also reference the following rules as needed. The list below is provided in TOON format, and `@` stands for the project root directory.");
|
|
48237
49076
|
lines.push("");
|
|
48238
|
-
const
|
|
49077
|
+
const rules = toolRulesWithoutRoot.map((toolRule) => {
|
|
48239
49078
|
const frontmatter = toolRule.toRulesyncRule().getFrontmatter();
|
|
48240
49079
|
const rule = { path: `@${toolRule.getRelativePathFromCwd()}` };
|
|
48241
49080
|
if (frontmatter.description) rule.description = frontmatter.description;
|
|
48242
49081
|
if (frontmatter.globs && frontmatter.globs.length > 0) rule.applyTo = frontmatter.globs;
|
|
48243
49082
|
return rule;
|
|
48244
|
-
})
|
|
49083
|
+
});
|
|
49084
|
+
const toonContent = (0, _toon_format_toon.encode)({ rules });
|
|
48245
49085
|
lines.push(toonContent);
|
|
48246
49086
|
return lines.join("\n") + "\n\n";
|
|
48247
49087
|
}
|
|
@@ -49403,8 +50243,8 @@ async function generateIgnoreCore(params) {
|
|
|
49403
50243
|
allPaths.push(...result.paths);
|
|
49404
50244
|
if (result.hasDiff) hasDiff = true;
|
|
49405
50245
|
} catch (error) {
|
|
49406
|
-
logger.
|
|
49407
|
-
|
|
50246
|
+
logger.error(`Failed to generate ${toolTarget} ignore files for ${outputRoot}: ${formatError(error)}`);
|
|
50247
|
+
throw error;
|
|
49408
50248
|
}
|
|
49409
50249
|
}
|
|
49410
50250
|
return {
|
|
@@ -49676,8 +50516,8 @@ async function generatePermissionsCore(params) {
|
|
|
49676
50516
|
allPaths.push(...result.paths);
|
|
49677
50517
|
if (result.hasDiff) hasDiff = true;
|
|
49678
50518
|
} catch (error) {
|
|
49679
|
-
logger.
|
|
49680
|
-
|
|
50519
|
+
logger.error(`Failed to generate ${toolTarget} permissions files for ${outputRoot}: ${formatError(error)}`);
|
|
50520
|
+
throw error;
|
|
49681
50521
|
}
|
|
49682
50522
|
}
|
|
49683
50523
|
return {
|