rulesync 16.3.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 +300 -54
- package/dist/cli/index.js +300 -55
- package/dist/cli/index.js.map +1 -1
- package/dist/{import-Bj5HxY9p.js → import-BlwypG9v.js} +1286 -376
- package/dist/import-BlwypG9v.js.map +1 -0
- package/dist/{import-DD-GRmB9.cjs → import-D87qlRUj.cjs} +1312 -390
- 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 +20 -17
- package/dist/import-Bj5HxY9p.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",
|
|
@@ -2381,6 +2395,7 @@ const PI_HOOK_EVENTS = [
|
|
|
2381
2395
|
"preToolUse",
|
|
2382
2396
|
"postToolUse",
|
|
2383
2397
|
"preModelInvocation",
|
|
2398
|
+
"postModelInvocation",
|
|
2384
2399
|
"beforeSubmitPrompt",
|
|
2385
2400
|
"stop",
|
|
2386
2401
|
"preCompact",
|
|
@@ -3054,7 +3069,6 @@ const CANONICAL_TO_OPENCODE_EVENT_NAMES = {
|
|
|
3054
3069
|
postToolUse: "tool.execute.after",
|
|
3055
3070
|
stop: "session.idle",
|
|
3056
3071
|
afterFileEdit: "file.edited",
|
|
3057
|
-
afterShellExecution: "command.executed",
|
|
3058
3072
|
permissionRequest: "permission.asked",
|
|
3059
3073
|
preCompact: "experimental.session.compacting",
|
|
3060
3074
|
postCompact: "session.compacted",
|
|
@@ -3087,6 +3101,7 @@ const CANONICAL_TO_PI_EVENT_NAMES = {
|
|
|
3087
3101
|
preToolUse: "tool_call",
|
|
3088
3102
|
postToolUse: "tool_result",
|
|
3089
3103
|
preModelInvocation: "context",
|
|
3104
|
+
postModelInvocation: "message_end",
|
|
3090
3105
|
beforeSubmitPrompt: "input",
|
|
3091
3106
|
stop: "agent_end",
|
|
3092
3107
|
preCompact: "session_before_compact",
|
|
@@ -4120,10 +4135,22 @@ const ClaudecodePermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
4120
4135
|
* category; the shared `permission` block still drives the base permission and
|
|
4121
4136
|
* allow/deny lists. Keyed by canonical category (e.g. `bash`, `edit`).
|
|
4122
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
|
+
*
|
|
4123
4145
|
* @example
|
|
4124
4146
|
* { "permission": { "bash": { "sensitive_patterns": ["rm *", "sudo *"] } } }
|
|
4147
|
+
* @example
|
|
4148
|
+
* { "enabled_tools": ["bash", "read_file", "grep"] }
|
|
4125
4149
|
*/
|
|
4126
|
-
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
|
+
});
|
|
4127
4154
|
/**
|
|
4128
4155
|
* Tool-scoped override block for Cursor CLI. Cursor's `cli.json` carries scalar
|
|
4129
4156
|
* autonomy settings with no canonical permission category — `approvalMode`
|
|
@@ -4195,7 +4222,10 @@ const QwencodePermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
4195
4222
|
const ReasonixPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
4196
4223
|
permission: zod_mini.z.optional(ToolScopedPermissionSchema),
|
|
4197
4224
|
sandbox: zod_mini.z.optional(zod_mini.z.looseObject({})),
|
|
4198
|
-
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()))
|
|
4199
4229
|
});
|
|
4200
4230
|
/**
|
|
4201
4231
|
* Tool-scoped override block for Factory Droid. Factory Droid's `settings.json`
|
|
@@ -4226,12 +4256,69 @@ const FactorydroidPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
4226
4256
|
* `agent_mode_execute_readonly_commands` (a read-only auto-execution boolean).
|
|
4227
4257
|
* Fields placed here are merged into `[agents.profiles]` of Warp's global
|
|
4228
4258
|
* `settings.toml`, while the shared `permission` block continues to drive the
|
|
4229
|
-
*
|
|
4230
|
-
*
|
|
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.
|
|
4231
4265
|
*
|
|
4232
4266
|
* @example
|
|
4233
|
-
* {
|
|
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).
|
|
4234
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
|
+
});
|
|
4235
4322
|
const WarpPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
4236
4323
|
permission: zod_mini.z.optional(ToolScopedPermissionSchema),
|
|
4237
4324
|
agent_mode_coding_permissions: zod_mini.z.optional(zod_mini.z.enum([
|
|
@@ -4240,7 +4327,8 @@ const WarpPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
4240
4327
|
"allow_reading_specific_files"
|
|
4241
4328
|
])),
|
|
4242
4329
|
agent_mode_coding_file_read_allowlist: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
4243
|
-
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)
|
|
4244
4332
|
});
|
|
4245
4333
|
/**
|
|
4246
4334
|
* The actions Junie's allowlist accepts. Verified against the shipped Junie
|
|
@@ -4972,7 +5060,8 @@ var AiDir = class {
|
|
|
4972
5060
|
getDirPath() {
|
|
4973
5061
|
const fullPath = node_path.default.join(this.outputRoot, this.relativeDirPath, this.dirName);
|
|
4974
5062
|
const resolvedFull = (0, node_path.resolve)(fullPath);
|
|
4975
|
-
const
|
|
5063
|
+
const resolvedBase = (0, node_path.resolve)(this.outputRoot);
|
|
5064
|
+
const rel = (0, node_path.relative)(resolvedBase, resolvedFull);
|
|
4976
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}"`);
|
|
4977
5066
|
return fullPath;
|
|
4978
5067
|
}
|
|
@@ -5255,6 +5344,9 @@ const RulesyncSubagentFrontmatterSchema = zod_mini.z.looseObject({
|
|
|
5255
5344
|
customInstructions: zod_mini.z.optional(zod_mini.z.string()),
|
|
5256
5345
|
groups: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.unknown()))
|
|
5257
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())) })),
|
|
5258
5350
|
vibe: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
5259
5351
|
agent_type: zod_mini.z.optional(zod_mini.z.enum(["agent", "subagent"])),
|
|
5260
5352
|
display_name: zod_mini.z.optional(zod_mini.z.string()),
|
|
@@ -5394,7 +5486,6 @@ const ROVODEV_SKILLS_DIR_PATH = (0, node_path.join)(ROVODEV_DIR, "skills");
|
|
|
5394
5486
|
const ROVODEV_SUBAGENTS_DIR_PATH = (0, node_path.join)(ROVODEV_DIR, "subagents");
|
|
5395
5487
|
const ROVODEV_MODULAR_RULES_DIR_PATH = (0, node_path.join)(ROVODEV_DIR, ".rulesync", "modular-rules");
|
|
5396
5488
|
const ROVODEV_RULE_FILE_NAME = "AGENTS.md";
|
|
5397
|
-
const ROVODEV_LEGACY_RULE_FILE_NAME = "AGENTS.local.md";
|
|
5398
5489
|
const ROVODEV_MCP_FILE_NAME = "mcp.json";
|
|
5399
5490
|
const ROVODEV_CONFIG_FILE_NAME = "config.yml";
|
|
5400
5491
|
const ROVODEV_AGENTS_SKILLS_DIR_PATH = (0, node_path.join)(".agents", "skills");
|
|
@@ -6613,6 +6704,7 @@ const KIMI_CODE_CONFIG_SHARED_FILE_KEY = ".kimi-code/config.toml";
|
|
|
6613
6704
|
const KIMI_CODE_HOME_CONFIG_SHARED_FILE_KEY = "config.toml";
|
|
6614
6705
|
const REASONIX_PROJECT_CONFIG_SHARED_FILE_KEY = "reasonix.toml";
|
|
6615
6706
|
const REASONIX_GLOBAL_CONFIG_SHARED_FILE_KEY = ".reasonix/config.toml";
|
|
6707
|
+
const ROVODEV_CONFIG_SHARED_FILE_KEY = ".rovodev/config.yml";
|
|
6616
6708
|
/**
|
|
6617
6709
|
* Build the `SHARED_CONFIG_OWNERSHIP` lookup key from a tool's settable paths.
|
|
6618
6710
|
* Mirrors `sharedFileKey` in `src/lib/shared-file-derive.ts` (kept separate so
|
|
@@ -6852,24 +6944,14 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
6852
6944
|
},
|
|
6853
6945
|
".devin/config.json": {
|
|
6854
6946
|
format: "json",
|
|
6855
|
-
features: {
|
|
6856
|
-
|
|
6857
|
-
|
|
6858
|
-
|
|
6859
|
-
},
|
|
6860
|
-
permissions: {
|
|
6861
|
-
kind: "replace-owned-keys",
|
|
6862
|
-
ownedKeys: ["permissions"]
|
|
6863
|
-
}
|
|
6864
|
-
}
|
|
6947
|
+
features: { permissions: {
|
|
6948
|
+
kind: "replace-owned-keys",
|
|
6949
|
+
ownedKeys: ["permissions"]
|
|
6950
|
+
} }
|
|
6865
6951
|
},
|
|
6866
6952
|
".config/devin/config.json": {
|
|
6867
6953
|
format: "json",
|
|
6868
6954
|
features: {
|
|
6869
|
-
mcp: {
|
|
6870
|
-
kind: "replace-owned-keys",
|
|
6871
|
-
ownedKeys: ["mcpServers"]
|
|
6872
|
-
},
|
|
6873
6955
|
hooks: {
|
|
6874
6956
|
kind: "replace-owned-keys",
|
|
6875
6957
|
ownedKeys: ["hooks"]
|
|
@@ -6962,6 +7044,10 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
6962
7044
|
permissions: {
|
|
6963
7045
|
kind: "replace-owned-keys",
|
|
6964
7046
|
ownedKeys: ["permission"]
|
|
7047
|
+
},
|
|
7048
|
+
rules: {
|
|
7049
|
+
kind: "replace-owned-keys",
|
|
7050
|
+
ownedKeys: ["instructions"]
|
|
6965
7051
|
}
|
|
6966
7052
|
}
|
|
6967
7053
|
},
|
|
@@ -7079,6 +7165,19 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
7079
7165
|
]
|
|
7080
7166
|
}
|
|
7081
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
|
+
}
|
|
7082
7181
|
}
|
|
7083
7182
|
};
|
|
7084
7183
|
/**
|
|
@@ -7500,7 +7599,8 @@ const toolCheckFactories = /* @__PURE__ */ new Map([
|
|
|
7500
7599
|
class: CursorCheck,
|
|
7501
7600
|
meta: {
|
|
7502
7601
|
supportsGlobal: false,
|
|
7503
|
-
filePattern: CURSOR_BUGBOT_FILE_NAME
|
|
7602
|
+
filePattern: CURSOR_BUGBOT_FILE_NAME,
|
|
7603
|
+
committedOutput: true
|
|
7504
7604
|
}
|
|
7505
7605
|
}],
|
|
7506
7606
|
["hermesagent", {
|
|
@@ -7514,7 +7614,8 @@ const toolCheckFactories = /* @__PURE__ */ new Map([
|
|
|
7514
7614
|
class: RovodevCheck,
|
|
7515
7615
|
meta: {
|
|
7516
7616
|
supportsGlobal: false,
|
|
7517
|
-
filePattern: ROVODEV_REVIEW_AGENT_FILE_NAME
|
|
7617
|
+
filePattern: ROVODEV_REVIEW_AGENT_FILE_NAME,
|
|
7618
|
+
committedOutput: true
|
|
7518
7619
|
}
|
|
7519
7620
|
}],
|
|
7520
7621
|
["takt", {
|
|
@@ -9069,6 +9170,7 @@ const DEVIN_GLOBAL_CONFIG_DIR_PATH = (0, node_path.join)(".config", "devin");
|
|
|
9069
9170
|
const DEVIN_GLOBAL_AGENTS_DIR_PATH = (0, node_path.join)(DEVIN_GLOBAL_CONFIG_DIR_PATH, "agents");
|
|
9070
9171
|
const DEVIN_GLOBAL_SKILLS_DIR_PATH = (0, node_path.join)(DEVIN_GLOBAL_CONFIG_DIR_PATH, "skills");
|
|
9071
9172
|
const DEVIN_CONFIG_FILE_NAME = "config.json";
|
|
9173
|
+
const DEVIN_MCP_CONFIG_FILE_NAME = "mcp_config.json";
|
|
9072
9174
|
const DEVIN_HOOKS_V1_FILE_NAME = "hooks.v1.json";
|
|
9073
9175
|
const DEVIN_GLOBAL_AGENTS_FILE_NAME = "AGENTS.md";
|
|
9074
9176
|
const DEVIN_IGNORE_FILE_NAME = ".devinignore";
|
|
@@ -9675,7 +9777,8 @@ var GrokcliCommand = class GrokcliCommand extends ToolCommand {
|
|
|
9675
9777
|
static async validateRulesyncCommands({ inputRoot, rulesyncCommands, logger }) {
|
|
9676
9778
|
const commandNames = new Set(rulesyncCommands.filter((command) => this.isTargetedByRulesyncCommand(command)).map((command) => (0, node_path.basename)(command.getRelativeFilePath(), ".md")));
|
|
9677
9779
|
if (commandNames.size === 0) return;
|
|
9678
|
-
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));
|
|
9679
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.`);
|
|
9680
9783
|
}
|
|
9681
9784
|
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
|
|
@@ -9710,6 +9813,20 @@ var GrokcliCommand = class GrokcliCommand extends ToolCommand {
|
|
|
9710
9813
|
function toolSkillSearchRoots(paths) {
|
|
9711
9814
|
return [paths.relativeDirPath, ...paths.alternativeSkillRoots ?? []];
|
|
9712
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
|
+
}
|
|
9713
9830
|
/** Ordered import roots: managed roots first, followed by read-only discovery roots. */
|
|
9714
9831
|
function toolSkillImportRoots(paths) {
|
|
9715
9832
|
return [...toolSkillSearchRoots(paths), ...paths.importOnlySkillRoots ?? []];
|
|
@@ -11326,6 +11443,7 @@ const QWENCODE_COMMANDS_DIR_PATH = (0, node_path.join)(QWENCODE_DIR, "commands")
|
|
|
11326
11443
|
const QWENCODE_AGENTS_DIR_PATH = (0, node_path.join)(QWENCODE_DIR, "agents");
|
|
11327
11444
|
const QWENCODE_SKILLS_DIR_PATH = (0, node_path.join)(QWENCODE_DIR, "skills");
|
|
11328
11445
|
const QWENCODE_RULE_FILE_NAME = "QWEN.md";
|
|
11446
|
+
const QWENCODE_LOCAL_RULE_FILE_NAME = "QWEN.local.md";
|
|
11329
11447
|
const QWENCODE_IGNORE_FILE_NAME = ".qwenignore";
|
|
11330
11448
|
const QWENCODE_SETTINGS_FILE_NAME = "settings.json";
|
|
11331
11449
|
//#endregion
|
|
@@ -11652,7 +11770,7 @@ var RooCommand = class RooCommand extends ToolCommand {
|
|
|
11652
11770
|
};
|
|
11653
11771
|
const body = rulesyncCommand.getBody();
|
|
11654
11772
|
const fileContent = stringifyFrontmatter(body, rooFrontmatter);
|
|
11655
|
-
return new
|
|
11773
|
+
return new this({
|
|
11656
11774
|
outputRoot,
|
|
11657
11775
|
frontmatter: rooFrontmatter,
|
|
11658
11776
|
body,
|
|
@@ -11689,7 +11807,7 @@ var RooCommand = class RooCommand extends ToolCommand {
|
|
|
11689
11807
|
const { frontmatter, body: content } = parseFrontmatter(fileContent, filePath);
|
|
11690
11808
|
const result = RooCommandFrontmatterSchema.safeParse(frontmatter);
|
|
11691
11809
|
if (!result.success) throw new Error(`Invalid frontmatter in ${filePath}: ${formatError(result.error)}`);
|
|
11692
|
-
return new
|
|
11810
|
+
return new this({
|
|
11693
11811
|
outputRoot,
|
|
11694
11812
|
relativeDirPath: RooCommand.getSettablePaths().relativeDirPath,
|
|
11695
11813
|
relativeFilePath,
|
|
@@ -11700,7 +11818,7 @@ var RooCommand = class RooCommand extends ToolCommand {
|
|
|
11700
11818
|
});
|
|
11701
11819
|
}
|
|
11702
11820
|
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
11703
|
-
return new
|
|
11821
|
+
return new this({
|
|
11704
11822
|
outputRoot,
|
|
11705
11823
|
relativeDirPath,
|
|
11706
11824
|
relativeFilePath,
|
|
@@ -12072,6 +12190,7 @@ const WARP_SKILLS_DIR_PATH = (0, node_path.join)(WARP_DIR, "skills");
|
|
|
12072
12190
|
const WARP_LINUX_DIR = (0, node_path.join)(".config", "warp-terminal");
|
|
12073
12191
|
const WARP_WIN32_DIR = (0, node_path.join)("AppData", "Local", "warp", "Warp", "config");
|
|
12074
12192
|
const WARP_RULE_FILE_NAME = "AGENTS.md";
|
|
12193
|
+
const WARP_GLOBAL_RULE_DIR = ".agents";
|
|
12075
12194
|
const WARP_MCP_FILE_NAME = ".mcp.json";
|
|
12076
12195
|
const WARP_PERMISSIONS_FILE_NAME = "settings.toml";
|
|
12077
12196
|
const WARP_IGNORE_FILE_NAME = ".warpindexingignore";
|
|
@@ -12157,6 +12276,39 @@ var WarpCommand = class WarpCommand extends ToolCommand {
|
|
|
12157
12276
|
}
|
|
12158
12277
|
};
|
|
12159
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
|
|
12160
12312
|
//#region src/features/commands/commands-processor.ts
|
|
12161
12313
|
const CommandsProcessorToolTargetSchema = zod_mini.z.enum(commandsProcessorToolTargetTuple);
|
|
12162
12314
|
/**
|
|
@@ -12416,6 +12568,16 @@ const toolCommandFactories = /* @__PURE__ */ new Map([
|
|
|
12416
12568
|
supportsSubdirectory: true
|
|
12417
12569
|
}
|
|
12418
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
|
+
}],
|
|
12419
12581
|
["rovodev", {
|
|
12420
12582
|
class: RovodevCommand,
|
|
12421
12583
|
meta: {
|
|
@@ -13795,16 +13957,17 @@ var ClaudecodeHooks = class extends ToolHooks {
|
|
|
13795
13957
|
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
13796
13958
|
const existingContent = await readFileContentOrNull(filePath) ?? JSON.stringify({}, null, 2);
|
|
13797
13959
|
const config = rulesyncHooks.getJson();
|
|
13960
|
+
const claudeHooks = canonicalToToolHooks({
|
|
13961
|
+
config,
|
|
13962
|
+
toolOverrideHooks: config.claudecode?.hooks,
|
|
13963
|
+
converterConfig: CLAUDE_CONVERTER_CONFIG,
|
|
13964
|
+
logger
|
|
13965
|
+
});
|
|
13798
13966
|
const fileContent = applySharedConfigPatch({
|
|
13799
13967
|
fileKey: CLAUDE_SETTINGS_SHARED_FILE_KEY,
|
|
13800
13968
|
feature: "hooks",
|
|
13801
13969
|
existingContent,
|
|
13802
|
-
patch: { hooks:
|
|
13803
|
-
config,
|
|
13804
|
-
toolOverrideHooks: config.claudecode?.hooks,
|
|
13805
|
-
converterConfig: CLAUDE_CONVERTER_CONFIG,
|
|
13806
|
-
logger
|
|
13807
|
-
}) },
|
|
13970
|
+
patch: { hooks: claudeHooks },
|
|
13808
13971
|
filePath
|
|
13809
13972
|
});
|
|
13810
13973
|
return new this({
|
|
@@ -14807,9 +14970,9 @@ const DEVIN_CONVERTER_CONFIG = {
|
|
|
14807
14970
|
* - Project scope: `.devin/hooks.v1.json`. This is a standalone file whose top
|
|
14808
14971
|
* level IS the event map (no wrapper key).
|
|
14809
14972
|
* - Global scope: `~/.config/devin/config.json` under the `"hooks"` key. This
|
|
14810
|
-
* file is shared with the
|
|
14811
|
-
*
|
|
14812
|
-
* never deleted in global mode.
|
|
14973
|
+
* file is shared with the permissions (`permissions`) feature (MCP moved to
|
|
14974
|
+
* the dedicated mcp_config.json in v3000.3), so reads and writes merge into
|
|
14975
|
+
* the existing JSON and the file is never deleted in global mode.
|
|
14813
14976
|
*
|
|
14814
14977
|
* @see https://docs.devin.ai/cli/extensibility/hooks/overview
|
|
14815
14978
|
*/
|
|
@@ -15558,6 +15721,30 @@ const NAMED_HOOK_MATCHER_SUBJECTS = {
|
|
|
15558
15721
|
"tool.execute.after": "input.tool",
|
|
15559
15722
|
"experimental.session.compacting": null
|
|
15560
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
|
+
};
|
|
15561
15748
|
function escapeForTemplateLiteral(command) {
|
|
15562
15749
|
return command.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
|
|
15563
15750
|
}
|
|
@@ -15578,15 +15765,19 @@ function validateAndSanitizeMatcher(matcher) {
|
|
|
15578
15765
|
*/
|
|
15579
15766
|
function collectOpencodeStyleHandlers({ effectiveHooks, eventMap, namedEventHandlers, genericEventHandlers }) {
|
|
15580
15767
|
for (const [canonicalEvent, definitions] of Object.entries(effectiveHooks)) {
|
|
15581
|
-
const
|
|
15768
|
+
const shellGate = SHELL_EVENT_TOOL_GATES[canonicalEvent];
|
|
15769
|
+
const toolEvent = shellGate?.toolEvent ?? eventMap[canonicalEvent];
|
|
15582
15770
|
if (!toolEvent) continue;
|
|
15771
|
+
const matcherSupported = !shellGate && Object.hasOwn(NAMED_HOOK_MATCHER_SUBJECTS, toolEvent) && NAMED_HOOK_MATCHER_SUBJECTS[toolEvent] !== null;
|
|
15583
15772
|
const handlers = [];
|
|
15584
15773
|
for (const def of definitions) {
|
|
15585
15774
|
if ((def.type ?? "command") !== "command") continue;
|
|
15586
15775
|
if (!def.command) continue;
|
|
15776
|
+
if (def.matcher && !matcherSupported) continue;
|
|
15587
15777
|
handlers.push({
|
|
15588
15778
|
command: def.command,
|
|
15589
|
-
matcher: def.matcher ? def.matcher : void 0
|
|
15779
|
+
matcher: def.matcher ? def.matcher : void 0,
|
|
15780
|
+
...shellGate ? { toolGate: shellGate.tool } : {}
|
|
15590
15781
|
});
|
|
15591
15782
|
}
|
|
15592
15783
|
if (handlers.length > 0) {
|
|
@@ -15623,7 +15814,11 @@ function buildNamedEventBodyLines(namedEventHandlers) {
|
|
|
15623
15814
|
bodyLines.push(` "${eventName}": async (input) => {`);
|
|
15624
15815
|
for (const handler of handlers) {
|
|
15625
15816
|
const escapedCommand = escapeForTemplateLiteral(handler.command);
|
|
15626
|
-
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) {
|
|
15627
15822
|
const safeMatcher = validateAndSanitizeMatcher(handler.matcher);
|
|
15628
15823
|
bodyLines.push(" {");
|
|
15629
15824
|
bodyLines.push(` const __re = new RegExp("${safeMatcher}");`);
|
|
@@ -16421,6 +16616,13 @@ var OpencodeHooks = class OpencodeHooks extends ToolHooks {
|
|
|
16421
16616
|
*/
|
|
16422
16617
|
const PI_TOOL_EVENTS = /* @__PURE__ */ new Set(["tool_call", "tool_result"]);
|
|
16423
16618
|
/**
|
|
16619
|
+
* Pi extension events that fire for every message role (user, assistant,
|
|
16620
|
+
* toolResult). The generated handler gates these on the assistant role so a
|
|
16621
|
+
* `postModelInvocation` hook runs once per finalized model response rather
|
|
16622
|
+
* than for every message in the conversation.
|
|
16623
|
+
*/
|
|
16624
|
+
const PI_ASSISTANT_MESSAGE_EVENTS = /* @__PURE__ */ new Set(["message_end"]);
|
|
16625
|
+
/**
|
|
16424
16626
|
* Validate a hook matcher as a regular expression and return it as a JS
|
|
16425
16627
|
* string-literal (JSON.stringify quoting) safe to embed in generated code.
|
|
16426
16628
|
*/
|
|
@@ -16461,7 +16663,10 @@ function buildSubscriptionLines(handlerGroups) {
|
|
|
16461
16663
|
const lines = [];
|
|
16462
16664
|
for (const [piEvent, handlers] of Object.entries(handlerGroups)) {
|
|
16463
16665
|
const usesToolName = PI_TOOL_EVENTS.has(piEvent) && handlers.some((h) => h.matcher);
|
|
16464
|
-
|
|
16666
|
+
const gatesOnAssistant = PI_ASSISTANT_MESSAGE_EVENTS.has(piEvent);
|
|
16667
|
+
const usesEvent = usesToolName || gatesOnAssistant;
|
|
16668
|
+
lines.push(` pi.on(${JSON.stringify(piEvent)}, async (${usesEvent ? "event" : ""}) => {`);
|
|
16669
|
+
if (gatesOnAssistant) lines.push(` if (event.message.role !== "assistant") return;`);
|
|
16465
16670
|
for (const handler of handlers) {
|
|
16466
16671
|
const embeddedCommand = JSON.stringify(handler.command);
|
|
16467
16672
|
if (usesToolName && handler.matcher) {
|
|
@@ -16596,6 +16801,7 @@ function canonicalDefToQwencodeHook(def) {
|
|
|
16596
16801
|
const type = def.type ?? "command";
|
|
16597
16802
|
const isHttp = type === "http";
|
|
16598
16803
|
const isCommand = type === "command";
|
|
16804
|
+
const isPrompt = type === "prompt";
|
|
16599
16805
|
return {
|
|
16600
16806
|
type,
|
|
16601
16807
|
...compact({
|
|
@@ -16605,6 +16811,8 @@ function canonicalDefToQwencodeHook(def) {
|
|
|
16605
16811
|
name: def.name,
|
|
16606
16812
|
description: def.description,
|
|
16607
16813
|
statusMessage: def.statusMessage,
|
|
16814
|
+
prompt: isPrompt ? def.prompt : void 0,
|
|
16815
|
+
model: isPrompt ? def.model : void 0,
|
|
16608
16816
|
async: isCommand ? def.async : void 0,
|
|
16609
16817
|
env: isCommand ? def.env : void 0,
|
|
16610
16818
|
shell: isCommand ? def.shell : void 0,
|
|
@@ -16623,7 +16831,7 @@ function canonicalDefToQwencodeHook(def) {
|
|
|
16623
16831
|
* is needed here — commands are passed through verbatim and any such variable
|
|
16624
16832
|
* reference stays intact for Qwen Code to expand.
|
|
16625
16833
|
*/
|
|
16626
|
-
function canonicalToQwencodeHooks(config) {
|
|
16834
|
+
function canonicalToQwencodeHooks(config, logger) {
|
|
16627
16835
|
const qwencodeSupported = new Set(QWENCODE_HOOK_EVENTS);
|
|
16628
16836
|
const sharedHooks = {};
|
|
16629
16837
|
for (const [event, defs] of Object.entries(config.hooks)) if (qwencodeSupported.has(event)) sharedHooks[event] = defs;
|
|
@@ -16643,6 +16851,7 @@ function canonicalToQwencodeHooks(config) {
|
|
|
16643
16851
|
const byMatcher = /* @__PURE__ */ new Map();
|
|
16644
16852
|
for (const def of definitions) {
|
|
16645
16853
|
if (!qwencodeSupportedTypes.has(def.type ?? "command")) continue;
|
|
16854
|
+
if (def.type === "prompt" && !def.prompt) logger?.warn(`Qwen Code prompt hook on '${eventName}' has no 'prompt' field; Qwen Code will load it and fail it at runtime.`);
|
|
16646
16855
|
const key = def.matcher ?? "";
|
|
16647
16856
|
const list = byMatcher.get(key);
|
|
16648
16857
|
if (list) list.push(def);
|
|
@@ -16681,7 +16890,9 @@ const QwencodeHookEntrySchema = zod_mini.z.looseObject({
|
|
|
16681
16890
|
shell: zod_mini.z.optional(zod_mini.z.string()),
|
|
16682
16891
|
headers: zod_mini.z.optional(zod_mini.z.record(zod_mini.z.string(), zod_mini.z.string())),
|
|
16683
16892
|
allowedEnvVars: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
16684
|
-
once: zod_mini.z.optional(zod_mini.z.boolean())
|
|
16893
|
+
once: zod_mini.z.optional(zod_mini.z.boolean()),
|
|
16894
|
+
prompt: zod_mini.z.optional(zod_mini.z.string()),
|
|
16895
|
+
model: zod_mini.z.optional(zod_mini.z.string())
|
|
16685
16896
|
});
|
|
16686
16897
|
/**
|
|
16687
16898
|
* A matcher group entry in a Qwen Code event array.
|
|
@@ -16696,37 +16907,43 @@ const QwencodeMatcherEntrySchema = zod_mini.z.looseObject({
|
|
|
16696
16907
|
/**
|
|
16697
16908
|
* Convert a single parsed Qwen Code matcher group into canonical hook definitions.
|
|
16698
16909
|
*/
|
|
16910
|
+
function qwencodeHookEntryToCanonical({ hook, sequential, matcher }) {
|
|
16911
|
+
const h = hook;
|
|
16912
|
+
const hookType = h.type === "command" || h.type === "prompt" || h.type === "http" || h.type === "function" ? h.type : "command";
|
|
16913
|
+
const isHttp = hookType === "http";
|
|
16914
|
+
const isCommand = hookType === "command";
|
|
16915
|
+
const isPrompt = hookType === "prompt";
|
|
16916
|
+
const shell = h.shell === "bash" || h.shell === "powershell" ? h.shell : void 0;
|
|
16917
|
+
return {
|
|
16918
|
+
type: hookType,
|
|
16919
|
+
...compact({
|
|
16920
|
+
command: h.command,
|
|
16921
|
+
url: h.url,
|
|
16922
|
+
timeout: h.timeout,
|
|
16923
|
+
name: h.name,
|
|
16924
|
+
description: h.description,
|
|
16925
|
+
statusMessage: h.statusMessage,
|
|
16926
|
+
async: isCommand ? h.async : void 0,
|
|
16927
|
+
env: isCommand ? h.env : void 0,
|
|
16928
|
+
shell: isCommand ? shell : void 0,
|
|
16929
|
+
headers: isHttp ? h.headers : void 0,
|
|
16930
|
+
allowedEnvVars: isHttp ? h.allowedEnvVars : void 0,
|
|
16931
|
+
once: isHttp ? h.once : void 0,
|
|
16932
|
+
prompt: isPrompt ? h.prompt : void 0,
|
|
16933
|
+
model: isPrompt ? h.model : void 0,
|
|
16934
|
+
sequential: sequential ? true : void 0,
|
|
16935
|
+
matcher
|
|
16936
|
+
})
|
|
16937
|
+
};
|
|
16938
|
+
}
|
|
16699
16939
|
function qwencodeMatcherEntryToCanonical(entry) {
|
|
16700
|
-
const defs = [];
|
|
16701
|
-
const hooks = entry.hooks ?? [];
|
|
16702
16940
|
const sequential = entry.sequential === true;
|
|
16703
16941
|
const matcher = entry.matcher !== void 0 && entry.matcher !== null && entry.matcher !== "" ? entry.matcher : void 0;
|
|
16704
|
-
|
|
16705
|
-
|
|
16706
|
-
|
|
16707
|
-
|
|
16708
|
-
|
|
16709
|
-
defs.push({
|
|
16710
|
-
type: hookType,
|
|
16711
|
-
...compact({
|
|
16712
|
-
command: h.command,
|
|
16713
|
-
url: h.url,
|
|
16714
|
-
timeout: h.timeout,
|
|
16715
|
-
name: h.name,
|
|
16716
|
-
description: h.description,
|
|
16717
|
-
statusMessage: h.statusMessage,
|
|
16718
|
-
async: isCommand ? h.async : void 0,
|
|
16719
|
-
env: isCommand ? h.env : void 0,
|
|
16720
|
-
shell: isCommand ? shell : void 0,
|
|
16721
|
-
headers: isHttp ? h.headers : void 0,
|
|
16722
|
-
allowedEnvVars: isHttp ? h.allowedEnvVars : void 0,
|
|
16723
|
-
once: isHttp ? h.once : void 0,
|
|
16724
|
-
sequential: sequential ? true : void 0,
|
|
16725
|
-
matcher
|
|
16726
|
-
})
|
|
16727
|
-
});
|
|
16728
|
-
}
|
|
16729
|
-
return defs;
|
|
16942
|
+
return (entry.hooks ?? []).map((hook) => qwencodeHookEntryToCanonical({
|
|
16943
|
+
hook,
|
|
16944
|
+
sequential,
|
|
16945
|
+
matcher
|
|
16946
|
+
}));
|
|
16730
16947
|
}
|
|
16731
16948
|
/**
|
|
16732
16949
|
* Extract hooks from Qwen Code settings.json into canonical format.
|
|
@@ -16774,12 +16991,12 @@ var QwencodeHooks = class QwencodeHooks extends ToolHooks {
|
|
|
16774
16991
|
validate
|
|
16775
16992
|
});
|
|
16776
16993
|
}
|
|
16777
|
-
static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false }) {
|
|
16994
|
+
static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false, logger }) {
|
|
16778
16995
|
const paths = QwencodeHooks.getSettablePaths({ global });
|
|
16779
16996
|
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
16780
16997
|
const existingContent = await readFileContentOrNull(filePath) ?? JSON.stringify({}, null, 2);
|
|
16781
16998
|
const config = rulesyncHooks.getJson();
|
|
16782
|
-
const patch = { hooks: canonicalToQwencodeHooks(config) };
|
|
16999
|
+
const patch = { hooks: canonicalToQwencodeHooks(config, logger) };
|
|
16783
17000
|
const disableAllHooks = config.qwencode?.disableAllHooks;
|
|
16784
17001
|
if (typeof disableAllHooks === "boolean") patch.disableAllHooks = disableAllHooks;
|
|
16785
17002
|
const fileContent = applySharedConfigPatch({
|
|
@@ -17331,7 +17548,8 @@ const toolHooksFactories = /* @__PURE__ */ new Map([
|
|
|
17331
17548
|
},
|
|
17332
17549
|
supportedEvents: KILO_HOOK_EVENTS,
|
|
17333
17550
|
supportedHookTypes: ["command"],
|
|
17334
|
-
supportsMatcher: true
|
|
17551
|
+
supportsMatcher: true,
|
|
17552
|
+
matcherEvents: ["preToolUse", "postToolUse"]
|
|
17335
17553
|
}],
|
|
17336
17554
|
["opencode", {
|
|
17337
17555
|
class: OpencodeHooks,
|
|
@@ -17342,7 +17560,8 @@ const toolHooksFactories = /* @__PURE__ */ new Map([
|
|
|
17342
17560
|
},
|
|
17343
17561
|
supportedEvents: OPENCODE_HOOK_EVENTS,
|
|
17344
17562
|
supportedHookTypes: ["command"],
|
|
17345
|
-
supportsMatcher: true
|
|
17563
|
+
supportsMatcher: true,
|
|
17564
|
+
matcherEvents: ["preToolUse", "postToolUse"]
|
|
17346
17565
|
}],
|
|
17347
17566
|
["pi", {
|
|
17348
17567
|
class: PiHooks,
|
|
@@ -19016,6 +19235,7 @@ const toolIgnoreFactories = /* @__PURE__ */ new Map([
|
|
|
19016
19235
|
["qwencode", { class: QwencodeIgnore }],
|
|
19017
19236
|
["reasonix", { class: ReasonixIgnore }],
|
|
19018
19237
|
["roo", { class: RooIgnore }],
|
|
19238
|
+
["zoocode", { class: RooIgnore }],
|
|
19019
19239
|
["devin", { class: DevinIgnore }],
|
|
19020
19240
|
["vibe", { class: VibeIgnore }],
|
|
19021
19241
|
["warp", { class: WarpIgnore }],
|
|
@@ -20385,7 +20605,6 @@ const resolveCopilotcliServerType = (server) => {
|
|
|
20385
20605
|
case "streamable-http": return "http";
|
|
20386
20606
|
case "stdio":
|
|
20387
20607
|
case "local": return declared;
|
|
20388
|
-
default: break;
|
|
20389
20608
|
}
|
|
20390
20609
|
if (server.command !== void 0) return "stdio";
|
|
20391
20610
|
return isRemoteMcpServer(server) ? "http" : "stdio";
|
|
@@ -20805,18 +21024,24 @@ var DeepagentsMcp = class DeepagentsMcp extends ToolMcp {
|
|
|
20805
21024
|
/**
|
|
20806
21025
|
* MCP generator for Devin Local (native `.devin/` configuration).
|
|
20807
21026
|
*
|
|
20808
|
-
*
|
|
20809
|
-
*
|
|
20810
|
-
*
|
|
21027
|
+
* Since v3000.3 (the Local 3.6 release), Devin reads MCP servers from a
|
|
21028
|
+
* dedicated `mcpServers`-keyed config file rather than the shared
|
|
21029
|
+
* `config.json`:
|
|
21030
|
+
* - Project scope: `.devin/mcp_config.json`
|
|
21031
|
+
* - Global scope: `~/.config/devin/mcp_config.json`
|
|
20811
21032
|
*
|
|
20812
|
-
*
|
|
20813
|
-
*
|
|
20814
|
-
*
|
|
20815
|
-
*
|
|
20816
|
-
*
|
|
20817
|
-
*
|
|
21033
|
+
* Legacy `mcpServers` entries left in `config.json` are auto-migrated into
|
|
21034
|
+
* the dedicated file on Devin startup, so writing the legacy key would fight
|
|
21035
|
+
* the migration. Import still falls back to the legacy `config.json`
|
|
21036
|
+
* `mcpServers` key when no dedicated file exists, so pre-v3000.3 repos
|
|
21037
|
+
* migrate cleanly. The gitignored `.devin/mcp_config.local.json` override is
|
|
21038
|
+
* the user's personal territory and is never read or written.
|
|
20818
21039
|
*
|
|
20819
|
-
*
|
|
21040
|
+
* Each server is a stdio entry ({ command, args, env }) or a remote entry
|
|
21041
|
+
* ({ serverUrl | url, headers }), and may carry an optional `disabledTools`
|
|
21042
|
+
* array.
|
|
21043
|
+
*
|
|
21044
|
+
* @see https://docs.devin.ai/cli/extensibility/mcp/configuration
|
|
20820
21045
|
*/
|
|
20821
21046
|
var DevinMcp = class DevinMcp extends ToolMcp {
|
|
20822
21047
|
json;
|
|
@@ -20834,31 +21059,27 @@ var DevinMcp = class DevinMcp extends ToolMcp {
|
|
|
20834
21059
|
throw new Error(`Failed to parse Devin MCP config at ${(0, node_path.join)(relativeDirPath, relativeFilePath)}: ${formatError(error)}`, { cause: error });
|
|
20835
21060
|
}
|
|
20836
21061
|
}
|
|
20837
|
-
/**
|
|
20838
|
-
* config.json may carry the permissions/hooks features' keys, so it is never
|
|
20839
|
-
* deleted; only the managed `mcpServers` key is rewritten.
|
|
20840
|
-
*/
|
|
20841
|
-
isDeletable() {
|
|
20842
|
-
return false;
|
|
20843
|
-
}
|
|
20844
21062
|
static getSettablePaths({ global = false } = {}) {
|
|
20845
21063
|
if (global) return {
|
|
20846
21064
|
relativeDirPath: DEVIN_GLOBAL_CONFIG_DIR_PATH,
|
|
20847
|
-
relativeFilePath:
|
|
21065
|
+
relativeFilePath: DEVIN_MCP_CONFIG_FILE_NAME
|
|
20848
21066
|
};
|
|
20849
21067
|
return {
|
|
20850
21068
|
relativeDirPath: DEVIN_DIR,
|
|
20851
|
-
relativeFilePath:
|
|
21069
|
+
relativeFilePath: DEVIN_MCP_CONFIG_FILE_NAME
|
|
20852
21070
|
};
|
|
20853
21071
|
}
|
|
20854
21072
|
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
20855
21073
|
const paths = this.getSettablePaths({ global });
|
|
20856
|
-
|
|
20857
|
-
|
|
20858
|
-
|
|
20859
|
-
|
|
20860
|
-
|
|
20861
|
-
|
|
21074
|
+
let fileContent = await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath));
|
|
21075
|
+
if (fileContent === null) {
|
|
21076
|
+
const legacyContent = await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, DEVIN_CONFIG_FILE_NAME));
|
|
21077
|
+
if (legacyContent !== null) {
|
|
21078
|
+
const legacyJson = this.parseJsonOrThrow(legacyContent, paths.relativeDirPath, DEVIN_CONFIG_FILE_NAME);
|
|
21079
|
+
fileContent = JSON.stringify({ mcpServers: legacyJson.mcpServers ?? {} }, null, 2);
|
|
21080
|
+
}
|
|
21081
|
+
}
|
|
21082
|
+
const newJson = { mcpServers: this.parseJsonOrThrow(fileContent ?? "{\"mcpServers\":{}}", paths.relativeDirPath, paths.relativeFilePath).mcpServers ?? {} };
|
|
20862
21083
|
return new DevinMcp({
|
|
20863
21084
|
outputRoot,
|
|
20864
21085
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -20868,21 +21089,21 @@ var DevinMcp = class DevinMcp extends ToolMcp {
|
|
|
20868
21089
|
global
|
|
20869
21090
|
});
|
|
20870
21091
|
}
|
|
20871
|
-
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false }) {
|
|
21092
|
+
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false, logger }) {
|
|
20872
21093
|
const paths = this.getSettablePaths({ global });
|
|
20873
|
-
const
|
|
20874
|
-
|
|
21094
|
+
const existingContent = await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath));
|
|
21095
|
+
if (existingContent !== null) {
|
|
21096
|
+
const existingJson = this.parseJsonOrThrow(existingContent, paths.relativeDirPath, paths.relativeFilePath);
|
|
21097
|
+
const existingServers = existingJson.mcpServers && typeof existingJson.mcpServers === "object" ? Object.keys(existingJson.mcpServers) : [];
|
|
21098
|
+
const managedServers = new Set(Object.keys(rulesyncMcp.getMcpServers()));
|
|
21099
|
+
const dropped = existingServers.filter((name) => !managedServers.has(name));
|
|
21100
|
+
if (dropped.length > 0) logger?.warn(`Devin MCP servers not managed by rulesync will be removed from ${(0, node_path.join)(paths.relativeDirPath, paths.relativeFilePath)}: ${dropped.join(", ")}. Run 'rulesync import' first to keep them, or move them to mcp_config.local.json.`);
|
|
21101
|
+
}
|
|
20875
21102
|
return new DevinMcp({
|
|
20876
21103
|
outputRoot,
|
|
20877
21104
|
relativeDirPath: paths.relativeDirPath,
|
|
20878
21105
|
relativeFilePath: paths.relativeFilePath,
|
|
20879
|
-
fileContent:
|
|
20880
|
-
fileKey: sharedConfigFileKey(paths),
|
|
20881
|
-
feature: "mcp",
|
|
20882
|
-
existingContent,
|
|
20883
|
-
patch: { mcpServers: rulesyncMcp.getMcpServers() },
|
|
20884
|
-
filePath
|
|
20885
|
-
}),
|
|
21106
|
+
fileContent: JSON.stringify({ mcpServers: rulesyncMcp.getMcpServers() }, null, 2),
|
|
20886
21107
|
validate,
|
|
20887
21108
|
global
|
|
20888
21109
|
});
|
|
@@ -22156,9 +22377,12 @@ var KiloMcp = class KiloMcp extends ToolMcp {
|
|
|
22156
22377
|
fileContent = await readFileContentOrNull(jsonPath);
|
|
22157
22378
|
if (fileContent) relativeFilePath = KILO_JSON_FILE_NAME;
|
|
22158
22379
|
}
|
|
22380
|
+
if (instructions.length === 0 && fileContent === null) return null;
|
|
22159
22381
|
const json = fileContent ? (0, jsonc_parser.parse)(fileContent) : {};
|
|
22160
22382
|
const existingInstructions = Array.isArray(json.instructions) ? json.instructions.filter((entry) => typeof entry === "string") : [];
|
|
22161
|
-
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();
|
|
22162
22386
|
return new KiloMcp({
|
|
22163
22387
|
outputRoot,
|
|
22164
22388
|
relativeDirPath: basePaths.relativeDirPath,
|
|
@@ -22167,7 +22391,7 @@ var KiloMcp = class KiloMcp extends ToolMcp {
|
|
|
22167
22391
|
fileKey: sharedConfigFileKey(basePaths),
|
|
22168
22392
|
feature: "rules",
|
|
22169
22393
|
existingContent: fileContent ?? "",
|
|
22170
|
-
patch: { instructions: mergedInstructions },
|
|
22394
|
+
patch: { instructions: mergedInstructions.length > 0 ? mergedInstructions : void 0 },
|
|
22171
22395
|
filePath: (0, node_path.join)(jsonDir, relativeFilePath)
|
|
22172
22396
|
}),
|
|
22173
22397
|
validate
|
|
@@ -22889,6 +23113,8 @@ var OpencodeMcp = class OpencodeMcp extends ToolMcp {
|
|
|
22889
23113
|
static async fromInstructions({ outputRoot = process.cwd(), instructions, validate = true, global = false }) {
|
|
22890
23114
|
const basePaths = this.getSettablePaths({ global });
|
|
22891
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);
|
|
22892
23118
|
let fileContent = null;
|
|
22893
23119
|
let relativeFilePath = OPENCODE_JSONC_FILE_NAME;
|
|
22894
23120
|
const jsoncPath = (0, node_path.join)(jsonDir, OPENCODE_JSONC_FILE_NAME);
|
|
@@ -22898,9 +23124,15 @@ var OpencodeMcp = class OpencodeMcp extends ToolMcp {
|
|
|
22898
23124
|
fileContent = await readFileContentOrNull(jsonPath);
|
|
22899
23125
|
if (fileContent) relativeFilePath = OPENCODE_JSON_FILE_NAME;
|
|
22900
23126
|
}
|
|
23127
|
+
if (instructions.length === 0 && fileContent === null) return null;
|
|
22901
23128
|
const json = fileContent ? (0, jsonc_parser.parse)(fileContent) : {};
|
|
22902
23129
|
const existingInstructions = Array.isArray(json.instructions) ? json.instructions.filter((entry) => typeof entry === "string") : [];
|
|
22903
|
-
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();
|
|
22904
23136
|
return new OpencodeMcp({
|
|
22905
23137
|
outputRoot,
|
|
22906
23138
|
relativeDirPath: basePaths.relativeDirPath,
|
|
@@ -22909,7 +23141,7 @@ var OpencodeMcp = class OpencodeMcp extends ToolMcp {
|
|
|
22909
23141
|
fileKey: sharedConfigFileKey(basePaths),
|
|
22910
23142
|
feature: "rules",
|
|
22911
23143
|
existingContent: fileContent ?? "",
|
|
22912
|
-
patch: { instructions: mergedInstructions },
|
|
23144
|
+
patch: { instructions: mergedInstructions.length > 0 ? mergedInstructions : void 0 },
|
|
22913
23145
|
filePath: (0, node_path.join)(jsonDir, relativeFilePath)
|
|
22914
23146
|
}),
|
|
22915
23147
|
validate
|
|
@@ -23349,9 +23581,13 @@ function parseRovodevMcpJson(fileContent, relativeDirPath, relativeFilePath) {
|
|
|
23349
23581
|
return parsed;
|
|
23350
23582
|
}
|
|
23351
23583
|
/**
|
|
23352
|
-
* Rovodev MCP:
|
|
23353
|
-
*
|
|
23354
|
-
*
|
|
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.
|
|
23355
23591
|
*/
|
|
23356
23592
|
/**
|
|
23357
23593
|
* Rovo Dev documents the per-server transport key as `transport`, with the
|
|
@@ -23377,11 +23613,7 @@ function lookupTransport(map, key) {
|
|
|
23377
23613
|
return Object.hasOwn(map, key) ? map[key] : void 0;
|
|
23378
23614
|
}
|
|
23379
23615
|
function toRovodevServer(name, server, logger) {
|
|
23380
|
-
const { type, transport, disabled, ...rest } = server;
|
|
23381
|
-
if (disabled === true) {
|
|
23382
|
-
logger?.warn(`Rovo Dev MCP: skipping "${name}" because it is disabled and mcp.json has no disable flag.`);
|
|
23383
|
-
return null;
|
|
23384
|
-
}
|
|
23616
|
+
const { type, transport, disabled: _disabled, ...rest } = server;
|
|
23385
23617
|
const declared = typeof transport === "string" ? transport : typeof type === "string" ? type : void 0;
|
|
23386
23618
|
if (declared === void 0) return rest;
|
|
23387
23619
|
const mapped = lookupTransport(CANONICAL_TO_ROVODEV_TRANSPORT, declared);
|
|
@@ -23403,6 +23635,44 @@ function fromRovodevServer(server) {
|
|
|
23403
23635
|
type: mapped
|
|
23404
23636
|
};
|
|
23405
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
|
+
};
|
|
23406
23676
|
var RovodevMcp = class RovodevMcp extends ToolMcp {
|
|
23407
23677
|
json;
|
|
23408
23678
|
constructor(params) {
|
|
@@ -23423,13 +23693,24 @@ var RovodevMcp = class RovodevMcp extends ToolMcp {
|
|
|
23423
23693
|
};
|
|
23424
23694
|
}
|
|
23425
23695
|
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
23426
|
-
if (!global) throw new Error("Rovodev MCP is global-only; use --global to sync ~/.rovodev/mcp.json");
|
|
23427
23696
|
const paths = this.getSettablePaths({ global });
|
|
23428
23697
|
const json = parseRovodevMcpJson(await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "{\"mcpServers\":{}}", paths.relativeDirPath, paths.relativeFilePath);
|
|
23429
23698
|
const newJson = {
|
|
23430
23699
|
...json,
|
|
23431
23700
|
mcpServers: json.mcpServers ?? {}
|
|
23432
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
|
+
}
|
|
23433
23714
|
return new RovodevMcp({
|
|
23434
23715
|
outputRoot,
|
|
23435
23716
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -23440,11 +23721,21 @@ var RovodevMcp = class RovodevMcp extends ToolMcp {
|
|
|
23440
23721
|
});
|
|
23441
23722
|
}
|
|
23442
23723
|
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false, logger }) {
|
|
23443
|
-
if (!global) throw new Error("Rovodev MCP is global-only; use --global to sync ~/.rovodev/mcp.json");
|
|
23444
23724
|
const paths = this.getSettablePaths({ global });
|
|
23445
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
|
+
}
|
|
23446
23732
|
const mcpServers = Object.fromEntries(Object.entries(rulesyncMcp.getMcpServers()).map(([name, server]) => {
|
|
23447
|
-
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);
|
|
23448
23739
|
return converted === null ? null : [name, converted];
|
|
23449
23740
|
}).filter((entry) => entry !== null));
|
|
23450
23741
|
const rovodevConfig = {
|
|
@@ -23460,6 +23751,64 @@ var RovodevMcp = class RovodevMcp extends ToolMcp {
|
|
|
23460
23751
|
global
|
|
23461
23752
|
});
|
|
23462
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
|
+
}
|
|
23463
23812
|
toRulesyncMcp() {
|
|
23464
23813
|
const rawServers = isMcpServers(this.json.mcpServers) ? this.json.mcpServers : {};
|
|
23465
23814
|
const mcpServers = Object.fromEntries(Object.entries(rawServers).filter(([, server]) => isPlainObject$1(server)).map(([name, server]) => [name, fromRovodevServer(server)]));
|
|
@@ -23608,7 +23957,6 @@ function transportOf(server) {
|
|
|
23608
23957
|
case "http":
|
|
23609
23958
|
case "streamable-http":
|
|
23610
23959
|
case "ws": return "http";
|
|
23611
|
-
default: break;
|
|
23612
23960
|
}
|
|
23613
23961
|
if (server.command !== void 0) return "stdio";
|
|
23614
23962
|
if (server.url !== void 0 || server.httpUrl !== void 0) return "http";
|
|
@@ -23636,6 +23984,20 @@ function deriveTransportAllowlist(servers) {
|
|
|
23636
23984
|
return allowlist;
|
|
23637
23985
|
}
|
|
23638
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
|
|
23639
24001
|
//#region src/features/mcp/vibe-mcp.ts
|
|
23640
24002
|
const VIBE_MCP_SERVER_FIELDS = [
|
|
23641
24003
|
"transport",
|
|
@@ -23704,8 +24066,9 @@ var VibeMcp = class VibeMcp extends ToolMcp {
|
|
|
23704
24066
|
global
|
|
23705
24067
|
});
|
|
23706
24068
|
}
|
|
23707
|
-
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false }) {
|
|
24069
|
+
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, logger, global = false }) {
|
|
23708
24070
|
const paths = this.getSettablePaths({ global });
|
|
24071
|
+
if (global) await warnIfGlobalVibeConfigIsShadowed(logger);
|
|
23709
24072
|
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
23710
24073
|
const existingContent = await readFileContentOrNull(filePath) ?? "";
|
|
23711
24074
|
const existingServers = normalizeMcpServersArray(parseSharedConfig({
|
|
@@ -23714,7 +24077,10 @@ var VibeMcp = class VibeMcp extends ToolMcp {
|
|
|
23714
24077
|
filePath
|
|
23715
24078
|
}).mcp_servers);
|
|
23716
24079
|
const existingByName = new Map(existingServers.map((server) => [server.name, server]));
|
|
23717
|
-
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));
|
|
23718
24084
|
return new VibeMcp({
|
|
23719
24085
|
outputRoot,
|
|
23720
24086
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -23723,7 +24089,7 @@ var VibeMcp = class VibeMcp extends ToolMcp {
|
|
|
23723
24089
|
fileKey: sharedConfigFileKey(paths),
|
|
23724
24090
|
feature: "mcp",
|
|
23725
24091
|
existingContent,
|
|
23726
|
-
patch: { mcp_servers: mcpServers },
|
|
24092
|
+
patch: { mcp_servers: [...mcpServers, ...unmanagedServers] },
|
|
23727
24093
|
filePath
|
|
23728
24094
|
}),
|
|
23729
24095
|
validate,
|
|
@@ -24425,10 +24791,19 @@ const toolMcpFactories = /* @__PURE__ */ new Map([
|
|
|
24425
24791
|
supportsDisabledTools: false
|
|
24426
24792
|
}
|
|
24427
24793
|
}],
|
|
24794
|
+
["zoocode", {
|
|
24795
|
+
class: RooMcp,
|
|
24796
|
+
meta: {
|
|
24797
|
+
supportsProject: true,
|
|
24798
|
+
supportsGlobal: false,
|
|
24799
|
+
supportsEnabledTools: false,
|
|
24800
|
+
supportsDisabledTools: false
|
|
24801
|
+
}
|
|
24802
|
+
}],
|
|
24428
24803
|
["rovodev", {
|
|
24429
24804
|
class: RovodevMcp,
|
|
24430
24805
|
meta: {
|
|
24431
|
-
supportsProject:
|
|
24806
|
+
supportsProject: true,
|
|
24432
24807
|
supportsGlobal: true,
|
|
24433
24808
|
supportsEnabledTools: false,
|
|
24434
24809
|
supportsDisabledTools: false
|
|
@@ -25294,9 +25669,7 @@ function convertRulesyncToAntigravityCliPermissions(config) {
|
|
|
25294
25669
|
case "ask":
|
|
25295
25670
|
ask.push(entry);
|
|
25296
25671
|
break;
|
|
25297
|
-
case "deny":
|
|
25298
|
-
deny.push(entry);
|
|
25299
|
-
break;
|
|
25672
|
+
case "deny": deny.push(entry);
|
|
25300
25673
|
}
|
|
25301
25674
|
}
|
|
25302
25675
|
}
|
|
@@ -25507,9 +25880,7 @@ function convertRulesyncToAntigravityIdePermissions(config) {
|
|
|
25507
25880
|
case "ask":
|
|
25508
25881
|
ask.push(entry);
|
|
25509
25882
|
break;
|
|
25510
|
-
case "deny":
|
|
25511
|
-
deny.push(entry);
|
|
25512
|
-
break;
|
|
25883
|
+
case "deny": deny.push(entry);
|
|
25513
25884
|
}
|
|
25514
25885
|
}
|
|
25515
25886
|
}
|
|
@@ -26241,9 +26612,7 @@ function convertRulesyncToClaudePermissions({ config, logger }) {
|
|
|
26241
26612
|
case "ask":
|
|
26242
26613
|
ask.push(entry);
|
|
26243
26614
|
break;
|
|
26244
|
-
case "deny":
|
|
26245
|
-
deny.push(entry);
|
|
26246
|
-
break;
|
|
26615
|
+
case "deny": deny.push(entry);
|
|
26247
26616
|
}
|
|
26248
26617
|
}
|
|
26249
26618
|
}
|
|
@@ -27493,9 +27862,7 @@ function convertRulesyncToCursorPermissions(config, logger) {
|
|
|
27493
27862
|
case "ask":
|
|
27494
27863
|
logger?.warn(`Cursor CLI permissions do not support the "ask" action. Skipping ${category} rule: ${pattern}`);
|
|
27495
27864
|
break;
|
|
27496
|
-
case "deny":
|
|
27497
|
-
deny.push(entry);
|
|
27498
|
-
break;
|
|
27865
|
+
case "deny": deny.push(entry);
|
|
27499
27866
|
}
|
|
27500
27867
|
}
|
|
27501
27868
|
}
|
|
@@ -27600,9 +27967,10 @@ function buildDevinPermissionEntry(scope, pattern) {
|
|
|
27600
27967
|
* - Project scope: `.devin/config.json`
|
|
27601
27968
|
* - Global scope: `~/.config/devin/config.json`
|
|
27602
27969
|
*
|
|
27603
|
-
*
|
|
27604
|
-
*
|
|
27605
|
-
* the file is never deleted; only the managed
|
|
27970
|
+
* In global mode the config file is shared with the hooks (`hooks`) feature
|
|
27971
|
+
* (MCP moved to the dedicated mcp_config.json in v3000.3), so reads and writes
|
|
27972
|
+
* merge into the existing JSON and the file is never deleted; only the managed
|
|
27973
|
+
* `permissions` key is rewritten.
|
|
27606
27974
|
*
|
|
27607
27975
|
* @see https://docs.devin.ai/cli/reference/permissions
|
|
27608
27976
|
*/
|
|
@@ -27731,9 +28099,7 @@ function convertRulesyncToDevinPermissions(config) {
|
|
|
27731
28099
|
case "ask":
|
|
27732
28100
|
ask.push(entry);
|
|
27733
28101
|
break;
|
|
27734
|
-
case "deny":
|
|
27735
|
-
deny.push(entry);
|
|
27736
|
-
break;
|
|
28102
|
+
case "deny": deny.push(entry);
|
|
27737
28103
|
}
|
|
27738
28104
|
}
|
|
27739
28105
|
}
|
|
@@ -27921,10 +28287,7 @@ function convertRulesyncToFactorydroidPermissions({ config, logger }) {
|
|
|
27921
28287
|
case "allow":
|
|
27922
28288
|
allow.push(pattern);
|
|
27923
28289
|
break;
|
|
27924
|
-
case "deny":
|
|
27925
|
-
deny.push(pattern);
|
|
27926
|
-
break;
|
|
27927
|
-
case "ask": break;
|
|
28290
|
+
case "deny": deny.push(pattern);
|
|
27928
28291
|
}
|
|
27929
28292
|
}
|
|
27930
28293
|
return {
|
|
@@ -28043,10 +28406,11 @@ var GoosePermissions = class GoosePermissions extends ToolPermissions {
|
|
|
28043
28406
|
throw new Error(`Failed to parse existing Goose permission.yaml at ${filePath}: ${formatError(error)}`, { cause: error });
|
|
28044
28407
|
}
|
|
28045
28408
|
const config = isRecord(parsed) ? { ...parsed } : {};
|
|
28046
|
-
|
|
28409
|
+
const userPermission = convertRulesyncToGoosePermissionConfig({
|
|
28047
28410
|
config: rulesyncPermissions.getJson(),
|
|
28048
28411
|
logger
|
|
28049
28412
|
});
|
|
28413
|
+
config[GOOSE_USER_KEY] = userPermission;
|
|
28050
28414
|
return new GoosePermissions({
|
|
28051
28415
|
outputRoot,
|
|
28052
28416
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -30264,9 +30628,7 @@ function convertRulesyncToQwenPermissions(config) {
|
|
|
30264
30628
|
case "ask":
|
|
30265
30629
|
ask.push(entry);
|
|
30266
30630
|
break;
|
|
30267
|
-
case "deny":
|
|
30268
|
-
deny.push(entry);
|
|
30269
|
-
break;
|
|
30631
|
+
case "deny": deny.push(entry);
|
|
30270
30632
|
}
|
|
30271
30633
|
}
|
|
30272
30634
|
}
|
|
@@ -30339,6 +30701,20 @@ function toCanonicalToolName$2(reasonixName) {
|
|
|
30339
30701
|
return REASONIX_TO_CANONICAL_TOOL_NAMES[reasonixName] ?? reasonixName;
|
|
30340
30702
|
}
|
|
30341
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
|
+
/**
|
|
30342
30718
|
* Parse a Reasonix permission entry like "Bash(npm run *)" into tool name and pattern.
|
|
30343
30719
|
* If no parentheses, returns the tool name with "*" as the pattern.
|
|
30344
30720
|
*/
|
|
@@ -30427,10 +30803,22 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
30427
30803
|
const config = rulesyncPermissions.getJson();
|
|
30428
30804
|
const { allow, ask, deny } = convertRulesyncToReasonixPermissions(config);
|
|
30429
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
|
+
]);
|
|
30430
30815
|
const existingPermissions = toReasonixTable(parsed.permissions);
|
|
30431
|
-
const preservedAllow
|
|
30432
|
-
|
|
30433
|
-
|
|
30816
|
+
const { allow: preservedAllow, ask: preservedAsk, deny: preservedDeny } = preserveExistingEntries({
|
|
30817
|
+
existingPermissions,
|
|
30818
|
+
managedToolNames,
|
|
30819
|
+
rawOwnedEntries,
|
|
30820
|
+
logger
|
|
30821
|
+
});
|
|
30434
30822
|
if (logger && managedToolNames.has("Read")) {
|
|
30435
30823
|
const droppedReadDenyEntries = toReasonixStringArray(existingPermissions.deny).filter((entry) => {
|
|
30436
30824
|
const { toolName } = parseReasonixPermissionEntry(entry);
|
|
@@ -30439,17 +30827,22 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
30439
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.`);
|
|
30440
30828
|
}
|
|
30441
30829
|
const mergedPermissions = { ...existingPermissions };
|
|
30442
|
-
|
|
30443
|
-
|
|
30444
|
-
|
|
30445
|
-
|
|
30446
|
-
|
|
30447
|
-
|
|
30448
|
-
|
|
30449
|
-
|
|
30450
|
-
|
|
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
|
+
]);
|
|
30451
30845
|
const patch = { permissions: mergedPermissions };
|
|
30452
|
-
const override = config.reasonix;
|
|
30453
30846
|
if (override?.sandbox !== void 0) patch.sandbox = {
|
|
30454
30847
|
...asReasonixRecord(parsed.sandbox),
|
|
30455
30848
|
...asReasonixRecord(override.sandbox)
|
|
@@ -30480,16 +30873,29 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
30480
30873
|
}
|
|
30481
30874
|
toRulesyncPermissions() {
|
|
30482
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));
|
|
30483
30886
|
const config = convertReasonixToRulesyncPermissions({
|
|
30484
|
-
allow:
|
|
30485
|
-
ask:
|
|
30486
|
-
deny:
|
|
30887
|
+
allow: allowSplit.translated,
|
|
30888
|
+
ask: askSplit.translated,
|
|
30889
|
+
deny: denySplit.translated
|
|
30487
30890
|
});
|
|
30488
30891
|
const sandbox = asReasonixRecord(this.toml.sandbox);
|
|
30489
30892
|
const agentPlanMode = pickReasonixKeys(this.toml.agent, [...REASONIX_OVERRIDE_AGENT_KEYS, ...REASONIX_RETIRED_AGENT_KEYS]);
|
|
30490
30893
|
const reasonixOverride = {};
|
|
30491
30894
|
if (Object.keys(sandbox).length > 0) reasonixOverride.sandbox = sandbox;
|
|
30492
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;
|
|
30493
30899
|
const result = { ...config };
|
|
30494
30900
|
if (Object.keys(reasonixOverride).length > 0) result.reasonix = reasonixOverride;
|
|
30495
30901
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
|
|
@@ -30519,6 +30925,41 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
30519
30925
|
}
|
|
30520
30926
|
};
|
|
30521
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
|
+
/**
|
|
30522
30963
|
* Convert rulesync permissions config to Reasonix allow/ask/deny arrays.
|
|
30523
30964
|
*/
|
|
30524
30965
|
function convertRulesyncToReasonixPermissions(config) {
|
|
@@ -30536,9 +30977,7 @@ function convertRulesyncToReasonixPermissions(config) {
|
|
|
30536
30977
|
case "ask":
|
|
30537
30978
|
ask.push(entry);
|
|
30538
30979
|
break;
|
|
30539
|
-
case "deny":
|
|
30540
|
-
deny.push(entry);
|
|
30541
|
-
break;
|
|
30980
|
+
case "deny": deny.push(entry);
|
|
30542
30981
|
}
|
|
30543
30982
|
}
|
|
30544
30983
|
}
|
|
@@ -30568,7 +31007,6 @@ function convertReasonixToRulesyncPermissions(params) {
|
|
|
30568
31007
|
}
|
|
30569
31008
|
//#endregion
|
|
30570
31009
|
//#region src/features/permissions/rovodev-permissions.ts
|
|
30571
|
-
const ROVODEV_GLOBAL_ONLY_MESSAGE = "Rovodev permissions are global-only; use --global to sync ~/.rovodev/config.yml";
|
|
30572
31010
|
const CATCH_ALL_PATTERN$1 = "*";
|
|
30573
31011
|
const CATEGORY_TO_TOOL_KEYS = {
|
|
30574
31012
|
read: [
|
|
@@ -30628,9 +31066,12 @@ const OWNED_TOOL_PERMISSION_KEYS = [
|
|
|
30628
31066
|
/**
|
|
30629
31067
|
* Permissions adapter for Rovo Dev CLI.
|
|
30630
31068
|
*
|
|
30631
|
-
* Rovo Dev reads tool permissions from the `toolPermissions` block of
|
|
30632
|
-
*
|
|
30633
|
-
*
|
|
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.
|
|
30634
31075
|
*
|
|
30635
31076
|
* Rovo Dev's three levels (`allow`/`ask`/`deny`) are an exact 1:1 with rulesync's
|
|
30636
31077
|
* canonical action enum, so action values pass through verbatim.
|
|
@@ -30677,7 +31118,6 @@ var RovodevPermissions = class RovodevPermissions extends ToolPermissions {
|
|
|
30677
31118
|
};
|
|
30678
31119
|
}
|
|
30679
31120
|
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
30680
|
-
if (!global) throw new Error(ROVODEV_GLOBAL_ONLY_MESSAGE);
|
|
30681
31121
|
const paths = RovodevPermissions.getSettablePaths({ global });
|
|
30682
31122
|
const fileContent = await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "";
|
|
30683
31123
|
return new RovodevPermissions({
|
|
@@ -30686,11 +31126,10 @@ var RovodevPermissions = class RovodevPermissions extends ToolPermissions {
|
|
|
30686
31126
|
relativeFilePath: paths.relativeFilePath,
|
|
30687
31127
|
fileContent,
|
|
30688
31128
|
validate,
|
|
30689
|
-
global
|
|
31129
|
+
global
|
|
30690
31130
|
});
|
|
30691
31131
|
}
|
|
30692
31132
|
static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, logger, global = false }) {
|
|
30693
|
-
if (!global) throw new Error(ROVODEV_GLOBAL_ONLY_MESSAGE);
|
|
30694
31133
|
const paths = RovodevPermissions.getSettablePaths({ global });
|
|
30695
31134
|
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
30696
31135
|
const existingContent = await readFileContentOrNull(filePath) ?? "";
|
|
@@ -30713,14 +31152,20 @@ var RovodevPermissions = class RovodevPermissions extends ToolPermissions {
|
|
|
30713
31152
|
filePath,
|
|
30714
31153
|
logger
|
|
30715
31154
|
});
|
|
30716
|
-
|
|
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);
|
|
30717
31162
|
return new RovodevPermissions({
|
|
30718
31163
|
outputRoot,
|
|
30719
31164
|
relativeDirPath: paths.relativeDirPath,
|
|
30720
31165
|
relativeFilePath: paths.relativeFilePath,
|
|
30721
|
-
fileContent
|
|
31166
|
+
fileContent,
|
|
30722
31167
|
validate: true,
|
|
30723
|
-
global
|
|
31168
|
+
global
|
|
30724
31169
|
});
|
|
30725
31170
|
}
|
|
30726
31171
|
toRulesyncPermissions() {
|
|
@@ -31257,6 +31702,7 @@ const CANONICAL_TO_VIBE_TOOL_NAMES = {
|
|
|
31257
31702
|
write: "write_file",
|
|
31258
31703
|
webfetch: "web_fetch",
|
|
31259
31704
|
websearch: "web_search",
|
|
31705
|
+
grep: "grep",
|
|
31260
31706
|
agent: "task"
|
|
31261
31707
|
};
|
|
31262
31708
|
const VIBE_TO_CANONICAL_TOOL_NAMES = {
|
|
@@ -31266,6 +31712,7 @@ const VIBE_TO_CANONICAL_TOOL_NAMES = {
|
|
|
31266
31712
|
write_file: "write",
|
|
31267
31713
|
web_fetch: "webfetch",
|
|
31268
31714
|
web_search: "websearch",
|
|
31715
|
+
grep: "grep",
|
|
31269
31716
|
task: "agent"
|
|
31270
31717
|
};
|
|
31271
31718
|
var VibePermissions = class VibePermissions extends ToolPermissions {
|
|
@@ -31303,6 +31750,7 @@ var VibePermissions = class VibePermissions extends ToolPermissions {
|
|
|
31303
31750
|
}
|
|
31304
31751
|
static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, validate = true, logger, global = false }) {
|
|
31305
31752
|
const paths = this.getSettablePaths({ global });
|
|
31753
|
+
if (global) await warnIfGlobalVibeConfigIsShadowed(logger);
|
|
31306
31754
|
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
31307
31755
|
const existingContent = await readFileContentOrNull(filePath) ?? "";
|
|
31308
31756
|
const config = parseVibeConfig(existingContent);
|
|
@@ -31310,46 +31758,31 @@ var VibePermissions = class VibePermissions extends ToolPermissions {
|
|
|
31310
31758
|
const tools = toVibeToolsRecord(config.tools);
|
|
31311
31759
|
const enabledTools = new Set(toStringArray(config.enabled_tools));
|
|
31312
31760
|
const disabledTools = new Set(toStringArray(config.disabled_tools));
|
|
31761
|
+
const removedEnabledTools = [];
|
|
31313
31762
|
for (const category of Object.keys(permission)) {
|
|
31763
|
+
if (!hasVibeToolName(category)) continue;
|
|
31314
31764
|
const vibeToolName = toVibeToolName(category);
|
|
31315
|
-
enabledTools.delete(vibeToolName);
|
|
31765
|
+
if (enabledTools.delete(vibeToolName)) removedEnabledTools.push(vibeToolName);
|
|
31316
31766
|
disabledTools.delete(vibeToolName);
|
|
31317
31767
|
}
|
|
31318
31768
|
for (const [category, rules] of Object.entries(permission)) {
|
|
31319
|
-
|
|
31320
|
-
|
|
31321
|
-
|
|
31322
|
-
|
|
31323
|
-
const deny = new Set(toStringArray(existingTool.deny ?? existingTool.denylist));
|
|
31324
|
-
for (const [pattern, action] of Object.entries(rules)) {
|
|
31325
|
-
if (pattern === "*") {
|
|
31326
|
-
applyWildcardPermission({
|
|
31327
|
-
action,
|
|
31328
|
-
toolConfig: nextTool
|
|
31329
|
-
});
|
|
31330
|
-
if (action === "deny") {
|
|
31331
|
-
disabledTools.add(vibeToolName);
|
|
31332
|
-
enabledTools.delete(vibeToolName);
|
|
31333
|
-
} else if (action === "allow") {
|
|
31334
|
-
enabledTools.add(vibeToolName);
|
|
31335
|
-
disabledTools.delete(vibeToolName);
|
|
31336
|
-
}
|
|
31337
|
-
continue;
|
|
31338
|
-
}
|
|
31339
|
-
if (action === "ask") {
|
|
31340
|
-
logger?.warn(`Vibe permissions do not support pattern-level "ask" rules. Skipping ${category}: ${pattern}`);
|
|
31341
|
-
continue;
|
|
31342
|
-
}
|
|
31343
|
-
if (action === "allow") allow.add(pattern);
|
|
31344
|
-
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;
|
|
31345
31773
|
}
|
|
31346
|
-
|
|
31347
|
-
|
|
31348
|
-
|
|
31349
|
-
|
|
31350
|
-
|
|
31774
|
+
applyCategoryRules({
|
|
31775
|
+
category,
|
|
31776
|
+
rules,
|
|
31777
|
+
tools,
|
|
31778
|
+
enabledTools,
|
|
31779
|
+
disabledTools,
|
|
31780
|
+
logger
|
|
31781
|
+
});
|
|
31351
31782
|
}
|
|
31352
|
-
|
|
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.`);
|
|
31353
31786
|
return new VibePermissions({
|
|
31354
31787
|
outputRoot,
|
|
31355
31788
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -31360,7 +31793,10 @@ var VibePermissions = class VibePermissions extends ToolPermissions {
|
|
|
31360
31793
|
existingContent,
|
|
31361
31794
|
patch: {
|
|
31362
31795
|
tools,
|
|
31363
|
-
enabled_tools:
|
|
31796
|
+
enabled_tools: resolveEnabledToolsPatch({
|
|
31797
|
+
vibeOverride,
|
|
31798
|
+
enabledTools
|
|
31799
|
+
}),
|
|
31364
31800
|
disabled_tools: disabledTools.size > 0 ? [...disabledTools].toSorted() : void 0
|
|
31365
31801
|
},
|
|
31366
31802
|
filePath
|
|
@@ -31372,7 +31808,7 @@ var VibePermissions = class VibePermissions extends ToolPermissions {
|
|
|
31372
31808
|
toRulesyncPermissions() {
|
|
31373
31809
|
const permission = {};
|
|
31374
31810
|
const vibeOverridePermission = {};
|
|
31375
|
-
|
|
31811
|
+
const enabledToolsList = toStringArray(this.toml.enabled_tools);
|
|
31376
31812
|
for (const tool of toStringArray(this.toml.disabled_tools)) ensurePermission(permission, toCanonicalToolName$1(tool))["*"] = "deny";
|
|
31377
31813
|
for (const [vibeToolName, toolConfig] of Object.entries(toVibeToolsRecord(this.toml.tools))) {
|
|
31378
31814
|
const category = toCanonicalToolName$1(vibeToolName);
|
|
@@ -31385,9 +31821,12 @@ var VibePermissions = class VibePermissions extends ToolPermissions {
|
|
|
31385
31821
|
if (sensitivePatterns.length > 0) vibeOverridePermission[category] = { sensitive_patterns: sensitivePatterns };
|
|
31386
31822
|
}
|
|
31387
31823
|
for (const [category, rules] of Object.entries(permission)) if (Object.keys(rules).length === 0) delete permission[category];
|
|
31388
|
-
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 ? {
|
|
31389
31828
|
permission,
|
|
31390
|
-
vibe:
|
|
31829
|
+
vibe: vibeOverride
|
|
31391
31830
|
} : { permission };
|
|
31392
31831
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(json, null, 2) });
|
|
31393
31832
|
}
|
|
@@ -31422,8 +31861,12 @@ var VibePermissions = class VibePermissions extends ToolPermissions {
|
|
|
31422
31861
|
* list for any category the override names: a present list is set, an empty
|
|
31423
31862
|
* one clears it. Categories not named keep whatever the existing file had.
|
|
31424
31863
|
*/
|
|
31425
|
-
function applyVibeSensitivePatterns(tools, vibeOverride) {
|
|
31864
|
+
function applyVibeSensitivePatterns(tools, vibeOverride, logger) {
|
|
31426
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
|
+
}
|
|
31427
31870
|
const vibeToolName = toVibeToolName(category);
|
|
31428
31871
|
const nextTool = toVibeToolConfig(tools[vibeToolName]);
|
|
31429
31872
|
const patterns = toStringArray(toolOverride.sensitive_patterns);
|
|
@@ -31432,6 +31875,57 @@ function applyVibeSensitivePatterns(tools, vibeOverride) {
|
|
|
31432
31875
|
tools[vibeToolName] = nextTool;
|
|
31433
31876
|
}
|
|
31434
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
|
+
}
|
|
31435
31929
|
function parseVibeConfig(fileContent) {
|
|
31436
31930
|
const parsed = smol_toml.parse(fileContent || smol_toml.stringify({}));
|
|
31437
31931
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
@@ -31440,6 +31934,17 @@ function parseVibeConfig(fileContent) {
|
|
|
31440
31934
|
function toVibeToolName(category) {
|
|
31441
31935
|
return CANONICAL_TO_VIBE_TOOL_NAMES[category] ?? category;
|
|
31442
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
|
+
}
|
|
31443
31948
|
function toCanonicalToolName$1(vibeToolName) {
|
|
31444
31949
|
return VIBE_TO_CANONICAL_TOOL_NAMES[vibeToolName] ?? vibeToolName;
|
|
31445
31950
|
}
|
|
@@ -31457,7 +31962,7 @@ function toStringArray(value) {
|
|
|
31457
31962
|
function applyWildcardPermission({ action, toolConfig }) {
|
|
31458
31963
|
if (action === "allow") toolConfig.permission = "always";
|
|
31459
31964
|
else if (action === "ask") toolConfig.permission = "ask";
|
|
31460
|
-
else
|
|
31965
|
+
else toolConfig.permission = "never";
|
|
31461
31966
|
}
|
|
31462
31967
|
function fromVibePermission(value) {
|
|
31463
31968
|
if (value === "always" || value === "allow") return "allow";
|
|
@@ -31476,11 +31981,29 @@ function ensurePermission(permission, category) {
|
|
|
31476
31981
|
const WARP_GLOBAL_ONLY_MESSAGE = "Warp permissions are global-only; use --global to sync Warp's settings.toml";
|
|
31477
31982
|
const ALLOWLIST_KEY = "agent_mode_command_execution_allowlist";
|
|
31478
31983
|
const DENYLIST_KEY = "agent_mode_command_execution_denylist";
|
|
31984
|
+
const EXECUTION_PROFILES_KEY = "execution_profiles";
|
|
31985
|
+
const DEFAULT_PROFILE_KEY = "default";
|
|
31986
|
+
const PROFILE_ALLOWLIST_KEY = "command_allowlist";
|
|
31987
|
+
const PROFILE_DENYLIST_KEY = "command_denylist";
|
|
31479
31988
|
const WARP_OVERRIDE_KEYS = [
|
|
31480
31989
|
"agent_mode_coding_permissions",
|
|
31481
31990
|
"agent_mode_coding_file_read_allowlist",
|
|
31482
31991
|
"agent_mode_execute_readonly_commands"
|
|
31483
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
|
+
];
|
|
31484
32007
|
/**
|
|
31485
32008
|
* Warp's `settings.toml` lives in a different directory per platform (Stable
|
|
31486
32009
|
* channel). The home directory is resolved by the processor through
|
|
@@ -31503,11 +32026,21 @@ function warpSettingsDir() {
|
|
|
31503
32026
|
/**
|
|
31504
32027
|
* Permissions adapter for Warp.
|
|
31505
32028
|
*
|
|
31506
|
-
* Warp gates **shell command** execution through two regex arrays
|
|
31507
|
-
*
|
|
31508
|
-
* - `
|
|
31509
|
-
*
|
|
31510
|
-
*
|
|
32029
|
+
* Warp gates **shell command** execution through two regex arrays in the
|
|
32030
|
+
* global user `settings.toml`. Since file-backed execution profiles went
|
|
32031
|
+
* Stable (2026-07-28) the authoritative surface is the `default` record of the
|
|
32032
|
+
* `[agents.execution_profiles.<id>]` collection:
|
|
32033
|
+
* - `command_allowlist` — commands that auto-execute.
|
|
32034
|
+
* - `command_denylist` — commands that always require permission (the denylist
|
|
32035
|
+
* wins over the allowlist).
|
|
32036
|
+
*
|
|
32037
|
+
* The legacy `[agents.profiles]` keys
|
|
32038
|
+
* (`agent_mode_command_execution_allowlist` / `denylist`) are consumed only
|
|
32039
|
+
* once by Warp's one-shot migration and are ignored afterwards. Both surfaces
|
|
32040
|
+
* are written: the legacy block keeps un-migrated installs and old clients
|
|
32041
|
+
* working, and the `default` execution profile (merged in place, only when the
|
|
32042
|
+
* collection already exists) keeps migrated installs enforcing the lists.
|
|
32043
|
+
* Import prefers the execution profile and falls back to the legacy keys.
|
|
31511
32044
|
*
|
|
31512
32045
|
* This surface is **global only** — there is no project-scoped Warp permissions
|
|
31513
32046
|
* file. rulesync's canonical `permission.bash` patterns map directly (`allow` →
|
|
@@ -31525,7 +32058,14 @@ function warpSettingsDir() {
|
|
|
31525
32058
|
* through the `warp` override namespace (see `WarpPermissionsOverrideSchema`):
|
|
31526
32059
|
* on **import** they are lifted from `settings.toml` into the override, and on
|
|
31527
32060
|
* **export** they are merged back into `[agents.profiles]` (the override wins).
|
|
31528
|
-
*
|
|
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.
|
|
31529
32069
|
*
|
|
31530
32070
|
* The `settings.toml` file holds all of Warp's settings, so the
|
|
31531
32071
|
* `[agents.profiles]` block is merged in place and the file is never deleted.
|
|
@@ -31578,7 +32118,11 @@ var WarpPermissions = class WarpPermissions extends ToolPermissions {
|
|
|
31578
32118
|
const agents = isRecord(settings.agents) ? { ...settings.agents } : {};
|
|
31579
32119
|
const profiles = isRecord(agents.profiles) ? { ...agents.profiles } : {};
|
|
31580
32120
|
const override = config.warp;
|
|
31581
|
-
|
|
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
|
+
}
|
|
31582
32126
|
const mergedAllow = (0, es_toolkit.uniq)(allow.toSorted());
|
|
31583
32127
|
const mergedDeny = (0, es_toolkit.uniq)(deny.toSorted());
|
|
31584
32128
|
if (mergedAllow.length > 0) profiles[ALLOWLIST_KEY] = mergedAllow;
|
|
@@ -31586,6 +32130,13 @@ var WarpPermissions = class WarpPermissions extends ToolPermissions {
|
|
|
31586
32130
|
if (mergedDeny.length > 0) profiles[DENYLIST_KEY] = mergedDeny;
|
|
31587
32131
|
else delete profiles[DENYLIST_KEY];
|
|
31588
32132
|
agents.profiles = profiles;
|
|
32133
|
+
mergeIntoDefaultExecutionProfile({
|
|
32134
|
+
agents,
|
|
32135
|
+
mergedAllow,
|
|
32136
|
+
mergedDeny,
|
|
32137
|
+
executionProfileOverride,
|
|
32138
|
+
logger
|
|
32139
|
+
});
|
|
31589
32140
|
settings.agents = agents;
|
|
31590
32141
|
return new WarpPermissions({
|
|
31591
32142
|
outputRoot,
|
|
@@ -31605,12 +32156,19 @@ var WarpPermissions = class WarpPermissions extends ToolPermissions {
|
|
|
31605
32156
|
}
|
|
31606
32157
|
const agents = isRecord(settings.agents) ? settings.agents : {};
|
|
31607
32158
|
const profiles = isRecord(agents.profiles) ? agents.profiles : {};
|
|
32159
|
+
const executionProfiles = isRecord(agents[EXECUTION_PROFILES_KEY]) ? agents[EXECUTION_PROFILES_KEY] : void 0;
|
|
32160
|
+
const defaultProfile = executionProfiles && isRecord(executionProfiles[DEFAULT_PROFILE_KEY]) ? executionProfiles[DEFAULT_PROFILE_KEY] : void 0;
|
|
31608
32161
|
const config = convertWarpToRulesyncPermissions({
|
|
31609
|
-
allow: isStringArray$1(profiles[ALLOWLIST_KEY]) ? profiles[ALLOWLIST_KEY] : [],
|
|
31610
|
-
deny: isStringArray$1(profiles[DENYLIST_KEY]) ? profiles[DENYLIST_KEY] : []
|
|
32162
|
+
allow: defaultProfile ? isStringArray$1(defaultProfile[PROFILE_ALLOWLIST_KEY]) ? defaultProfile[PROFILE_ALLOWLIST_KEY] : [] : isStringArray$1(profiles[ALLOWLIST_KEY]) ? profiles[ALLOWLIST_KEY] : [],
|
|
32163
|
+
deny: defaultProfile ? isStringArray$1(defaultProfile[PROFILE_DENYLIST_KEY]) ? defaultProfile[PROFILE_DENYLIST_KEY] : [] : isStringArray$1(profiles[DENYLIST_KEY]) ? profiles[DENYLIST_KEY] : []
|
|
31611
32164
|
});
|
|
31612
32165
|
const warpOverride = {};
|
|
31613
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
|
+
}
|
|
31614
32172
|
const result = { ...config };
|
|
31615
32173
|
if (Object.keys(warpOverride).length > 0) result.warp = warpOverride;
|
|
31616
32174
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
|
|
@@ -31633,6 +32191,36 @@ var WarpPermissions = class WarpPermissions extends ToolPermissions {
|
|
|
31633
32191
|
}
|
|
31634
32192
|
};
|
|
31635
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
|
+
/**
|
|
31636
32224
|
* Convert rulesync permissions config to Warp command allow/deny regex lists.
|
|
31637
32225
|
* Only the `bash` category maps; `ask` rules and non-`bash` categories are
|
|
31638
32226
|
* dropped (the latter with a warning when they carry `deny` rules).
|
|
@@ -31649,10 +32237,7 @@ function convertRulesyncToWarpPermissions({ config, logger }) {
|
|
|
31649
32237
|
case "allow":
|
|
31650
32238
|
allow.push(pattern);
|
|
31651
32239
|
break;
|
|
31652
|
-
case "deny":
|
|
31653
|
-
deny.push(pattern);
|
|
31654
|
-
break;
|
|
31655
|
-
case "ask": break;
|
|
32240
|
+
case "deny": deny.push(pattern);
|
|
31656
32241
|
}
|
|
31657
32242
|
}
|
|
31658
32243
|
return {
|
|
@@ -32107,7 +32692,7 @@ const toolPermissionsFactories = /* @__PURE__ */ new Map([
|
|
|
32107
32692
|
["rovodev", {
|
|
32108
32693
|
class: RovodevPermissions,
|
|
32109
32694
|
meta: {
|
|
32110
|
-
supportsProject:
|
|
32695
|
+
supportsProject: true,
|
|
32111
32696
|
supportsGlobal: true,
|
|
32112
32697
|
supportsImport: true
|
|
32113
32698
|
}
|
|
@@ -33304,6 +33889,38 @@ var AugmentcodeSkill = class AugmentcodeSkill extends ToolSkill {
|
|
|
33304
33889
|
}
|
|
33305
33890
|
};
|
|
33306
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
|
|
33307
33924
|
//#region src/features/skills/claudecode-skill.ts
|
|
33308
33925
|
const ClaudecodeSkillFrontmatterSchema = zod_mini.z.looseObject({
|
|
33309
33926
|
name: zod_mini.z.string(),
|
|
@@ -33389,6 +34006,41 @@ var ClaudecodeSkill = class extends ToolSkill {
|
|
|
33389
34006
|
alternativeSkillRoots: [CLAUDECODE_SCHEDULED_TASKS_DIR_PATH]
|
|
33390
34007
|
};
|
|
33391
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
|
+
}
|
|
33392
34044
|
getFrontmatter() {
|
|
33393
34045
|
return ClaudecodeSkillFrontmatterSchema.parse(this.requireMainFileFrontmatter());
|
|
33394
34046
|
}
|
|
@@ -36787,7 +37439,8 @@ var TaktSkill = class TaktSkill extends ToolSkill {
|
|
|
36787
37439
|
getDirPath() {
|
|
36788
37440
|
const fullPath = (0, node_path.join)(this.outputRoot, this.relativeDirPath);
|
|
36789
37441
|
const resolvedFull = (0, node_path.resolve)(fullPath);
|
|
36790
|
-
const
|
|
37442
|
+
const resolvedBase = (0, node_path.resolve)(this.outputRoot);
|
|
37443
|
+
const rel = (0, node_path.relative)(resolvedBase, resolvedFull);
|
|
36791
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}"`);
|
|
36792
37445
|
return fullPath;
|
|
36793
37446
|
}
|
|
@@ -37337,6 +37990,23 @@ var ZedSkill = class ZedSkill extends ToolSkill {
|
|
|
37337
37990
|
}
|
|
37338
37991
|
};
|
|
37339
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
|
|
37340
38010
|
//#region src/features/skills/skills-processor.ts
|
|
37341
38011
|
const SkillsProcessorToolTargetSchema = zod_mini.z.enum(skillsProcessorToolTargetTuple);
|
|
37342
38012
|
/**
|
|
@@ -37357,7 +38027,8 @@ const toolSkillFactories = /* @__PURE__ */ new Map([
|
|
|
37357
38027
|
meta: {
|
|
37358
38028
|
supportsProject: true,
|
|
37359
38029
|
supportsSimulated: false,
|
|
37360
|
-
supportsGlobal: true
|
|
38030
|
+
supportsGlobal: true,
|
|
38031
|
+
lenientImport: true
|
|
37361
38032
|
}
|
|
37362
38033
|
}],
|
|
37363
38034
|
["aiassistant", {
|
|
@@ -37608,6 +38279,14 @@ const toolSkillFactories = /* @__PURE__ */ new Map([
|
|
|
37608
38279
|
supportsGlobal: true
|
|
37609
38280
|
}
|
|
37610
38281
|
}],
|
|
38282
|
+
["zoocode", {
|
|
38283
|
+
class: ZoocodeSkill,
|
|
38284
|
+
meta: {
|
|
38285
|
+
supportsProject: true,
|
|
38286
|
+
supportsSimulated: false,
|
|
38287
|
+
supportsGlobal: true
|
|
38288
|
+
}
|
|
38289
|
+
}],
|
|
37611
38290
|
["rovodev", {
|
|
37612
38291
|
class: RovodevSkill,
|
|
37613
38292
|
meta: {
|
|
@@ -37770,7 +38449,7 @@ var SkillsProcessor = class extends DirFeatureProcessor {
|
|
|
37770
38449
|
for (const root of roots) {
|
|
37771
38450
|
const rootOutputRoot = typeof root === "string" ? this.outputRoot : root.outputRoot;
|
|
37772
38451
|
const relativeDirPath = typeof root === "string" ? root : root.relativeDirPath;
|
|
37773
|
-
const
|
|
38452
|
+
const isLenientRoot = configuredRootPaths.has(relativeDirPath) || factory.meta.lenientImport === true || isAgentSkillsInteropRoot(relativeDirPath);
|
|
37774
38453
|
const skillsDirPath = (0, node_path.join)(rootOutputRoot, relativeDirPath);
|
|
37775
38454
|
if (!await directoryExists(skillsDirPath)) continue;
|
|
37776
38455
|
const dirPaths = await findFilesByGlobs((0, node_path.join)(skillsDirPath, "*"), { type: "dir" });
|
|
@@ -37794,7 +38473,7 @@ var SkillsProcessor = class extends DirFeatureProcessor {
|
|
|
37794
38473
|
global: this.global
|
|
37795
38474
|
});
|
|
37796
38475
|
} catch (error) {
|
|
37797
|
-
if (!
|
|
38476
|
+
if (!isLenientRoot) throw error;
|
|
37798
38477
|
this.logger.warn(`Skipping ${(0, node_path.join)(relativeDirPath, dirName)}: ${formatError(error)}`);
|
|
37799
38478
|
return null;
|
|
37800
38479
|
}
|
|
@@ -37809,12 +38488,20 @@ var SkillsProcessor = class extends DirFeatureProcessor {
|
|
|
37809
38488
|
const fromFlatFile = factory.class.fromFlatFile;
|
|
37810
38489
|
const directoryStems = new Set(ownedDirNames);
|
|
37811
38490
|
const flatFilePaths = (await findFilesByGlobs((0, node_path.join)(skillsDirPath, "*.md"), { type: "file" })).filter((filePath) => !directoryStems.has((0, node_path.basename)(filePath, ".md")));
|
|
37812
|
-
const flatSkills = await Promise.all(flatFilePaths.map((filePath) =>
|
|
37813
|
-
|
|
37814
|
-
|
|
37815
|
-
|
|
37816
|
-
|
|
37817
|
-
|
|
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);
|
|
37818
38505
|
for (const skill of flatSkills) {
|
|
37819
38506
|
const skillName = skill.getImportIdentity();
|
|
37820
38507
|
if (seenSkillNames.has(skillName)) continue;
|
|
@@ -37887,14 +38574,15 @@ var SkillsProcessor = class extends DirFeatureProcessor {
|
|
|
37887
38574
|
*/
|
|
37888
38575
|
static getSimulatedConventionSection({ skillList }) {
|
|
37889
38576
|
if (!skillList || skillList.length === 0) return "";
|
|
38577
|
+
const skillListWithAtPrefix = skillList.map((skill) => ({
|
|
38578
|
+
...skill,
|
|
38579
|
+
path: `@${skill.path}`
|
|
38580
|
+
}));
|
|
37890
38581
|
return `## Simulated Skills
|
|
37891
38582
|
|
|
37892
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.
|
|
37893
38584
|
|
|
37894
|
-
${(0, _toon_format_toon.encode)({ skillList:
|
|
37895
|
-
...skill,
|
|
37896
|
-
path: `@${skill.path}`
|
|
37897
|
-
})) })}`;
|
|
38585
|
+
${(0, _toon_format_toon.encode)({ skillList: skillListWithAtPrefix })}`;
|
|
37898
38586
|
}
|
|
37899
38587
|
/**
|
|
37900
38588
|
* Return the tool targets that this processor supports in global mode
|
|
@@ -40498,7 +41186,8 @@ var HermesagentSubagent = class HermesagentSubagent extends ToolSubagent {
|
|
|
40498
41186
|
return getHermesagentSharedConfigWritePaths();
|
|
40499
41187
|
}
|
|
40500
41188
|
static getSettablePathsForRulesyncSubagent(rulesyncSubagent) {
|
|
40501
|
-
|
|
41189
|
+
const slug = subagentSlug(rulesyncSubagent.getRelativePathFromCwd());
|
|
41190
|
+
return [(0, node_path.join)(HERMESAGENT_RULESYNC_SUBAGENTS_DIR_PATH, `${slug}.json`)];
|
|
40502
41191
|
}
|
|
40503
41192
|
toRulesyncSubagent() {
|
|
40504
41193
|
const slug = (0, node_path.basename)(this.getRelativeFilePath(), ".json");
|
|
@@ -41733,7 +42422,7 @@ function stringifyRooModes(modes) {
|
|
|
41733
42422
|
*
|
|
41734
42423
|
* @see https://roocodeinc.github.io/Roo-Code/features/custom-modes
|
|
41735
42424
|
*/
|
|
41736
|
-
var RooSubagent = class
|
|
42425
|
+
var RooSubagent = class extends ToolSubagent {
|
|
41737
42426
|
modes;
|
|
41738
42427
|
constructor({ modes, ...rest }) {
|
|
41739
42428
|
if (rest.validate !== false) {
|
|
@@ -41789,7 +42478,7 @@ var RooSubagent = class RooSubagent extends ToolSubagent {
|
|
|
41789
42478
|
const mode = this.toRooMode(rulesyncSubagent);
|
|
41790
42479
|
bySlug.set(mode.slug, mode);
|
|
41791
42480
|
}
|
|
41792
|
-
return new
|
|
42481
|
+
return new this({
|
|
41793
42482
|
outputRoot,
|
|
41794
42483
|
relativeDirPath: this.getSettablePaths().relativeDirPath,
|
|
41795
42484
|
relativeFilePath: ROO_MODES_FILE_NAME,
|
|
@@ -41868,7 +42557,7 @@ var RooSubagent = class RooSubagent extends ToolSubagent {
|
|
|
41868
42557
|
}
|
|
41869
42558
|
const result = RooModesFileSchema.safeParse(parsed ?? { customModes: [] });
|
|
41870
42559
|
if (!result.success) throw new Error(`Invalid .roomodes in ${filePath}: ${formatError(result.error)}`);
|
|
41871
|
-
return new
|
|
42560
|
+
return new this({
|
|
41872
42561
|
outputRoot,
|
|
41873
42562
|
relativeDirPath: paths.relativeDirPath,
|
|
41874
42563
|
relativeFilePath: (0, node_path.basename)(relativeFilePath),
|
|
@@ -41877,7 +42566,7 @@ var RooSubagent = class RooSubagent extends ToolSubagent {
|
|
|
41877
42566
|
});
|
|
41878
42567
|
}
|
|
41879
42568
|
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
41880
|
-
return new
|
|
42569
|
+
return new this({
|
|
41881
42570
|
outputRoot,
|
|
41882
42571
|
relativeDirPath,
|
|
41883
42572
|
relativeFilePath,
|
|
@@ -42123,6 +42812,60 @@ var VibeSubagent = class VibeSubagent extends ToolSubagent {
|
|
|
42123
42812
|
}
|
|
42124
42813
|
};
|
|
42125
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
|
|
42126
42869
|
//#region src/features/subagents/subagents-processor.ts
|
|
42127
42870
|
const SubagentsProcessorToolTargetSchema = zod_mini.z.enum(subagentsProcessorToolTargetTuple);
|
|
42128
42871
|
/**
|
|
@@ -42362,6 +43105,14 @@ const toolSubagentFactories = /* @__PURE__ */ new Map([
|
|
|
42362
43105
|
filePattern: ".roomodes"
|
|
42363
43106
|
}
|
|
42364
43107
|
}],
|
|
43108
|
+
["zoocode", {
|
|
43109
|
+
class: ZoocodeSubagent,
|
|
43110
|
+
meta: {
|
|
43111
|
+
supportsSimulated: false,
|
|
43112
|
+
supportsGlobal: false,
|
|
43113
|
+
filePattern: ".roomodes"
|
|
43114
|
+
}
|
|
43115
|
+
}],
|
|
42365
43116
|
["rovodev", {
|
|
42366
43117
|
class: RovodevSubagent,
|
|
42367
43118
|
meta: {
|
|
@@ -42622,11 +43373,13 @@ For example, if the user instructs \`Call planner subagent to plan the refactori
|
|
|
42622
43373
|
//#region src/features/rules/tool-rule.ts
|
|
42623
43374
|
var ToolRule = class extends ToolFile {
|
|
42624
43375
|
root;
|
|
43376
|
+
localRoot;
|
|
42625
43377
|
description;
|
|
42626
43378
|
globs;
|
|
42627
|
-
constructor({ root = false, description, globs, ...rest }) {
|
|
43379
|
+
constructor({ root = false, localRoot = false, description, globs, ...rest }) {
|
|
42628
43380
|
super(rest);
|
|
42629
43381
|
this.root = root;
|
|
43382
|
+
this.localRoot = localRoot;
|
|
42630
43383
|
this.description = description;
|
|
42631
43384
|
this.globs = globs;
|
|
42632
43385
|
}
|
|
@@ -42709,6 +43462,38 @@ var ToolRule = class extends ToolFile {
|
|
|
42709
43462
|
isRoot() {
|
|
42710
43463
|
return this.root;
|
|
42711
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
|
+
}
|
|
42712
43497
|
/**
|
|
42713
43498
|
* Whether this rule must be left out of the root rule's reference/MCP
|
|
42714
43499
|
* instruction listings even though it is a non-root survivor. Used by files
|
|
@@ -42741,31 +43526,6 @@ function buildToolPath(toolDir, subDir, excludeToolDir) {
|
|
|
42741
43526
|
}
|
|
42742
43527
|
//#endregion
|
|
42743
43528
|
//#region src/features/rules/agentsmd-rule.ts
|
|
42744
|
-
/**
|
|
42745
|
-
* Dependency trees never scanned for nested `AGENTS.md` files, at any depth. An
|
|
42746
|
-
* `AGENTS.md` there describes somebody else's project, and neither name is ever
|
|
42747
|
-
* a package name. Hidden directories are excluded separately, because an
|
|
42748
|
-
* `AGENTS.md` inside one is another tool's generated output (rulesync writes
|
|
42749
|
-
* several itself).
|
|
42750
|
-
*/
|
|
42751
|
-
const NESTED_SCAN_EXCLUDED_DIRS_ANY_DEPTH = ["node_modules", "__pycache__"];
|
|
42752
|
-
/**
|
|
42753
|
-
* Build, vendoring and scratch directories, excluded at the **project root
|
|
42754
|
-
* only**. A top-level `build/` is a build directory; `packages/build/` is a
|
|
42755
|
-
* package, and dropping it silently would lose a real subproject.
|
|
42756
|
-
*/
|
|
42757
|
-
const NESTED_SCAN_EXCLUDED_ROOT_DIRS = [
|
|
42758
|
-
"vendor",
|
|
42759
|
-
"third_party",
|
|
42760
|
-
"dist",
|
|
42761
|
-
"build",
|
|
42762
|
-
"out",
|
|
42763
|
-
"target",
|
|
42764
|
-
"coverage",
|
|
42765
|
-
"tmp",
|
|
42766
|
-
"temp",
|
|
42767
|
-
"venv"
|
|
42768
|
-
];
|
|
42769
43529
|
var AgentsMdRule = class AgentsMdRule extends ToolRule {
|
|
42770
43530
|
constructor({ fileContent, root, ...rest }) {
|
|
42771
43531
|
super({
|
|
@@ -42822,7 +43582,8 @@ var AgentsMdRule = class AgentsMdRule extends ToolRule {
|
|
|
42822
43582
|
const normalizedDirPath = relativeDirPath === void 0 ? "." : toPosixPath(relativeDirPath);
|
|
42823
43583
|
const isNested = relativeFilePath === "AGENTS.md" && normalizedDirPath !== "." && normalizedDirPath !== "" && !normalizedDirPath.startsWith(".");
|
|
42824
43584
|
const isRoot = !isNested && relativeFilePath === "AGENTS.md" && (normalizedDirPath === "." || normalizedDirPath === "");
|
|
42825
|
-
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));
|
|
42826
43587
|
return new AgentsMdRule({
|
|
42827
43588
|
outputRoot,
|
|
42828
43589
|
relativeDirPath: isNested ? (0, node_path.join)(normalizedDirPath) : isRoot ? this.getSettablePaths().root.relativeDirPath : this.getSettablePaths().nonRoot.relativeDirPath,
|
|
@@ -43129,7 +43890,8 @@ var AntigravityCliRule = class AntigravityCliRule extends ToolRule {
|
|
|
43129
43890
|
});
|
|
43130
43891
|
}
|
|
43131
43892
|
if (!paths.nonRoot) throw new Error(`nonRoot path is not set for ${relativeFilePath}`);
|
|
43132
|
-
const
|
|
43893
|
+
const relativePath = (0, node_path.join)(paths.nonRoot.relativeDirPath, relativeFilePath);
|
|
43894
|
+
const fileContent = await readFileContent((0, node_path.join)(outputRoot, relativePath));
|
|
43133
43895
|
return new AntigravityCliRule({
|
|
43134
43896
|
outputRoot,
|
|
43135
43897
|
relativeDirPath: paths.nonRoot.relativeDirPath,
|
|
@@ -43590,7 +44352,8 @@ var AugmentcodeLegacyRule = class AugmentcodeLegacyRule extends ToolRule {
|
|
|
43590
44352
|
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true }) {
|
|
43591
44353
|
const settablePaths = this.getSettablePaths();
|
|
43592
44354
|
const isRoot = relativeFilePath === settablePaths.root.relativeFilePath;
|
|
43593
|
-
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));
|
|
43594
44357
|
return new AugmentcodeLegacyRule({
|
|
43595
44358
|
outputRoot,
|
|
43596
44359
|
relativeDirPath: isRoot ? settablePaths.root.relativeDirPath : settablePaths.nonRoot.relativeDirPath,
|
|
@@ -43814,7 +44577,8 @@ var ClaudecodeLegacyRule = class ClaudecodeLegacyRule extends ToolRule {
|
|
|
43814
44577
|
});
|
|
43815
44578
|
}
|
|
43816
44579
|
if (!paths.nonRoot) throw new Error(`nonRoot path is not set for ${relativeFilePath}`);
|
|
43817
|
-
const
|
|
44580
|
+
const relativePath = (0, node_path.join)(paths.nonRoot.relativeDirPath, relativeFilePath);
|
|
44581
|
+
const fileContent = await readFileContent((0, node_path.join)(outputRoot, relativePath));
|
|
43818
44582
|
return new ClaudecodeLegacyRule({
|
|
43819
44583
|
outputRoot,
|
|
43820
44584
|
relativeDirPath: paths.nonRoot.relativeDirPath,
|
|
@@ -43935,7 +44699,8 @@ var ClaudecodeRule = class ClaudecodeRule extends ToolRule {
|
|
|
43935
44699
|
});
|
|
43936
44700
|
}
|
|
43937
44701
|
if (!paths.nonRoot) throw new Error(`nonRoot path is not set for ${relativeFilePath}`);
|
|
43938
|
-
const
|
|
44702
|
+
const relativePath = (0, node_path.join)(paths.nonRoot.relativeDirPath, relativeFilePath);
|
|
44703
|
+
const filePath = (0, node_path.join)(outputRoot, relativePath);
|
|
43939
44704
|
const { frontmatter, body: content } = parseFrontmatter(await readFileContent(filePath), filePath);
|
|
43940
44705
|
const result = ClaudecodeRuleFrontmatterSchema.safeParse(frontmatter);
|
|
43941
44706
|
if (!result.success) throw new Error(`Invalid frontmatter in ${filePath}: ${formatError(result.error)}`);
|
|
@@ -44322,7 +45087,8 @@ var CodexcliRule = class CodexcliRule extends ToolRule {
|
|
|
44322
45087
|
}
|
|
44323
45088
|
static async fromFile({ outputRoot = process.cwd(), relativeFilePath: _relativeFilePath, validate = true, global = false }) {
|
|
44324
45089
|
const { root } = this.getSettablePaths({ global });
|
|
44325
|
-
const
|
|
45090
|
+
const relativePath = (0, node_path.join)(root.relativeDirPath, root.relativeFilePath);
|
|
45091
|
+
const fileContent = await readFileContent((0, node_path.join)(outputRoot, relativePath));
|
|
44326
45092
|
return new CodexcliRule({
|
|
44327
45093
|
outputRoot,
|
|
44328
45094
|
relativeDirPath: root.relativeDirPath,
|
|
@@ -44491,7 +45257,8 @@ var CopilotRule = class CopilotRule extends ToolRule {
|
|
|
44491
45257
|
}) : relativeFilePath === paths.root.relativeFilePath;
|
|
44492
45258
|
const resolvedRelativeDirPath = relativeDirPath ?? (isRoot ? paths.root.relativeDirPath : paths.nonRoot?.relativeDirPath ?? paths.root.relativeDirPath);
|
|
44493
45259
|
if (isRoot) {
|
|
44494
|
-
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));
|
|
44495
45262
|
return new CopilotRule({
|
|
44496
45263
|
outputRoot,
|
|
44497
45264
|
relativeDirPath: paths.root.relativeDirPath,
|
|
@@ -44503,7 +45270,8 @@ var CopilotRule = class CopilotRule extends ToolRule {
|
|
|
44503
45270
|
});
|
|
44504
45271
|
}
|
|
44505
45272
|
if (!paths.nonRoot) throw new Error(`nonRoot path is not set for ${relativeFilePath}`);
|
|
44506
|
-
const
|
|
45273
|
+
const relativePath = (0, node_path.join)(resolvedRelativeDirPath, relativeFilePath);
|
|
45274
|
+
const filePath = (0, node_path.join)(outputRoot, relativePath);
|
|
44507
45275
|
const { frontmatter, body: content } = parseFrontmatter(await readFileContent(filePath), filePath);
|
|
44508
45276
|
const result = CopilotRuleFrontmatterSchema.safeParse(frontmatter);
|
|
44509
45277
|
if (!result.success) throw new Error(`Invalid frontmatter in ${filePath}: ${formatError(result.error)}`);
|
|
@@ -44775,7 +45543,8 @@ var DeepagentsRule = class DeepagentsRule extends ToolRule {
|
|
|
44775
45543
|
}
|
|
44776
45544
|
static async fromFile({ outputRoot = process.cwd(), relativeFilePath: _relativeFilePath, validate = true, global = false }) {
|
|
44777
45545
|
const settablePaths = this.getSettablePaths({ global });
|
|
44778
|
-
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));
|
|
44779
45548
|
return new DeepagentsRule({
|
|
44780
45549
|
outputRoot,
|
|
44781
45550
|
relativeDirPath: settablePaths.root.relativeDirPath,
|
|
@@ -45202,7 +45971,8 @@ var FactorydroidRule = class FactorydroidRule extends ToolRule {
|
|
|
45202
45971
|
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
|
|
45203
45972
|
const paths = this.getSettablePaths({ global });
|
|
45204
45973
|
if (relativeFilePath === paths.root.relativeFilePath) {
|
|
45205
|
-
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));
|
|
45206
45976
|
return new FactorydroidRule({
|
|
45207
45977
|
outputRoot,
|
|
45208
45978
|
relativeDirPath: paths.root.relativeDirPath,
|
|
@@ -45213,7 +45983,8 @@ var FactorydroidRule = class FactorydroidRule extends ToolRule {
|
|
|
45213
45983
|
});
|
|
45214
45984
|
}
|
|
45215
45985
|
if (!paths.nonRoot) throw new Error(`nonRoot path is not set for ${relativeFilePath}`);
|
|
45216
|
-
const
|
|
45986
|
+
const relativePath = (0, node_path.join)(paths.nonRoot.relativeDirPath, relativeFilePath);
|
|
45987
|
+
const fileContent = await readFileContent((0, node_path.join)(outputRoot, relativePath));
|
|
45217
45988
|
return new FactorydroidRule({
|
|
45218
45989
|
outputRoot,
|
|
45219
45990
|
relativeDirPath: paths.nonRoot.relativeDirPath,
|
|
@@ -45443,7 +46214,8 @@ var HermesagentRule = class HermesagentRule extends ToolRule {
|
|
|
45443
46214
|
}
|
|
45444
46215
|
static async fromFile({ outputRoot = process.cwd(), relativeFilePath: _relativeFilePath, validate = true }) {
|
|
45445
46216
|
const { root } = this.getSettablePaths();
|
|
45446
|
-
const
|
|
46217
|
+
const relativePath = (0, node_path.join)(root.relativeDirPath, root.relativeFilePath);
|
|
46218
|
+
const fileContent = await readFileContent((0, node_path.join)(outputRoot, relativePath));
|
|
45447
46219
|
return new HermesagentRule({
|
|
45448
46220
|
outputRoot,
|
|
45449
46221
|
relativeDirPath: root.relativeDirPath,
|
|
@@ -45554,7 +46326,8 @@ var JunieRule = class JunieRule extends ToolRule {
|
|
|
45554
46326
|
});
|
|
45555
46327
|
}
|
|
45556
46328
|
const relativeDirPath = this.getSettablePaths().root.relativeDirPath;
|
|
45557
|
-
const
|
|
46329
|
+
const relativePath = (0, node_path.join)(relativeDirPath, relativeFilePath);
|
|
46330
|
+
const fileContent = await readFileContent((0, node_path.join)(outputRoot, relativePath));
|
|
45558
46331
|
return new JunieRule({
|
|
45559
46332
|
outputRoot,
|
|
45560
46333
|
relativeDirPath,
|
|
@@ -45654,7 +46427,8 @@ var KiloRule = class KiloRule extends ToolRule {
|
|
|
45654
46427
|
});
|
|
45655
46428
|
}
|
|
45656
46429
|
if (!paths.nonRoot) throw new Error(`nonRoot path is not set for ${relativeFilePath}`);
|
|
45657
|
-
const
|
|
46430
|
+
const relativePath = (0, node_path.join)(paths.nonRoot.relativeDirPath, relativeFilePath);
|
|
46431
|
+
const fileContent = await readFileContent((0, node_path.join)(outputRoot, relativePath));
|
|
45658
46432
|
return new KiloRule({
|
|
45659
46433
|
outputRoot,
|
|
45660
46434
|
relativeDirPath: paths.nonRoot.relativeDirPath,
|
|
@@ -45991,10 +46765,13 @@ var KiroIdeRule = class extends KiroRule {
|
|
|
45991
46765
|
//#region src/features/rules/opencode-rule.ts
|
|
45992
46766
|
var OpenCodeRule = class OpenCodeRule extends ToolRule {
|
|
45993
46767
|
static getSettablePaths({ global, excludeToolDir } = {}) {
|
|
45994
|
-
if (global) return {
|
|
45995
|
-
|
|
45996
|
-
|
|
45997
|
-
|
|
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
|
+
};
|
|
45998
46775
|
return {
|
|
45999
46776
|
root: {
|
|
46000
46777
|
relativeDirPath: ".",
|
|
@@ -46004,7 +46781,7 @@ var OpenCodeRule = class OpenCodeRule extends ToolRule {
|
|
|
46004
46781
|
};
|
|
46005
46782
|
}
|
|
46006
46783
|
static getExtraSharedWritePaths({ global = false } = {}) {
|
|
46007
|
-
return
|
|
46784
|
+
return [OpencodeMcp.getSettablePaths({ global })];
|
|
46008
46785
|
}
|
|
46009
46786
|
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
|
|
46010
46787
|
const paths = this.getSettablePaths({ global });
|
|
@@ -46021,7 +46798,8 @@ var OpenCodeRule = class OpenCodeRule extends ToolRule {
|
|
|
46021
46798
|
});
|
|
46022
46799
|
}
|
|
46023
46800
|
if (!paths.nonRoot) throw new Error(`nonRoot path is not set for ${relativeFilePath}`);
|
|
46024
|
-
const
|
|
46801
|
+
const relativePath = (0, node_path.join)(paths.nonRoot.relativeDirPath, relativeFilePath);
|
|
46802
|
+
const fileContent = await readFileContent((0, node_path.join)(outputRoot, relativePath));
|
|
46025
46803
|
return new OpenCodeRule({
|
|
46026
46804
|
outputRoot,
|
|
46027
46805
|
relativeDirPath: paths.nonRoot.relativeDirPath,
|
|
@@ -46136,7 +46914,8 @@ var PiRule = class PiRule extends ToolRule {
|
|
|
46136
46914
|
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
|
|
46137
46915
|
const { root, appendSystemPrompt } = this.getSettablePaths({ global });
|
|
46138
46916
|
if (relativeFilePath === "APPEND_SYSTEM.md") {
|
|
46139
|
-
const
|
|
46917
|
+
const relativePath = (0, node_path.join)(appendSystemPrompt.relativeDirPath, appendSystemPrompt.relativeFilePath);
|
|
46918
|
+
const fileContent = await readFileContent((0, node_path.join)(outputRoot, relativePath));
|
|
46140
46919
|
return new PiRule({
|
|
46141
46920
|
outputRoot,
|
|
46142
46921
|
relativeDirPath: appendSystemPrompt.relativeDirPath,
|
|
@@ -46147,7 +46926,8 @@ var PiRule = class PiRule extends ToolRule {
|
|
|
46147
46926
|
appendSystemPrompt: true
|
|
46148
46927
|
});
|
|
46149
46928
|
}
|
|
46150
|
-
const
|
|
46929
|
+
const relativePath = (0, node_path.join)(root.relativeDirPath, root.relativeFilePath);
|
|
46930
|
+
const fileContent = await readFileContent((0, node_path.join)(outputRoot, relativePath));
|
|
46151
46931
|
return new PiRule({
|
|
46152
46932
|
outputRoot,
|
|
46153
46933
|
relativeDirPath: root.relativeDirPath,
|
|
@@ -46443,6 +47223,13 @@ var QwencodeRule = class QwencodeRule extends ToolRule {
|
|
|
46443
47223
|
toolTarget: "qwencode"
|
|
46444
47224
|
});
|
|
46445
47225
|
}
|
|
47226
|
+
/**
|
|
47227
|
+
* The personal local context file lives under `.qwen/`, not at the project
|
|
47228
|
+
* root where the settable root path points, so override the deletion glob.
|
|
47229
|
+
*/
|
|
47230
|
+
static getLocalRootFileGlob({ outputRoot, fileName }) {
|
|
47231
|
+
return (0, node_path.join)(outputRoot, QWENCODE_DIR, fileName);
|
|
47232
|
+
}
|
|
46446
47233
|
};
|
|
46447
47234
|
//#endregion
|
|
46448
47235
|
//#region src/features/rules/reasonix-rule.ts
|
|
@@ -46460,9 +47247,51 @@ var ReasonixRule = class ReasonixRule extends ToolRule {
|
|
|
46460
47247
|
relativeFilePath: REASONIX_RULE_FILE_NAME
|
|
46461
47248
|
} };
|
|
46462
47249
|
}
|
|
46463
|
-
|
|
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 }) {
|
|
46464
47281
|
const { root } = this.getSettablePaths({ global });
|
|
46465
|
-
|
|
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));
|
|
46466
47295
|
return new ReasonixRule({
|
|
46467
47296
|
outputRoot,
|
|
46468
47297
|
relativeDirPath: root.relativeDirPath,
|
|
@@ -46474,7 +47303,17 @@ var ReasonixRule = class ReasonixRule extends ToolRule {
|
|
|
46474
47303
|
}
|
|
46475
47304
|
static fromRulesyncRule({ outputRoot = process.cwd(), rulesyncRule, validate = true, global = false }) {
|
|
46476
47305
|
const { root } = this.getSettablePaths({ global });
|
|
46477
|
-
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
|
+
});
|
|
46478
47317
|
return new ReasonixRule({
|
|
46479
47318
|
outputRoot,
|
|
46480
47319
|
relativeDirPath: root.relativeDirPath,
|
|
@@ -46485,7 +47324,22 @@ var ReasonixRule = class ReasonixRule extends ToolRule {
|
|
|
46485
47324
|
});
|
|
46486
47325
|
}
|
|
46487
47326
|
toRulesyncRule() {
|
|
46488
|
-
|
|
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
|
+
});
|
|
46489
47343
|
}
|
|
46490
47344
|
validate() {
|
|
46491
47345
|
return {
|
|
@@ -46655,7 +47509,7 @@ var RooRule = class RooRule extends ToolRule {
|
|
|
46655
47509
|
* Glob for the `separate-local-file` deletion; Roo reads `AGENTS.local.md`
|
|
46656
47510
|
* at the project root, not under `.roo/` (mirrors rovodev).
|
|
46657
47511
|
*/
|
|
46658
|
-
static
|
|
47512
|
+
static getLocalRootFileGlob({ outputRoot, fileName }) {
|
|
46659
47513
|
return (0, node_path.join)(outputRoot, fileName);
|
|
46660
47514
|
}
|
|
46661
47515
|
};
|
|
@@ -46787,19 +47641,6 @@ var RovodevRule = class RovodevRule extends ToolRule {
|
|
|
46787
47641
|
}));
|
|
46788
47642
|
}
|
|
46789
47643
|
toRulesyncRule() {
|
|
46790
|
-
if (this.getRelativeFilePath() === "AGENTS.local.md") return new RulesyncRule({
|
|
46791
|
-
outputRoot: this.getOutputRoot(),
|
|
46792
|
-
relativeDirPath: RULESYNC_RULES_RELATIVE_DIR_PATH,
|
|
46793
|
-
relativeFilePath: ROVODEV_LEGACY_RULE_FILE_NAME,
|
|
46794
|
-
frontmatter: {
|
|
46795
|
-
targets: ["rovodev"],
|
|
46796
|
-
root: false,
|
|
46797
|
-
localRoot: true,
|
|
46798
|
-
globs: []
|
|
46799
|
-
},
|
|
46800
|
-
body: this.getFileContent(),
|
|
46801
|
-
validate: true
|
|
46802
|
-
});
|
|
46803
47644
|
if (!this.isRoot()) return new RulesyncRule({
|
|
46804
47645
|
outputRoot: this.getOutputRoot(),
|
|
46805
47646
|
relativeDirPath: RULESYNC_RULES_RELATIVE_DIR_PATH,
|
|
@@ -46856,7 +47697,7 @@ var RovodevRule = class RovodevRule extends ToolRule {
|
|
|
46856
47697
|
};
|
|
46857
47698
|
}
|
|
46858
47699
|
/** Glob for the `separate-local-file` deletion; rovodev writes it at project root, not under `.rovodev/`. */
|
|
46859
|
-
static
|
|
47700
|
+
static getLocalRootFileGlob({ outputRoot, fileName }) {
|
|
46860
47701
|
return (0, node_path.join)(outputRoot, fileName);
|
|
46861
47702
|
}
|
|
46862
47703
|
};
|
|
@@ -47046,15 +47887,20 @@ var WarpRule = class WarpRule extends ToolRule {
|
|
|
47046
47887
|
root: root ?? false
|
|
47047
47888
|
});
|
|
47048
47889
|
}
|
|
47049
|
-
static getSettablePaths(
|
|
47890
|
+
static getSettablePaths({ global = false } = {}) {
|
|
47891
|
+
if (global) return { root: {
|
|
47892
|
+
relativeDirPath: WARP_GLOBAL_RULE_DIR,
|
|
47893
|
+
relativeFilePath: WARP_RULE_FILE_NAME
|
|
47894
|
+
} };
|
|
47050
47895
|
return { root: {
|
|
47051
47896
|
relativeDirPath: ".",
|
|
47052
47897
|
relativeFilePath: WARP_RULE_FILE_NAME
|
|
47053
47898
|
} };
|
|
47054
47899
|
}
|
|
47055
|
-
static async fromFile({ outputRoot = process.cwd(), relativeFilePath: _relativeFilePath, validate = true }) {
|
|
47056
|
-
const { root } = this.getSettablePaths();
|
|
47057
|
-
const
|
|
47900
|
+
static async fromFile({ outputRoot = process.cwd(), relativeFilePath: _relativeFilePath, validate = true, global = false }) {
|
|
47901
|
+
const { root } = this.getSettablePaths({ global });
|
|
47902
|
+
const relativePath = (0, node_path.join)(root.relativeDirPath, root.relativeFilePath);
|
|
47903
|
+
const fileContent = await readFileContent((0, node_path.join)(outputRoot, relativePath));
|
|
47058
47904
|
return new WarpRule({
|
|
47059
47905
|
outputRoot,
|
|
47060
47906
|
relativeDirPath: root.relativeDirPath,
|
|
@@ -47064,8 +47910,8 @@ var WarpRule = class WarpRule extends ToolRule {
|
|
|
47064
47910
|
root: true
|
|
47065
47911
|
});
|
|
47066
47912
|
}
|
|
47067
|
-
static fromRulesyncRule({ outputRoot = process.cwd(), rulesyncRule, validate = true }) {
|
|
47068
|
-
const { root } = this.getSettablePaths();
|
|
47913
|
+
static fromRulesyncRule({ outputRoot = process.cwd(), rulesyncRule, validate = true, global = false }) {
|
|
47914
|
+
const { root } = this.getSettablePaths({ global });
|
|
47069
47915
|
const isRoot = rulesyncRule.getFrontmatter().root ?? false;
|
|
47070
47916
|
return new WarpRule({
|
|
47071
47917
|
outputRoot,
|
|
@@ -47085,8 +47931,8 @@ var WarpRule = class WarpRule extends ToolRule {
|
|
|
47085
47931
|
error: null
|
|
47086
47932
|
};
|
|
47087
47933
|
}
|
|
47088
|
-
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
47089
|
-
const { root } = this.getSettablePaths();
|
|
47934
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
47935
|
+
const { root } = this.getSettablePaths({ global });
|
|
47090
47936
|
const isRoot = relativeFilePath === root.relativeFilePath && relativeDirPath === root.relativeDirPath;
|
|
47091
47937
|
return new WarpRule({
|
|
47092
47938
|
outputRoot,
|
|
@@ -47179,6 +48025,27 @@ var ZedRule = class ZedRule extends ToolRule {
|
|
|
47179
48025
|
}
|
|
47180
48026
|
};
|
|
47181
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
|
|
47182
48049
|
//#region src/features/rules/rules-processor.ts
|
|
47183
48050
|
const RulesProcessorToolTargetSchema = zod_mini.z.enum(rulesProcessorToolTargetTuple);
|
|
47184
48051
|
const formatRulePaths = (rules) => rules.map((r) => (0, node_path.join)(r.getRelativeDirPath(), r.getRelativeFilePath())).join(", ");
|
|
@@ -47441,6 +48308,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
47441
48308
|
supportsGlobal: true,
|
|
47442
48309
|
ruleDiscoveryMode: "toon",
|
|
47443
48310
|
mcpInstructionsRegistrar: OpencodeMcp,
|
|
48311
|
+
mcpInstructionsRegistrarGlobal: true,
|
|
47444
48312
|
collisionPolicy: "compose"
|
|
47445
48313
|
}
|
|
47446
48314
|
}],
|
|
@@ -47459,6 +48327,8 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
47459
48327
|
extension: "md",
|
|
47460
48328
|
supportsGlobal: true,
|
|
47461
48329
|
ruleDiscoveryMode: "auto",
|
|
48330
|
+
localRootMode: "separate-local-file",
|
|
48331
|
+
localRootFileName: QWENCODE_LOCAL_RULE_FILE_NAME,
|
|
47462
48332
|
additionalConventions: { subagents: { subagentClass: QwencodeSubagent } }
|
|
47463
48333
|
}
|
|
47464
48334
|
}],
|
|
@@ -47503,6 +48373,16 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
47503
48373
|
localRootFileName: "AGENTS.local.md"
|
|
47504
48374
|
}
|
|
47505
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
|
+
}],
|
|
47506
48386
|
["takt", {
|
|
47507
48387
|
class: TaktRule,
|
|
47508
48388
|
meta: {
|
|
@@ -47523,7 +48403,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
47523
48403
|
class: WarpRule,
|
|
47524
48404
|
meta: {
|
|
47525
48405
|
extension: "md",
|
|
47526
|
-
supportsGlobal:
|
|
48406
|
+
supportsGlobal: true,
|
|
47527
48407
|
ruleDiscoveryMode: "toon",
|
|
47528
48408
|
collisionPolicy: "fold"
|
|
47529
48409
|
}
|
|
@@ -47675,18 +48555,19 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
47675
48555
|
/**
|
|
47676
48556
|
* Non-root rules of some tools are not auto-loaded; the tool's MCP feature
|
|
47677
48557
|
* registers them in its shared config's `instructions` key. The root rule is
|
|
47678
|
-
* auto-loaded and never registered.
|
|
48558
|
+
* auto-loaded and never registered. Global scope opt-in via
|
|
48559
|
+
* `mcpInstructionsRegistrarGlobal`.
|
|
47679
48560
|
*/
|
|
47680
48561
|
async buildMcpInstructionFiles({ toolRules, meta }) {
|
|
47681
|
-
if (!meta.mcpInstructionsRegistrar || this.global) return [];
|
|
48562
|
+
if (!meta.mcpInstructionsRegistrar || this.global && !meta.mcpInstructionsRegistrarGlobal) return [];
|
|
47682
48563
|
const instructionPaths = toolRules.filter((rule) => !rule.isRoot() && !rule.isExcludedFromRootReferences()).map((rule) => toPosixPath((0, node_path.join)(rule.getRelativeDirPath(), rule.getRelativeFilePath())));
|
|
47683
|
-
|
|
47684
|
-
return [await meta.mcpInstructionsRegistrar.fromInstructions({
|
|
48564
|
+
const registered = await meta.mcpInstructionsRegistrar.fromInstructions({
|
|
47685
48565
|
outputRoot: this.outputRoot,
|
|
47686
48566
|
instructions: instructionPaths,
|
|
47687
48567
|
validate: true,
|
|
47688
48568
|
global: this.global
|
|
47689
|
-
})
|
|
48569
|
+
});
|
|
48570
|
+
return registered ? [registered] : [];
|
|
47690
48571
|
}
|
|
47691
48572
|
/**
|
|
47692
48573
|
* For tools that don't create a separate conventions rule, prepend the
|
|
@@ -47817,45 +48698,58 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
47817
48698
|
const rootRule = toolRules.find((rule) => rule.isRoot());
|
|
47818
48699
|
if (rootRule) rootRule.setFileContent(rootRule.getFileContent() + "\n\n" + localRootBody);
|
|
47819
48700
|
}
|
|
47820
|
-
buildLocalRootFile({ factory, fileName, body }) {
|
|
48701
|
+
buildLocalRootFile({ factory, fileName, body, relativeDirPath, localRoot = false }) {
|
|
47821
48702
|
if (factory.class === ClaudecodeRule) {
|
|
47822
48703
|
const paths = ClaudecodeRule.getSettablePaths({ global: this.global });
|
|
47823
48704
|
return new ClaudecodeRule({
|
|
47824
48705
|
outputRoot: this.outputRoot,
|
|
47825
|
-
relativeDirPath: paths.root.relativeDirPath,
|
|
48706
|
+
relativeDirPath: relativeDirPath ?? paths.root.relativeDirPath,
|
|
47826
48707
|
relativeFilePath: fileName,
|
|
47827
48708
|
frontmatter: {},
|
|
47828
48709
|
body,
|
|
47829
48710
|
validate: true,
|
|
47830
|
-
root: true
|
|
48711
|
+
root: true,
|
|
48712
|
+
localRoot
|
|
47831
48713
|
});
|
|
47832
48714
|
}
|
|
47833
48715
|
if (factory.class === ClaudecodeLegacyRule) {
|
|
47834
48716
|
const paths = ClaudecodeLegacyRule.getSettablePaths({ global: this.global });
|
|
47835
48717
|
return new ClaudecodeLegacyRule({
|
|
47836
48718
|
outputRoot: this.outputRoot,
|
|
47837
|
-
relativeDirPath: paths.root.relativeDirPath,
|
|
48719
|
+
relativeDirPath: relativeDirPath ?? paths.root.relativeDirPath,
|
|
47838
48720
|
relativeFilePath: fileName,
|
|
47839
48721
|
fileContent: body,
|
|
47840
48722
|
validate: true,
|
|
47841
|
-
root: true
|
|
48723
|
+
root: true,
|
|
48724
|
+
localRoot
|
|
47842
48725
|
});
|
|
47843
48726
|
}
|
|
47844
48727
|
if (factory.class === RovodevRule) return new RovodevRule({
|
|
47845
48728
|
outputRoot: this.outputRoot,
|
|
47846
|
-
relativeDirPath: ".",
|
|
48729
|
+
relativeDirPath: relativeDirPath ?? ".",
|
|
47847
48730
|
relativeFilePath: fileName,
|
|
47848
48731
|
fileContent: body,
|
|
47849
48732
|
validate: true,
|
|
47850
|
-
root: true
|
|
48733
|
+
root: true,
|
|
48734
|
+
localRoot
|
|
47851
48735
|
});
|
|
47852
48736
|
if (factory.class === RooRule) return new RooRule({
|
|
47853
48737
|
outputRoot: this.outputRoot,
|
|
47854
|
-
relativeDirPath: ".",
|
|
48738
|
+
relativeDirPath: relativeDirPath ?? ".",
|
|
47855
48739
|
relativeFilePath: fileName,
|
|
47856
48740
|
fileContent: body,
|
|
47857
48741
|
validate: true,
|
|
47858
|
-
root: true
|
|
48742
|
+
root: true,
|
|
48743
|
+
localRoot
|
|
48744
|
+
});
|
|
48745
|
+
if (factory.class === QwencodeRule) return new QwencodeRule({
|
|
48746
|
+
outputRoot: this.outputRoot,
|
|
48747
|
+
relativeDirPath: relativeDirPath ?? ".qwen",
|
|
48748
|
+
relativeFilePath: fileName,
|
|
48749
|
+
fileContent: body,
|
|
48750
|
+
validate: true,
|
|
48751
|
+
root: true,
|
|
48752
|
+
localRoot
|
|
47859
48753
|
});
|
|
47860
48754
|
return null;
|
|
47861
48755
|
}
|
|
@@ -47893,6 +48787,7 @@ As this project's AI coding tool, you must follow the additional conventions bel
|
|
|
47893
48787
|
async convertToolFilesToRulesyncFiles(toolFiles) {
|
|
47894
48788
|
const toolRules = toolFiles.filter((file) => file instanceof ToolRule);
|
|
47895
48789
|
const rulesyncRules = toolRules.map((toolRule) => {
|
|
48790
|
+
if (toolRule.isLocalRoot()) return toolRule.toLocalRootRulesyncRule({ targets: [this.toolTarget] });
|
|
47896
48791
|
return toolRule.toRulesyncRule();
|
|
47897
48792
|
});
|
|
47898
48793
|
const claimedBy = /* @__PURE__ */ new Map();
|
|
@@ -48026,20 +48921,34 @@ As this project's AI coding tool, you must follow the additional conventions bel
|
|
|
48026
48921
|
})();
|
|
48027
48922
|
this.logger.debug(`Found ${rootToolRules.length} root tool rule files`);
|
|
48028
48923
|
const localRootToolRules = await (async () => {
|
|
48029
|
-
if (
|
|
48924
|
+
if (this.global || factory.meta.localRootMode !== "separate-local-file" || !factory.meta.localRootFileName) return [];
|
|
48030
48925
|
const fileName = factory.meta.localRootFileName;
|
|
48031
|
-
|
|
48032
|
-
|
|
48926
|
+
const filePaths = await (async () => {
|
|
48927
|
+
if (factory.class.getLocalRootFileGlob) return await findFilesByGlobs(factory.class.getLocalRootFileGlob({
|
|
48033
48928
|
outputRoot: this.outputRoot,
|
|
48034
48929
|
fileName
|
|
48035
48930
|
}));
|
|
48036
|
-
|
|
48037
|
-
|
|
48038
|
-
|
|
48039
|
-
|
|
48040
|
-
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);
|
|
48041
48950
|
})();
|
|
48042
|
-
this.logger.debug(`Found ${localRootToolRules.length} local root tool rule files
|
|
48951
|
+
this.logger.debug(`Found ${localRootToolRules.length} local root tool rule files`);
|
|
48043
48952
|
const rootMirrorDeletionRules = await (async () => {
|
|
48044
48953
|
const rootMirror = factory.class.getRootMirror?.();
|
|
48045
48954
|
if (!forDeletion || this.global || !rootMirror) return [];
|
|
@@ -48165,13 +49074,14 @@ As this project's AI coding tool, you must follow the additional conventions bel
|
|
|
48165
49074
|
const lines = [];
|
|
48166
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.");
|
|
48167
49076
|
lines.push("");
|
|
48168
|
-
const
|
|
49077
|
+
const rules = toolRulesWithoutRoot.map((toolRule) => {
|
|
48169
49078
|
const frontmatter = toolRule.toRulesyncRule().getFrontmatter();
|
|
48170
49079
|
const rule = { path: `@${toolRule.getRelativePathFromCwd()}` };
|
|
48171
49080
|
if (frontmatter.description) rule.description = frontmatter.description;
|
|
48172
49081
|
if (frontmatter.globs && frontmatter.globs.length > 0) rule.applyTo = frontmatter.globs;
|
|
48173
49082
|
return rule;
|
|
48174
|
-
})
|
|
49083
|
+
});
|
|
49084
|
+
const toonContent = (0, _toon_format_toon.encode)({ rules });
|
|
48175
49085
|
lines.push(toonContent);
|
|
48176
49086
|
return lines.join("\n") + "\n\n";
|
|
48177
49087
|
}
|
|
@@ -49333,8 +50243,8 @@ async function generateIgnoreCore(params) {
|
|
|
49333
50243
|
allPaths.push(...result.paths);
|
|
49334
50244
|
if (result.hasDiff) hasDiff = true;
|
|
49335
50245
|
} catch (error) {
|
|
49336
|
-
logger.
|
|
49337
|
-
|
|
50246
|
+
logger.error(`Failed to generate ${toolTarget} ignore files for ${outputRoot}: ${formatError(error)}`);
|
|
50247
|
+
throw error;
|
|
49338
50248
|
}
|
|
49339
50249
|
}
|
|
49340
50250
|
return {
|
|
@@ -49606,8 +50516,8 @@ async function generatePermissionsCore(params) {
|
|
|
49606
50516
|
allPaths.push(...result.paths);
|
|
49607
50517
|
if (result.hasDiff) hasDiff = true;
|
|
49608
50518
|
} catch (error) {
|
|
49609
|
-
logger.
|
|
49610
|
-
|
|
50519
|
+
logger.error(`Failed to generate ${toolTarget} permissions files for ${outputRoot}: ${formatError(error)}`);
|
|
50520
|
+
throw error;
|
|
49611
50521
|
}
|
|
49612
50522
|
}
|
|
49613
50523
|
return {
|
|
@@ -50171,6 +51081,18 @@ Object.defineProperty(exports, "PACKAGING_TOOL_TARGETS", {
|
|
|
50171
51081
|
return PACKAGING_TOOL_TARGETS;
|
|
50172
51082
|
}
|
|
50173
51083
|
});
|
|
51084
|
+
Object.defineProperty(exports, "QWENCODE_DIR", {
|
|
51085
|
+
enumerable: true,
|
|
51086
|
+
get: function() {
|
|
51087
|
+
return QWENCODE_DIR;
|
|
51088
|
+
}
|
|
51089
|
+
});
|
|
51090
|
+
Object.defineProperty(exports, "QWENCODE_LOCAL_RULE_FILE_NAME", {
|
|
51091
|
+
enumerable: true,
|
|
51092
|
+
get: function() {
|
|
51093
|
+
return QWENCODE_LOCAL_RULE_FILE_NAME;
|
|
51094
|
+
}
|
|
51095
|
+
});
|
|
50174
51096
|
Object.defineProperty(exports, "RULESYNC_AIIGNORE_FILE_NAME", {
|
|
50175
51097
|
enumerable: true,
|
|
50176
51098
|
get: function() {
|