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
|
@@ -141,7 +141,7 @@ const RULESYNC_PERMISSIONS_LEGACY_FILE_NAME = "permissions.json";
|
|
|
141
141
|
const RULESYNC_CONFIG_SCHEMA_URL = "https://github.com/dyoshikawa/rulesync/releases/latest/download/config-schema.json";
|
|
142
142
|
const RULESYNC_MCP_SCHEMA_URL = "https://github.com/dyoshikawa/rulesync/releases/latest/download/mcp-schema.json";
|
|
143
143
|
const RULESYNC_PERMISSIONS_SCHEMA_URL = "https://github.com/dyoshikawa/rulesync/releases/latest/download/permissions-schema.json";
|
|
144
|
-
const MAX_FILE_SIZE =
|
|
144
|
+
const MAX_FILE_SIZE = 10485760;
|
|
145
145
|
//#endregion
|
|
146
146
|
//#region src/types/tool-target-tuples.ts
|
|
147
147
|
const rulesProcessorToolTargetTuple = [
|
|
@@ -182,7 +182,8 @@ const rulesProcessorToolTargetTuple = [
|
|
|
182
182
|
"vibe",
|
|
183
183
|
"warp",
|
|
184
184
|
"devin",
|
|
185
|
-
"zed"
|
|
185
|
+
"zed",
|
|
186
|
+
"zoocode"
|
|
186
187
|
];
|
|
187
188
|
const ignoreProcessorToolTargetTuple = [
|
|
188
189
|
"aiassistant",
|
|
@@ -204,7 +205,8 @@ const ignoreProcessorToolTargetTuple = [
|
|
|
204
205
|
"devin",
|
|
205
206
|
"vibe",
|
|
206
207
|
"warp",
|
|
207
|
-
"zed"
|
|
208
|
+
"zed",
|
|
209
|
+
"zoocode"
|
|
208
210
|
];
|
|
209
211
|
const mcpProcessorToolTargetTuple = [
|
|
210
212
|
"aiassistant",
|
|
@@ -241,7 +243,8 @@ const mcpProcessorToolTargetTuple = [
|
|
|
241
243
|
"vibe",
|
|
242
244
|
"warp",
|
|
243
245
|
"devin",
|
|
244
|
-
"zed"
|
|
246
|
+
"zed",
|
|
247
|
+
"zoocode"
|
|
245
248
|
];
|
|
246
249
|
const commandsProcessorToolTargetTuple = [
|
|
247
250
|
"agentsmd",
|
|
@@ -269,6 +272,7 @@ const commandsProcessorToolTargetTuple = [
|
|
|
269
272
|
"qwencode",
|
|
270
273
|
"reasonix",
|
|
271
274
|
"roo",
|
|
275
|
+
"zoocode",
|
|
272
276
|
"rovodev",
|
|
273
277
|
"takt",
|
|
274
278
|
"devin",
|
|
@@ -304,6 +308,7 @@ const subagentsProcessorToolTargetTuple = [
|
|
|
304
308
|
"qwencode",
|
|
305
309
|
"reasonix",
|
|
306
310
|
"roo",
|
|
311
|
+
"zoocode",
|
|
307
312
|
"rovodev",
|
|
308
313
|
"takt",
|
|
309
314
|
"vibe"
|
|
@@ -347,7 +352,8 @@ const skillsProcessorToolTargetTuple = [
|
|
|
347
352
|
"vibe",
|
|
348
353
|
"warp",
|
|
349
354
|
"devin",
|
|
350
|
-
"zed"
|
|
355
|
+
"zed",
|
|
356
|
+
"zoocode"
|
|
351
357
|
];
|
|
352
358
|
const hooksProcessorToolTargetTuple = [
|
|
353
359
|
"amp",
|
|
@@ -660,7 +666,8 @@ async function findFilesByGlobs(globs, options = {}) {
|
|
|
660
666
|
onlyFiles: false,
|
|
661
667
|
onlyDirectories: false
|
|
662
668
|
};
|
|
663
|
-
const
|
|
669
|
+
const normalizedGlobs = Array.isArray(globs) ? globs.map((g) => g.replaceAll("\\", "/")) : globs.replaceAll("\\", "/");
|
|
670
|
+
const results = globbySync(normalizedGlobs, {
|
|
664
671
|
absolute: true,
|
|
665
672
|
followSymbolicLinks,
|
|
666
673
|
...ignore ? { ignore: ignore.map((pattern) => pattern.replaceAll("\\", "/")) } : {},
|
|
@@ -771,7 +778,8 @@ function validateOutputRoot(outputRoot) {
|
|
|
771
778
|
function toKebabCaseFilename(filename) {
|
|
772
779
|
const lastDotIndex = filename.lastIndexOf(".");
|
|
773
780
|
const extension = lastDotIndex > 0 ? filename.slice(lastDotIndex) : "";
|
|
774
|
-
|
|
781
|
+
const nameWithoutExt = lastDotIndex > 0 ? filename.slice(0, lastDotIndex) : filename;
|
|
782
|
+
return kebabCase(nameWithoutExt) + extension;
|
|
775
783
|
}
|
|
776
784
|
/**
|
|
777
785
|
* Create a temporary directory atomically and return its path.
|
|
@@ -1433,7 +1441,8 @@ const getDefaults = () => ({
|
|
|
1433
1441
|
});
|
|
1434
1442
|
const loadConfigFromFile = async (filePath) => {
|
|
1435
1443
|
if (!await fileExists(filePath)) return {};
|
|
1436
|
-
const
|
|
1444
|
+
const fileContent = await readFileContent(filePath);
|
|
1445
|
+
const jsonData = parse(fileContent);
|
|
1437
1446
|
const { $schema: _schema, ...configParams } = ConfigFileSchema.parse(jsonData);
|
|
1438
1447
|
assertTargetsFeaturesExclusive({
|
|
1439
1448
|
targets: configParams.targets,
|
|
@@ -1523,7 +1532,8 @@ var ConfigResolver = class {
|
|
|
1523
1532
|
if (inputRoot !== void 0) validateOutputRoot(inputRoot);
|
|
1524
1533
|
const validatedConfigPath = resolvePath(configPath, resolve(inputRoot ?? cwd));
|
|
1525
1534
|
const baseConfig = await loadConfigFromFile(validatedConfigPath);
|
|
1526
|
-
const
|
|
1535
|
+
const configDir = dirname(validatedConfigPath);
|
|
1536
|
+
const localConfigPath = join(configDir, RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH);
|
|
1527
1537
|
const localConfig = await loadConfigFromFile(localConfigPath);
|
|
1528
1538
|
const configByFile = mergeConfigs(baseConfig, localConfig);
|
|
1529
1539
|
if (inputRoot === void 0 && configByFile.inputRoot !== void 0) validateOutputRoot(configByFile.inputRoot);
|
|
@@ -1688,6 +1698,8 @@ const SHARED_USER_MANAGED_CONFIG_PATHS = [
|
|
|
1688
1698
|
".devin/config.json",
|
|
1689
1699
|
".factory/settings.json",
|
|
1690
1700
|
".grok/config.toml",
|
|
1701
|
+
".rovodev/config.yml",
|
|
1702
|
+
".rovodev/mcp.json",
|
|
1691
1703
|
".vibe/config.toml",
|
|
1692
1704
|
"reasonix.toml",
|
|
1693
1705
|
".vscode/settings.json",
|
|
@@ -1757,7 +1769,8 @@ var AiFile = class {
|
|
|
1757
1769
|
getFilePath() {
|
|
1758
1770
|
const fullPath = path.join(this.outputRoot, this.relativeDirPath, this.relativeFilePath);
|
|
1759
1771
|
const resolvedFull = resolve(fullPath);
|
|
1760
|
-
const
|
|
1772
|
+
const resolvedBase = resolve(this.outputRoot);
|
|
1773
|
+
const rel = relative(resolvedBase, resolvedFull);
|
|
1761
1774
|
if (rel.startsWith("..") || path.isAbsolute(rel)) throw new Error(`Path traversal detected: Final path escapes outputRoot. outputRoot="${this.outputRoot}", relativeDirPath="${this.relativeDirPath}", relativeFilePath="${this.relativeFilePath}"`);
|
|
1762
1775
|
return fullPath;
|
|
1763
1776
|
}
|
|
@@ -2325,6 +2338,7 @@ const OPENCODE_HOOK_EVENTS = [
|
|
|
2325
2338
|
"postToolUse",
|
|
2326
2339
|
"stop",
|
|
2327
2340
|
"afterFileEdit",
|
|
2341
|
+
"beforeShellExecution",
|
|
2328
2342
|
"afterShellExecution",
|
|
2329
2343
|
"permissionRequest",
|
|
2330
2344
|
"preCompact",
|
|
@@ -2356,6 +2370,7 @@ const PI_HOOK_EVENTS = [
|
|
|
2356
2370
|
"preToolUse",
|
|
2357
2371
|
"postToolUse",
|
|
2358
2372
|
"preModelInvocation",
|
|
2373
|
+
"postModelInvocation",
|
|
2359
2374
|
"beforeSubmitPrompt",
|
|
2360
2375
|
"stop",
|
|
2361
2376
|
"preCompact",
|
|
@@ -3029,7 +3044,6 @@ const CANONICAL_TO_OPENCODE_EVENT_NAMES = {
|
|
|
3029
3044
|
postToolUse: "tool.execute.after",
|
|
3030
3045
|
stop: "session.idle",
|
|
3031
3046
|
afterFileEdit: "file.edited",
|
|
3032
|
-
afterShellExecution: "command.executed",
|
|
3033
3047
|
permissionRequest: "permission.asked",
|
|
3034
3048
|
preCompact: "experimental.session.compacting",
|
|
3035
3049
|
postCompact: "session.compacted",
|
|
@@ -3062,6 +3076,7 @@ const CANONICAL_TO_PI_EVENT_NAMES = {
|
|
|
3062
3076
|
preToolUse: "tool_call",
|
|
3063
3077
|
postToolUse: "tool_result",
|
|
3064
3078
|
preModelInvocation: "context",
|
|
3079
|
+
postModelInvocation: "message_end",
|
|
3065
3080
|
beforeSubmitPrompt: "input",
|
|
3066
3081
|
stop: "agent_end",
|
|
3067
3082
|
preCompact: "session_before_compact",
|
|
@@ -4095,10 +4110,22 @@ const ClaudecodePermissionsOverrideSchema = z.looseObject({
|
|
|
4095
4110
|
* category; the shared `permission` block still drives the base permission and
|
|
4096
4111
|
* allow/deny lists. Keyed by canonical category (e.g. `bash`, `edit`).
|
|
4097
4112
|
*
|
|
4113
|
+
* Vibe's top-level `enabled_tools` is an **exclusive** allowlist — when set,
|
|
4114
|
+
* only the listed tools (name or pattern) are active and every other builtin
|
|
4115
|
+
* and MCP tool is off. That narrowing cannot be derived from canonical `allow`
|
|
4116
|
+
* rules (an allow grants one tool without revoking the rest), so the list is
|
|
4117
|
+
* only ever authored explicitly here, verbatim in Vibe's tool-name vocabulary,
|
|
4118
|
+
* and round-trips through this override on import.
|
|
4119
|
+
*
|
|
4098
4120
|
* @example
|
|
4099
4121
|
* { "permission": { "bash": { "sensitive_patterns": ["rm *", "sudo *"] } } }
|
|
4122
|
+
* @example
|
|
4123
|
+
* { "enabled_tools": ["bash", "read_file", "grep"] }
|
|
4100
4124
|
*/
|
|
4101
|
-
const VibePermissionsOverrideSchema = z.looseObject({
|
|
4125
|
+
const VibePermissionsOverrideSchema = z.looseObject({
|
|
4126
|
+
permission: z.optional(z.record(z.string(), z.looseObject({ sensitive_patterns: z.optional(z.array(z.string())) }))),
|
|
4127
|
+
enabled_tools: z.optional(z.array(z.string()))
|
|
4128
|
+
});
|
|
4102
4129
|
/**
|
|
4103
4130
|
* Tool-scoped override block for Cursor CLI. Cursor's `cli.json` carries scalar
|
|
4104
4131
|
* autonomy settings with no canonical permission category — `approvalMode`
|
|
@@ -4170,7 +4197,10 @@ const QwencodePermissionsOverrideSchema = z.looseObject({
|
|
|
4170
4197
|
const ReasonixPermissionsOverrideSchema = z.looseObject({
|
|
4171
4198
|
permission: z.optional(ToolScopedPermissionSchema),
|
|
4172
4199
|
sandbox: z.optional(z.looseObject({})),
|
|
4173
|
-
agent: z.optional(z.looseObject({}))
|
|
4200
|
+
agent: z.optional(z.looseObject({})),
|
|
4201
|
+
rawAllow: z.optional(z.array(z.string())),
|
|
4202
|
+
rawAsk: z.optional(z.array(z.string())),
|
|
4203
|
+
rawDeny: z.optional(z.array(z.string()))
|
|
4174
4204
|
});
|
|
4175
4205
|
/**
|
|
4176
4206
|
* Tool-scoped override block for Factory Droid. Factory Droid's `settings.json`
|
|
@@ -4201,12 +4231,69 @@ const FactorydroidPermissionsOverrideSchema = z.looseObject({
|
|
|
4201
4231
|
* `agent_mode_execute_readonly_commands` (a read-only auto-execution boolean).
|
|
4202
4232
|
* Fields placed here are merged into `[agents.profiles]` of Warp's global
|
|
4203
4233
|
* `settings.toml`, while the shared `permission` block continues to drive the
|
|
4204
|
-
*
|
|
4205
|
-
*
|
|
4234
|
+
* command regex arrays (legacy `agent_mode_command_execution_allowlist`/
|
|
4235
|
+
* `_denylist` plus the `default` execution profile's `command_allowlist`/
|
|
4236
|
+
* `command_denylist`). On migrated installs the `[agents.profiles]` keys are
|
|
4237
|
+
* inert; their execution-profile counterparts are authored via the nested
|
|
4238
|
+
* `execution_profile` block instead, which is merged into the `default` record
|
|
4239
|
+
* of `[agents.execution_profiles.<id>]`. Warp permissions are global-only.
|
|
4206
4240
|
*
|
|
4207
4241
|
* @example
|
|
4208
|
-
* {
|
|
4242
|
+
* {
|
|
4243
|
+
* "agent_mode_coding_permissions": "always_allow_reading",
|
|
4244
|
+
* "agent_mode_execute_readonly_commands": true,
|
|
4245
|
+
* "execution_profile": { "read_files": "always_allow", "mcp_denylist": ["untrusted-server"] }
|
|
4246
|
+
* }
|
|
4247
|
+
*/
|
|
4248
|
+
/**
|
|
4249
|
+
* Per-action autonomy value of Warp's execution profiles
|
|
4250
|
+
* (`FileActionPermission` in the Warp repository's
|
|
4251
|
+
* `app/src/ai/execution_profiles/config.rs`, serialized snake_case).
|
|
4209
4252
|
*/
|
|
4253
|
+
const WarpFileActionPermissionSchema = z.enum([
|
|
4254
|
+
"agent_decides",
|
|
4255
|
+
"always_allow",
|
|
4256
|
+
"always_ask"
|
|
4257
|
+
]);
|
|
4258
|
+
/**
|
|
4259
|
+
* Permission keys of the `default` record in Warp's
|
|
4260
|
+
* `[agents.execution_profiles.<id>]` collection (the surface runtime
|
|
4261
|
+
* enforcement reads on migrated installs). Loose so forward-compat keys pass
|
|
4262
|
+
* through verbatim. The rulesync-owned `command_allowlist`/`command_denylist`
|
|
4263
|
+
* are driven by the shared `permission.bash` block and always win over values
|
|
4264
|
+
* placed here.
|
|
4265
|
+
*
|
|
4266
|
+
* @see https://github.com/warpdotdev/warp/blob/main/app/src/ai/execution_profiles/config.rs (`ExecutionProfileFile`)
|
|
4267
|
+
*/
|
|
4268
|
+
const WarpExecutionProfileOverrideSchema = z.looseObject({
|
|
4269
|
+
read_files: z.optional(WarpFileActionPermissionSchema),
|
|
4270
|
+
apply_code_diffs: z.optional(WarpFileActionPermissionSchema),
|
|
4271
|
+
execute_commands: z.optional(WarpFileActionPermissionSchema),
|
|
4272
|
+
mcp_permissions: z.optional(WarpFileActionPermissionSchema),
|
|
4273
|
+
write_to_pty: z.optional(z.enum([
|
|
4274
|
+
"always_allow",
|
|
4275
|
+
"always_ask",
|
|
4276
|
+
"ask_on_first_write"
|
|
4277
|
+
])),
|
|
4278
|
+
ask_user_question: z.optional(z.enum([
|
|
4279
|
+
"never",
|
|
4280
|
+
"ask_except_in_auto_approve",
|
|
4281
|
+
"always_ask"
|
|
4282
|
+
])),
|
|
4283
|
+
run_agents: z.optional(z.enum([
|
|
4284
|
+
"never_allow",
|
|
4285
|
+
"always_allow",
|
|
4286
|
+
"always_ask"
|
|
4287
|
+
])),
|
|
4288
|
+
computer_use: z.optional(z.enum([
|
|
4289
|
+
"never",
|
|
4290
|
+
"always_ask",
|
|
4291
|
+
"always_allow"
|
|
4292
|
+
])),
|
|
4293
|
+
directory_allowlist: z.optional(z.array(z.string())),
|
|
4294
|
+
mcp_allowlist: z.optional(z.array(z.string())),
|
|
4295
|
+
mcp_denylist: z.optional(z.array(z.string()))
|
|
4296
|
+
});
|
|
4210
4297
|
const WarpPermissionsOverrideSchema = z.looseObject({
|
|
4211
4298
|
permission: z.optional(ToolScopedPermissionSchema),
|
|
4212
4299
|
agent_mode_coding_permissions: z.optional(z.enum([
|
|
@@ -4215,7 +4302,8 @@ const WarpPermissionsOverrideSchema = z.looseObject({
|
|
|
4215
4302
|
"allow_reading_specific_files"
|
|
4216
4303
|
])),
|
|
4217
4304
|
agent_mode_coding_file_read_allowlist: z.optional(z.array(z.string())),
|
|
4218
|
-
agent_mode_execute_readonly_commands: z.optional(z.boolean())
|
|
4305
|
+
agent_mode_execute_readonly_commands: z.optional(z.boolean()),
|
|
4306
|
+
execution_profile: z.optional(WarpExecutionProfileOverrideSchema)
|
|
4219
4307
|
});
|
|
4220
4308
|
/**
|
|
4221
4309
|
* The actions Junie's allowlist accepts. Verified against the shipped Junie
|
|
@@ -4947,7 +5035,8 @@ var AiDir = class {
|
|
|
4947
5035
|
getDirPath() {
|
|
4948
5036
|
const fullPath = path.join(this.outputRoot, this.relativeDirPath, this.dirName);
|
|
4949
5037
|
const resolvedFull = resolve(fullPath);
|
|
4950
|
-
const
|
|
5038
|
+
const resolvedBase = resolve(this.outputRoot);
|
|
5039
|
+
const rel = relative(resolvedBase, resolvedFull);
|
|
4951
5040
|
if (rel.startsWith("..") || path.isAbsolute(rel)) throw new Error(`Path traversal detected: Final path escapes outputRoot. outputRoot="${this.outputRoot}", relativeDirPath="${this.relativeDirPath}", dirName="${this.dirName}"`);
|
|
4952
5041
|
return fullPath;
|
|
4953
5042
|
}
|
|
@@ -5230,6 +5319,9 @@ const RulesyncSubagentFrontmatterSchema = z.looseObject({
|
|
|
5230
5319
|
customInstructions: z.optional(z.string()),
|
|
5231
5320
|
groups: z.optional(z.array(z.unknown()))
|
|
5232
5321
|
})),
|
|
5322
|
+
zoocode: z.optional(z.looseObject({
|
|
5323
|
+
/** Per-mode MCP server allowlist (Zoo Code v3.60.0+); omitted = all. */
|
|
5324
|
+
allowedMcpServers: z.optional(z.array(z.string())) })),
|
|
5233
5325
|
vibe: z.optional(z.looseObject({
|
|
5234
5326
|
agent_type: z.optional(z.enum(["agent", "subagent"])),
|
|
5235
5327
|
display_name: z.optional(z.string()),
|
|
@@ -5369,7 +5461,6 @@ const ROVODEV_SKILLS_DIR_PATH = join(ROVODEV_DIR, "skills");
|
|
|
5369
5461
|
const ROVODEV_SUBAGENTS_DIR_PATH = join(ROVODEV_DIR, "subagents");
|
|
5370
5462
|
const ROVODEV_MODULAR_RULES_DIR_PATH = join(ROVODEV_DIR, ".rulesync", "modular-rules");
|
|
5371
5463
|
const ROVODEV_RULE_FILE_NAME = "AGENTS.md";
|
|
5372
|
-
const ROVODEV_LEGACY_RULE_FILE_NAME = "AGENTS.local.md";
|
|
5373
5464
|
const ROVODEV_MCP_FILE_NAME = "mcp.json";
|
|
5374
5465
|
const ROVODEV_CONFIG_FILE_NAME = "config.yml";
|
|
5375
5466
|
const ROVODEV_AGENTS_SKILLS_DIR_PATH = join(".agents", "skills");
|
|
@@ -6588,6 +6679,7 @@ const KIMI_CODE_CONFIG_SHARED_FILE_KEY = ".kimi-code/config.toml";
|
|
|
6588
6679
|
const KIMI_CODE_HOME_CONFIG_SHARED_FILE_KEY = "config.toml";
|
|
6589
6680
|
const REASONIX_PROJECT_CONFIG_SHARED_FILE_KEY = "reasonix.toml";
|
|
6590
6681
|
const REASONIX_GLOBAL_CONFIG_SHARED_FILE_KEY = ".reasonix/config.toml";
|
|
6682
|
+
const ROVODEV_CONFIG_SHARED_FILE_KEY = ".rovodev/config.yml";
|
|
6591
6683
|
/**
|
|
6592
6684
|
* Build the `SHARED_CONFIG_OWNERSHIP` lookup key from a tool's settable paths.
|
|
6593
6685
|
* Mirrors `sharedFileKey` in `src/lib/shared-file-derive.ts` (kept separate so
|
|
@@ -6827,24 +6919,14 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
6827
6919
|
},
|
|
6828
6920
|
".devin/config.json": {
|
|
6829
6921
|
format: "json",
|
|
6830
|
-
features: {
|
|
6831
|
-
|
|
6832
|
-
|
|
6833
|
-
|
|
6834
|
-
},
|
|
6835
|
-
permissions: {
|
|
6836
|
-
kind: "replace-owned-keys",
|
|
6837
|
-
ownedKeys: ["permissions"]
|
|
6838
|
-
}
|
|
6839
|
-
}
|
|
6922
|
+
features: { permissions: {
|
|
6923
|
+
kind: "replace-owned-keys",
|
|
6924
|
+
ownedKeys: ["permissions"]
|
|
6925
|
+
} }
|
|
6840
6926
|
},
|
|
6841
6927
|
".config/devin/config.json": {
|
|
6842
6928
|
format: "json",
|
|
6843
6929
|
features: {
|
|
6844
|
-
mcp: {
|
|
6845
|
-
kind: "replace-owned-keys",
|
|
6846
|
-
ownedKeys: ["mcpServers"]
|
|
6847
|
-
},
|
|
6848
6930
|
hooks: {
|
|
6849
6931
|
kind: "replace-owned-keys",
|
|
6850
6932
|
ownedKeys: ["hooks"]
|
|
@@ -6937,6 +7019,10 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
6937
7019
|
permissions: {
|
|
6938
7020
|
kind: "replace-owned-keys",
|
|
6939
7021
|
ownedKeys: ["permission"]
|
|
7022
|
+
},
|
|
7023
|
+
rules: {
|
|
7024
|
+
kind: "replace-owned-keys",
|
|
7025
|
+
ownedKeys: ["instructions"]
|
|
6940
7026
|
}
|
|
6941
7027
|
}
|
|
6942
7028
|
},
|
|
@@ -7054,6 +7140,19 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
7054
7140
|
]
|
|
7055
7141
|
}
|
|
7056
7142
|
}
|
|
7143
|
+
},
|
|
7144
|
+
[ROVODEV_CONFIG_SHARED_FILE_KEY]: {
|
|
7145
|
+
format: "yaml",
|
|
7146
|
+
features: {
|
|
7147
|
+
mcp: {
|
|
7148
|
+
kind: "replace-owned-keys",
|
|
7149
|
+
ownedKeys: ["mcp"]
|
|
7150
|
+
},
|
|
7151
|
+
permissions: {
|
|
7152
|
+
kind: "replace-owned-keys",
|
|
7153
|
+
ownedKeys: ["toolPermissions"]
|
|
7154
|
+
}
|
|
7155
|
+
}
|
|
7057
7156
|
}
|
|
7058
7157
|
};
|
|
7059
7158
|
/**
|
|
@@ -7475,7 +7574,8 @@ const toolCheckFactories = /* @__PURE__ */ new Map([
|
|
|
7475
7574
|
class: CursorCheck,
|
|
7476
7575
|
meta: {
|
|
7477
7576
|
supportsGlobal: false,
|
|
7478
|
-
filePattern: CURSOR_BUGBOT_FILE_NAME
|
|
7577
|
+
filePattern: CURSOR_BUGBOT_FILE_NAME,
|
|
7578
|
+
committedOutput: true
|
|
7479
7579
|
}
|
|
7480
7580
|
}],
|
|
7481
7581
|
["hermesagent", {
|
|
@@ -7489,7 +7589,8 @@ const toolCheckFactories = /* @__PURE__ */ new Map([
|
|
|
7489
7589
|
class: RovodevCheck,
|
|
7490
7590
|
meta: {
|
|
7491
7591
|
supportsGlobal: false,
|
|
7492
|
-
filePattern: ROVODEV_REVIEW_AGENT_FILE_NAME
|
|
7592
|
+
filePattern: ROVODEV_REVIEW_AGENT_FILE_NAME,
|
|
7593
|
+
committedOutput: true
|
|
7493
7594
|
}
|
|
7494
7595
|
}],
|
|
7495
7596
|
["takt", {
|
|
@@ -9044,6 +9145,7 @@ const DEVIN_GLOBAL_CONFIG_DIR_PATH = join(".config", "devin");
|
|
|
9044
9145
|
const DEVIN_GLOBAL_AGENTS_DIR_PATH = join(DEVIN_GLOBAL_CONFIG_DIR_PATH, "agents");
|
|
9045
9146
|
const DEVIN_GLOBAL_SKILLS_DIR_PATH = join(DEVIN_GLOBAL_CONFIG_DIR_PATH, "skills");
|
|
9046
9147
|
const DEVIN_CONFIG_FILE_NAME = "config.json";
|
|
9148
|
+
const DEVIN_MCP_CONFIG_FILE_NAME = "mcp_config.json";
|
|
9047
9149
|
const DEVIN_HOOKS_V1_FILE_NAME = "hooks.v1.json";
|
|
9048
9150
|
const DEVIN_GLOBAL_AGENTS_FILE_NAME = "AGENTS.md";
|
|
9049
9151
|
const DEVIN_IGNORE_FILE_NAME = ".devinignore";
|
|
@@ -9650,7 +9752,8 @@ var GrokcliCommand = class GrokcliCommand extends ToolCommand {
|
|
|
9650
9752
|
static async validateRulesyncCommands({ inputRoot, rulesyncCommands, logger }) {
|
|
9651
9753
|
const commandNames = new Set(rulesyncCommands.filter((command) => this.isTargetedByRulesyncCommand(command)).map((command) => basename(command.getRelativeFilePath(), ".md")));
|
|
9652
9754
|
if (commandNames.size === 0) return;
|
|
9653
|
-
const
|
|
9755
|
+
const skillsRoot = join(inputRoot, RULESYNC_SKILLS_RELATIVE_DIR_PATH);
|
|
9756
|
+
const shadowed = (await findFilesByGlobs(join(skillsRoot, "**", "SKILL.md"))).map((filePath) => basename(dirname(filePath))).filter((skillName) => commandNames.has(skillName));
|
|
9654
9757
|
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.`);
|
|
9655
9758
|
}
|
|
9656
9759
|
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
|
|
@@ -9685,6 +9788,20 @@ var GrokcliCommand = class GrokcliCommand extends ToolCommand {
|
|
|
9685
9788
|
function toolSkillSearchRoots(paths) {
|
|
9686
9789
|
return [paths.relativeDirPath, ...paths.alternativeSkillRoots ?? []];
|
|
9687
9790
|
}
|
|
9791
|
+
/**
|
|
9792
|
+
* The Agent Skills interoperability roots: project `.agents/skills/`, global
|
|
9793
|
+
* `~/.agents/skills/` (same relative path, home output root), and Amp's global
|
|
9794
|
+
* equivalent `~/.config/agents/skills/`. Foreign-authored — potentially
|
|
9795
|
+
* non-conformant — skills are the most likely to live here, so every tool that
|
|
9796
|
+
* scans one of these roots imports it leniently (skip-and-warn per skill), per
|
|
9797
|
+
* the Agent Skills client-implementation guide, while keeping fail-fast
|
|
9798
|
+
* behavior for its own native root.
|
|
9799
|
+
* https://agentskills.io/client-implementation/adding-skills-support
|
|
9800
|
+
*/
|
|
9801
|
+
const AGENT_SKILLS_INTEROP_ROOTS = /* @__PURE__ */ new Set([toPosixPath(AGENTSMD_SKILLS_DIR_PATH), toPosixPath(AMP_SKILLS_GLOBAL_DIR)]);
|
|
9802
|
+
function isAgentSkillsInteropRoot(relativeDirPath) {
|
|
9803
|
+
return AGENT_SKILLS_INTEROP_ROOTS.has(toPosixPath(relativeDirPath));
|
|
9804
|
+
}
|
|
9688
9805
|
/** Ordered import roots: managed roots first, followed by read-only discovery roots. */
|
|
9689
9806
|
function toolSkillImportRoots(paths) {
|
|
9690
9807
|
return [...toolSkillSearchRoots(paths), ...paths.importOnlySkillRoots ?? []];
|
|
@@ -11301,6 +11418,7 @@ const QWENCODE_COMMANDS_DIR_PATH = join(QWENCODE_DIR, "commands");
|
|
|
11301
11418
|
const QWENCODE_AGENTS_DIR_PATH = join(QWENCODE_DIR, "agents");
|
|
11302
11419
|
const QWENCODE_SKILLS_DIR_PATH = join(QWENCODE_DIR, "skills");
|
|
11303
11420
|
const QWENCODE_RULE_FILE_NAME = "QWEN.md";
|
|
11421
|
+
const QWENCODE_LOCAL_RULE_FILE_NAME = "QWEN.local.md";
|
|
11304
11422
|
const QWENCODE_IGNORE_FILE_NAME = ".qwenignore";
|
|
11305
11423
|
const QWENCODE_SETTINGS_FILE_NAME = "settings.json";
|
|
11306
11424
|
//#endregion
|
|
@@ -11627,7 +11745,7 @@ var RooCommand = class RooCommand extends ToolCommand {
|
|
|
11627
11745
|
};
|
|
11628
11746
|
const body = rulesyncCommand.getBody();
|
|
11629
11747
|
const fileContent = stringifyFrontmatter(body, rooFrontmatter);
|
|
11630
|
-
return new
|
|
11748
|
+
return new this({
|
|
11631
11749
|
outputRoot,
|
|
11632
11750
|
frontmatter: rooFrontmatter,
|
|
11633
11751
|
body,
|
|
@@ -11664,7 +11782,7 @@ var RooCommand = class RooCommand extends ToolCommand {
|
|
|
11664
11782
|
const { frontmatter, body: content } = parseFrontmatter(fileContent, filePath);
|
|
11665
11783
|
const result = RooCommandFrontmatterSchema.safeParse(frontmatter);
|
|
11666
11784
|
if (!result.success) throw new Error(`Invalid frontmatter in ${filePath}: ${formatError(result.error)}`);
|
|
11667
|
-
return new
|
|
11785
|
+
return new this({
|
|
11668
11786
|
outputRoot,
|
|
11669
11787
|
relativeDirPath: RooCommand.getSettablePaths().relativeDirPath,
|
|
11670
11788
|
relativeFilePath,
|
|
@@ -11675,7 +11793,7 @@ var RooCommand = class RooCommand extends ToolCommand {
|
|
|
11675
11793
|
});
|
|
11676
11794
|
}
|
|
11677
11795
|
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
11678
|
-
return new
|
|
11796
|
+
return new this({
|
|
11679
11797
|
outputRoot,
|
|
11680
11798
|
relativeDirPath,
|
|
11681
11799
|
relativeFilePath,
|
|
@@ -12047,6 +12165,7 @@ const WARP_SKILLS_DIR_PATH = join(WARP_DIR, "skills");
|
|
|
12047
12165
|
const WARP_LINUX_DIR = join(".config", "warp-terminal");
|
|
12048
12166
|
const WARP_WIN32_DIR = join("AppData", "Local", "warp", "Warp", "config");
|
|
12049
12167
|
const WARP_RULE_FILE_NAME = "AGENTS.md";
|
|
12168
|
+
const WARP_GLOBAL_RULE_DIR = ".agents";
|
|
12050
12169
|
const WARP_MCP_FILE_NAME = ".mcp.json";
|
|
12051
12170
|
const WARP_PERMISSIONS_FILE_NAME = "settings.toml";
|
|
12052
12171
|
const WARP_IGNORE_FILE_NAME = ".warpindexingignore";
|
|
@@ -12132,6 +12251,39 @@ var WarpCommand = class WarpCommand extends ToolCommand {
|
|
|
12132
12251
|
}
|
|
12133
12252
|
};
|
|
12134
12253
|
//#endregion
|
|
12254
|
+
//#region src/features/commands/zoocode-command.ts
|
|
12255
|
+
/**
|
|
12256
|
+
* Command generator for **Zoo Code** (the community continuation of Roo Code).
|
|
12257
|
+
* Zoo Code keeps Roo's `.roo/commands/` layout, so this target reuses
|
|
12258
|
+
* {@link RooCommand} verbatim and only narrows the targeting.
|
|
12259
|
+
*
|
|
12260
|
+
* @see https://docs.zoocode.dev
|
|
12261
|
+
*/
|
|
12262
|
+
var ZoocodeCommand = class extends RooCommand {
|
|
12263
|
+
static isTargetedByRulesyncCommand(rulesyncCommand) {
|
|
12264
|
+
return this.isTargetedByRulesyncCommandDefault({
|
|
12265
|
+
rulesyncCommand,
|
|
12266
|
+
toolTarget: "zoocode"
|
|
12267
|
+
});
|
|
12268
|
+
}
|
|
12269
|
+
toRulesyncCommand() {
|
|
12270
|
+
const command = super.toRulesyncCommand();
|
|
12271
|
+
const frontmatter = {
|
|
12272
|
+
...command.getFrontmatter(),
|
|
12273
|
+
targets: ["zoocode"]
|
|
12274
|
+
};
|
|
12275
|
+
return new RulesyncCommand({
|
|
12276
|
+
outputRoot: command.getOutputRoot(),
|
|
12277
|
+
relativeDirPath: command.getRelativeDirPath(),
|
|
12278
|
+
relativeFilePath: command.getRelativeFilePath(),
|
|
12279
|
+
frontmatter,
|
|
12280
|
+
body: command.getBody(),
|
|
12281
|
+
fileContent: stringifyFrontmatter(command.getBody(), frontmatter),
|
|
12282
|
+
validate: true
|
|
12283
|
+
});
|
|
12284
|
+
}
|
|
12285
|
+
};
|
|
12286
|
+
//#endregion
|
|
12135
12287
|
//#region src/features/commands/commands-processor.ts
|
|
12136
12288
|
const CommandsProcessorToolTargetSchema = z.enum(commandsProcessorToolTargetTuple);
|
|
12137
12289
|
/**
|
|
@@ -12391,6 +12543,16 @@ const toolCommandFactories = /* @__PURE__ */ new Map([
|
|
|
12391
12543
|
supportsSubdirectory: true
|
|
12392
12544
|
}
|
|
12393
12545
|
}],
|
|
12546
|
+
["zoocode", {
|
|
12547
|
+
class: ZoocodeCommand,
|
|
12548
|
+
meta: {
|
|
12549
|
+
extension: "md",
|
|
12550
|
+
supportsProject: true,
|
|
12551
|
+
supportsGlobal: true,
|
|
12552
|
+
isSimulated: false,
|
|
12553
|
+
supportsSubdirectory: true
|
|
12554
|
+
}
|
|
12555
|
+
}],
|
|
12394
12556
|
["rovodev", {
|
|
12395
12557
|
class: RovodevCommand,
|
|
12396
12558
|
meta: {
|
|
@@ -13770,16 +13932,17 @@ var ClaudecodeHooks = class extends ToolHooks {
|
|
|
13770
13932
|
const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
13771
13933
|
const existingContent = await readFileContentOrNull(filePath) ?? JSON.stringify({}, null, 2);
|
|
13772
13934
|
const config = rulesyncHooks.getJson();
|
|
13935
|
+
const claudeHooks = canonicalToToolHooks({
|
|
13936
|
+
config,
|
|
13937
|
+
toolOverrideHooks: config.claudecode?.hooks,
|
|
13938
|
+
converterConfig: CLAUDE_CONVERTER_CONFIG,
|
|
13939
|
+
logger
|
|
13940
|
+
});
|
|
13773
13941
|
const fileContent = applySharedConfigPatch({
|
|
13774
13942
|
fileKey: CLAUDE_SETTINGS_SHARED_FILE_KEY,
|
|
13775
13943
|
feature: "hooks",
|
|
13776
13944
|
existingContent,
|
|
13777
|
-
patch: { hooks:
|
|
13778
|
-
config,
|
|
13779
|
-
toolOverrideHooks: config.claudecode?.hooks,
|
|
13780
|
-
converterConfig: CLAUDE_CONVERTER_CONFIG,
|
|
13781
|
-
logger
|
|
13782
|
-
}) },
|
|
13945
|
+
patch: { hooks: claudeHooks },
|
|
13783
13946
|
filePath
|
|
13784
13947
|
});
|
|
13785
13948
|
return new this({
|
|
@@ -14782,9 +14945,9 @@ const DEVIN_CONVERTER_CONFIG = {
|
|
|
14782
14945
|
* - Project scope: `.devin/hooks.v1.json`. This is a standalone file whose top
|
|
14783
14946
|
* level IS the event map (no wrapper key).
|
|
14784
14947
|
* - Global scope: `~/.config/devin/config.json` under the `"hooks"` key. This
|
|
14785
|
-
* file is shared with the
|
|
14786
|
-
*
|
|
14787
|
-
* never deleted in global mode.
|
|
14948
|
+
* file is shared with the permissions (`permissions`) feature (MCP moved to
|
|
14949
|
+
* the dedicated mcp_config.json in v3000.3), so reads and writes merge into
|
|
14950
|
+
* the existing JSON and the file is never deleted in global mode.
|
|
14788
14951
|
*
|
|
14789
14952
|
* @see https://docs.devin.ai/cli/extensibility/hooks/overview
|
|
14790
14953
|
*/
|
|
@@ -15533,6 +15696,30 @@ const NAMED_HOOK_MATCHER_SUBJECTS = {
|
|
|
15533
15696
|
"tool.execute.after": "input.tool",
|
|
15534
15697
|
"experimental.session.compacting": null
|
|
15535
15698
|
};
|
|
15699
|
+
/**
|
|
15700
|
+
* OpenCode (and Kilo) have no shell-execution lifecycle event — the
|
|
15701
|
+
* `command.executed` event these canonical events were once mapped to is a
|
|
15702
|
+
* *slash-command* event, so a hook wired there never fired on bash commands
|
|
15703
|
+
* and fired on every slash command instead. Observing a shell invocation is
|
|
15704
|
+
* done with the named `tool.execute.before/after` hooks gated on the `bash`
|
|
15705
|
+
* tool (the `.env protection` pattern in the OpenCode plugins doc), so these
|
|
15706
|
+
* canonical events are emitted into those named hooks with an implicit
|
|
15707
|
+
* `input.tool === "bash"` gate. Matchers on them are dropped upstream with a
|
|
15708
|
+
* warning (`matcherEvents` covers only `preToolUse`/`postToolUse`) — the
|
|
15709
|
+
* named hooks expose no command text to match against.
|
|
15710
|
+
*
|
|
15711
|
+
* @see https://opencode.ai/docs/plugins/
|
|
15712
|
+
*/
|
|
15713
|
+
const SHELL_EVENT_TOOL_GATES = {
|
|
15714
|
+
beforeShellExecution: {
|
|
15715
|
+
toolEvent: "tool.execute.before",
|
|
15716
|
+
tool: "bash"
|
|
15717
|
+
},
|
|
15718
|
+
afterShellExecution: {
|
|
15719
|
+
toolEvent: "tool.execute.after",
|
|
15720
|
+
tool: "bash"
|
|
15721
|
+
}
|
|
15722
|
+
};
|
|
15536
15723
|
function escapeForTemplateLiteral(command) {
|
|
15537
15724
|
return command.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
|
|
15538
15725
|
}
|
|
@@ -15553,15 +15740,19 @@ function validateAndSanitizeMatcher(matcher) {
|
|
|
15553
15740
|
*/
|
|
15554
15741
|
function collectOpencodeStyleHandlers({ effectiveHooks, eventMap, namedEventHandlers, genericEventHandlers }) {
|
|
15555
15742
|
for (const [canonicalEvent, definitions] of Object.entries(effectiveHooks)) {
|
|
15556
|
-
const
|
|
15743
|
+
const shellGate = SHELL_EVENT_TOOL_GATES[canonicalEvent];
|
|
15744
|
+
const toolEvent = shellGate?.toolEvent ?? eventMap[canonicalEvent];
|
|
15557
15745
|
if (!toolEvent) continue;
|
|
15746
|
+
const matcherSupported = !shellGate && Object.hasOwn(NAMED_HOOK_MATCHER_SUBJECTS, toolEvent) && NAMED_HOOK_MATCHER_SUBJECTS[toolEvent] !== null;
|
|
15558
15747
|
const handlers = [];
|
|
15559
15748
|
for (const def of definitions) {
|
|
15560
15749
|
if ((def.type ?? "command") !== "command") continue;
|
|
15561
15750
|
if (!def.command) continue;
|
|
15751
|
+
if (def.matcher && !matcherSupported) continue;
|
|
15562
15752
|
handlers.push({
|
|
15563
15753
|
command: def.command,
|
|
15564
|
-
matcher: def.matcher ? def.matcher : void 0
|
|
15754
|
+
matcher: def.matcher ? def.matcher : void 0,
|
|
15755
|
+
...shellGate ? { toolGate: shellGate.tool } : {}
|
|
15565
15756
|
});
|
|
15566
15757
|
}
|
|
15567
15758
|
if (handlers.length > 0) {
|
|
@@ -15598,7 +15789,11 @@ function buildNamedEventBodyLines(namedEventHandlers) {
|
|
|
15598
15789
|
bodyLines.push(` "${eventName}": async (input) => {`);
|
|
15599
15790
|
for (const handler of handlers) {
|
|
15600
15791
|
const escapedCommand = escapeForTemplateLiteral(handler.command);
|
|
15601
|
-
if (handler.
|
|
15792
|
+
if (handler.toolGate) {
|
|
15793
|
+
bodyLines.push(` if (input.tool === "${handler.toolGate}") {`);
|
|
15794
|
+
bodyLines.push(` await $\`${escapedCommand}\`;`);
|
|
15795
|
+
bodyLines.push(" }");
|
|
15796
|
+
} else if (handler.matcher && matcherSubject !== null) {
|
|
15602
15797
|
const safeMatcher = validateAndSanitizeMatcher(handler.matcher);
|
|
15603
15798
|
bodyLines.push(" {");
|
|
15604
15799
|
bodyLines.push(` const __re = new RegExp("${safeMatcher}");`);
|
|
@@ -16396,6 +16591,13 @@ var OpencodeHooks = class OpencodeHooks extends ToolHooks {
|
|
|
16396
16591
|
*/
|
|
16397
16592
|
const PI_TOOL_EVENTS = /* @__PURE__ */ new Set(["tool_call", "tool_result"]);
|
|
16398
16593
|
/**
|
|
16594
|
+
* Pi extension events that fire for every message role (user, assistant,
|
|
16595
|
+
* toolResult). The generated handler gates these on the assistant role so a
|
|
16596
|
+
* `postModelInvocation` hook runs once per finalized model response rather
|
|
16597
|
+
* than for every message in the conversation.
|
|
16598
|
+
*/
|
|
16599
|
+
const PI_ASSISTANT_MESSAGE_EVENTS = /* @__PURE__ */ new Set(["message_end"]);
|
|
16600
|
+
/**
|
|
16399
16601
|
* Validate a hook matcher as a regular expression and return it as a JS
|
|
16400
16602
|
* string-literal (JSON.stringify quoting) safe to embed in generated code.
|
|
16401
16603
|
*/
|
|
@@ -16436,7 +16638,10 @@ function buildSubscriptionLines(handlerGroups) {
|
|
|
16436
16638
|
const lines = [];
|
|
16437
16639
|
for (const [piEvent, handlers] of Object.entries(handlerGroups)) {
|
|
16438
16640
|
const usesToolName = PI_TOOL_EVENTS.has(piEvent) && handlers.some((h) => h.matcher);
|
|
16439
|
-
|
|
16641
|
+
const gatesOnAssistant = PI_ASSISTANT_MESSAGE_EVENTS.has(piEvent);
|
|
16642
|
+
const usesEvent = usesToolName || gatesOnAssistant;
|
|
16643
|
+
lines.push(` pi.on(${JSON.stringify(piEvent)}, async (${usesEvent ? "event" : ""}) => {`);
|
|
16644
|
+
if (gatesOnAssistant) lines.push(` if (event.message.role !== "assistant") return;`);
|
|
16440
16645
|
for (const handler of handlers) {
|
|
16441
16646
|
const embeddedCommand = JSON.stringify(handler.command);
|
|
16442
16647
|
if (usesToolName && handler.matcher) {
|
|
@@ -16571,6 +16776,7 @@ function canonicalDefToQwencodeHook(def) {
|
|
|
16571
16776
|
const type = def.type ?? "command";
|
|
16572
16777
|
const isHttp = type === "http";
|
|
16573
16778
|
const isCommand = type === "command";
|
|
16779
|
+
const isPrompt = type === "prompt";
|
|
16574
16780
|
return {
|
|
16575
16781
|
type,
|
|
16576
16782
|
...compact({
|
|
@@ -16580,6 +16786,8 @@ function canonicalDefToQwencodeHook(def) {
|
|
|
16580
16786
|
name: def.name,
|
|
16581
16787
|
description: def.description,
|
|
16582
16788
|
statusMessage: def.statusMessage,
|
|
16789
|
+
prompt: isPrompt ? def.prompt : void 0,
|
|
16790
|
+
model: isPrompt ? def.model : void 0,
|
|
16583
16791
|
async: isCommand ? def.async : void 0,
|
|
16584
16792
|
env: isCommand ? def.env : void 0,
|
|
16585
16793
|
shell: isCommand ? def.shell : void 0,
|
|
@@ -16598,7 +16806,7 @@ function canonicalDefToQwencodeHook(def) {
|
|
|
16598
16806
|
* is needed here — commands are passed through verbatim and any such variable
|
|
16599
16807
|
* reference stays intact for Qwen Code to expand.
|
|
16600
16808
|
*/
|
|
16601
|
-
function canonicalToQwencodeHooks(config) {
|
|
16809
|
+
function canonicalToQwencodeHooks(config, logger) {
|
|
16602
16810
|
const qwencodeSupported = new Set(QWENCODE_HOOK_EVENTS);
|
|
16603
16811
|
const sharedHooks = {};
|
|
16604
16812
|
for (const [event, defs] of Object.entries(config.hooks)) if (qwencodeSupported.has(event)) sharedHooks[event] = defs;
|
|
@@ -16618,6 +16826,7 @@ function canonicalToQwencodeHooks(config) {
|
|
|
16618
16826
|
const byMatcher = /* @__PURE__ */ new Map();
|
|
16619
16827
|
for (const def of definitions) {
|
|
16620
16828
|
if (!qwencodeSupportedTypes.has(def.type ?? "command")) continue;
|
|
16829
|
+
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.`);
|
|
16621
16830
|
const key = def.matcher ?? "";
|
|
16622
16831
|
const list = byMatcher.get(key);
|
|
16623
16832
|
if (list) list.push(def);
|
|
@@ -16656,7 +16865,9 @@ const QwencodeHookEntrySchema = z.looseObject({
|
|
|
16656
16865
|
shell: z.optional(z.string()),
|
|
16657
16866
|
headers: z.optional(z.record(z.string(), z.string())),
|
|
16658
16867
|
allowedEnvVars: z.optional(z.array(z.string())),
|
|
16659
|
-
once: z.optional(z.boolean())
|
|
16868
|
+
once: z.optional(z.boolean()),
|
|
16869
|
+
prompt: z.optional(z.string()),
|
|
16870
|
+
model: z.optional(z.string())
|
|
16660
16871
|
});
|
|
16661
16872
|
/**
|
|
16662
16873
|
* A matcher group entry in a Qwen Code event array.
|
|
@@ -16671,37 +16882,43 @@ const QwencodeMatcherEntrySchema = z.looseObject({
|
|
|
16671
16882
|
/**
|
|
16672
16883
|
* Convert a single parsed Qwen Code matcher group into canonical hook definitions.
|
|
16673
16884
|
*/
|
|
16885
|
+
function qwencodeHookEntryToCanonical({ hook, sequential, matcher }) {
|
|
16886
|
+
const h = hook;
|
|
16887
|
+
const hookType = h.type === "command" || h.type === "prompt" || h.type === "http" || h.type === "function" ? h.type : "command";
|
|
16888
|
+
const isHttp = hookType === "http";
|
|
16889
|
+
const isCommand = hookType === "command";
|
|
16890
|
+
const isPrompt = hookType === "prompt";
|
|
16891
|
+
const shell = h.shell === "bash" || h.shell === "powershell" ? h.shell : void 0;
|
|
16892
|
+
return {
|
|
16893
|
+
type: hookType,
|
|
16894
|
+
...compact({
|
|
16895
|
+
command: h.command,
|
|
16896
|
+
url: h.url,
|
|
16897
|
+
timeout: h.timeout,
|
|
16898
|
+
name: h.name,
|
|
16899
|
+
description: h.description,
|
|
16900
|
+
statusMessage: h.statusMessage,
|
|
16901
|
+
async: isCommand ? h.async : void 0,
|
|
16902
|
+
env: isCommand ? h.env : void 0,
|
|
16903
|
+
shell: isCommand ? shell : void 0,
|
|
16904
|
+
headers: isHttp ? h.headers : void 0,
|
|
16905
|
+
allowedEnvVars: isHttp ? h.allowedEnvVars : void 0,
|
|
16906
|
+
once: isHttp ? h.once : void 0,
|
|
16907
|
+
prompt: isPrompt ? h.prompt : void 0,
|
|
16908
|
+
model: isPrompt ? h.model : void 0,
|
|
16909
|
+
sequential: sequential ? true : void 0,
|
|
16910
|
+
matcher
|
|
16911
|
+
})
|
|
16912
|
+
};
|
|
16913
|
+
}
|
|
16674
16914
|
function qwencodeMatcherEntryToCanonical(entry) {
|
|
16675
|
-
const defs = [];
|
|
16676
|
-
const hooks = entry.hooks ?? [];
|
|
16677
16915
|
const sequential = entry.sequential === true;
|
|
16678
16916
|
const matcher = entry.matcher !== void 0 && entry.matcher !== null && entry.matcher !== "" ? entry.matcher : void 0;
|
|
16679
|
-
|
|
16680
|
-
|
|
16681
|
-
|
|
16682
|
-
|
|
16683
|
-
|
|
16684
|
-
defs.push({
|
|
16685
|
-
type: hookType,
|
|
16686
|
-
...compact({
|
|
16687
|
-
command: h.command,
|
|
16688
|
-
url: h.url,
|
|
16689
|
-
timeout: h.timeout,
|
|
16690
|
-
name: h.name,
|
|
16691
|
-
description: h.description,
|
|
16692
|
-
statusMessage: h.statusMessage,
|
|
16693
|
-
async: isCommand ? h.async : void 0,
|
|
16694
|
-
env: isCommand ? h.env : void 0,
|
|
16695
|
-
shell: isCommand ? shell : void 0,
|
|
16696
|
-
headers: isHttp ? h.headers : void 0,
|
|
16697
|
-
allowedEnvVars: isHttp ? h.allowedEnvVars : void 0,
|
|
16698
|
-
once: isHttp ? h.once : void 0,
|
|
16699
|
-
sequential: sequential ? true : void 0,
|
|
16700
|
-
matcher
|
|
16701
|
-
})
|
|
16702
|
-
});
|
|
16703
|
-
}
|
|
16704
|
-
return defs;
|
|
16917
|
+
return (entry.hooks ?? []).map((hook) => qwencodeHookEntryToCanonical({
|
|
16918
|
+
hook,
|
|
16919
|
+
sequential,
|
|
16920
|
+
matcher
|
|
16921
|
+
}));
|
|
16705
16922
|
}
|
|
16706
16923
|
/**
|
|
16707
16924
|
* Extract hooks from Qwen Code settings.json into canonical format.
|
|
@@ -16749,12 +16966,12 @@ var QwencodeHooks = class QwencodeHooks extends ToolHooks {
|
|
|
16749
16966
|
validate
|
|
16750
16967
|
});
|
|
16751
16968
|
}
|
|
16752
|
-
static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false }) {
|
|
16969
|
+
static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false, logger }) {
|
|
16753
16970
|
const paths = QwencodeHooks.getSettablePaths({ global });
|
|
16754
16971
|
const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
16755
16972
|
const existingContent = await readFileContentOrNull(filePath) ?? JSON.stringify({}, null, 2);
|
|
16756
16973
|
const config = rulesyncHooks.getJson();
|
|
16757
|
-
const patch = { hooks: canonicalToQwencodeHooks(config) };
|
|
16974
|
+
const patch = { hooks: canonicalToQwencodeHooks(config, logger) };
|
|
16758
16975
|
const disableAllHooks = config.qwencode?.disableAllHooks;
|
|
16759
16976
|
if (typeof disableAllHooks === "boolean") patch.disableAllHooks = disableAllHooks;
|
|
16760
16977
|
const fileContent = applySharedConfigPatch({
|
|
@@ -17306,7 +17523,8 @@ const toolHooksFactories = /* @__PURE__ */ new Map([
|
|
|
17306
17523
|
},
|
|
17307
17524
|
supportedEvents: KILO_HOOK_EVENTS,
|
|
17308
17525
|
supportedHookTypes: ["command"],
|
|
17309
|
-
supportsMatcher: true
|
|
17526
|
+
supportsMatcher: true,
|
|
17527
|
+
matcherEvents: ["preToolUse", "postToolUse"]
|
|
17310
17528
|
}],
|
|
17311
17529
|
["opencode", {
|
|
17312
17530
|
class: OpencodeHooks,
|
|
@@ -17317,7 +17535,8 @@ const toolHooksFactories = /* @__PURE__ */ new Map([
|
|
|
17317
17535
|
},
|
|
17318
17536
|
supportedEvents: OPENCODE_HOOK_EVENTS,
|
|
17319
17537
|
supportedHookTypes: ["command"],
|
|
17320
|
-
supportsMatcher: true
|
|
17538
|
+
supportsMatcher: true,
|
|
17539
|
+
matcherEvents: ["preToolUse", "postToolUse"]
|
|
17321
17540
|
}],
|
|
17322
17541
|
["pi", {
|
|
17323
17542
|
class: PiHooks,
|
|
@@ -18991,6 +19210,7 @@ const toolIgnoreFactories = /* @__PURE__ */ new Map([
|
|
|
18991
19210
|
["qwencode", { class: QwencodeIgnore }],
|
|
18992
19211
|
["reasonix", { class: ReasonixIgnore }],
|
|
18993
19212
|
["roo", { class: RooIgnore }],
|
|
19213
|
+
["zoocode", { class: RooIgnore }],
|
|
18994
19214
|
["devin", { class: DevinIgnore }],
|
|
18995
19215
|
["vibe", { class: VibeIgnore }],
|
|
18996
19216
|
["warp", { class: WarpIgnore }],
|
|
@@ -20360,7 +20580,6 @@ const resolveCopilotcliServerType = (server) => {
|
|
|
20360
20580
|
case "streamable-http": return "http";
|
|
20361
20581
|
case "stdio":
|
|
20362
20582
|
case "local": return declared;
|
|
20363
|
-
default: break;
|
|
20364
20583
|
}
|
|
20365
20584
|
if (server.command !== void 0) return "stdio";
|
|
20366
20585
|
return isRemoteMcpServer(server) ? "http" : "stdio";
|
|
@@ -20780,18 +20999,24 @@ var DeepagentsMcp = class DeepagentsMcp extends ToolMcp {
|
|
|
20780
20999
|
/**
|
|
20781
21000
|
* MCP generator for Devin Local (native `.devin/` configuration).
|
|
20782
21001
|
*
|
|
20783
|
-
*
|
|
20784
|
-
*
|
|
20785
|
-
*
|
|
21002
|
+
* Since v3000.3 (the Local 3.6 release), Devin reads MCP servers from a
|
|
21003
|
+
* dedicated `mcpServers`-keyed config file rather than the shared
|
|
21004
|
+
* `config.json`:
|
|
21005
|
+
* - Project scope: `.devin/mcp_config.json`
|
|
21006
|
+
* - Global scope: `~/.config/devin/mcp_config.json`
|
|
21007
|
+
*
|
|
21008
|
+
* Legacy `mcpServers` entries left in `config.json` are auto-migrated into
|
|
21009
|
+
* the dedicated file on Devin startup, so writing the legacy key would fight
|
|
21010
|
+
* the migration. Import still falls back to the legacy `config.json`
|
|
21011
|
+
* `mcpServers` key when no dedicated file exists, so pre-v3000.3 repos
|
|
21012
|
+
* migrate cleanly. The gitignored `.devin/mcp_config.local.json` override is
|
|
21013
|
+
* the user's personal territory and is never read or written.
|
|
20786
21014
|
*
|
|
20787
|
-
*
|
|
20788
|
-
*
|
|
20789
|
-
*
|
|
20790
|
-
* never deleted. Each server is a stdio entry ({ command, args, env }) or a
|
|
20791
|
-
* remote entry ({ serverUrl | url, headers }), and may carry an optional
|
|
20792
|
-
* `disabledTools` array.
|
|
21015
|
+
* Each server is a stdio entry ({ command, args, env }) or a remote entry
|
|
21016
|
+
* ({ serverUrl | url, headers }), and may carry an optional `disabledTools`
|
|
21017
|
+
* array.
|
|
20793
21018
|
*
|
|
20794
|
-
* @see https://docs.devin.ai/cli/extensibility/configuration
|
|
21019
|
+
* @see https://docs.devin.ai/cli/extensibility/mcp/configuration
|
|
20795
21020
|
*/
|
|
20796
21021
|
var DevinMcp = class DevinMcp extends ToolMcp {
|
|
20797
21022
|
json;
|
|
@@ -20809,31 +21034,27 @@ var DevinMcp = class DevinMcp extends ToolMcp {
|
|
|
20809
21034
|
throw new Error(`Failed to parse Devin MCP config at ${join(relativeDirPath, relativeFilePath)}: ${formatError(error)}`, { cause: error });
|
|
20810
21035
|
}
|
|
20811
21036
|
}
|
|
20812
|
-
/**
|
|
20813
|
-
* config.json may carry the permissions/hooks features' keys, so it is never
|
|
20814
|
-
* deleted; only the managed `mcpServers` key is rewritten.
|
|
20815
|
-
*/
|
|
20816
|
-
isDeletable() {
|
|
20817
|
-
return false;
|
|
20818
|
-
}
|
|
20819
21037
|
static getSettablePaths({ global = false } = {}) {
|
|
20820
21038
|
if (global) return {
|
|
20821
21039
|
relativeDirPath: DEVIN_GLOBAL_CONFIG_DIR_PATH,
|
|
20822
|
-
relativeFilePath:
|
|
21040
|
+
relativeFilePath: DEVIN_MCP_CONFIG_FILE_NAME
|
|
20823
21041
|
};
|
|
20824
21042
|
return {
|
|
20825
21043
|
relativeDirPath: DEVIN_DIR,
|
|
20826
|
-
relativeFilePath:
|
|
21044
|
+
relativeFilePath: DEVIN_MCP_CONFIG_FILE_NAME
|
|
20827
21045
|
};
|
|
20828
21046
|
}
|
|
20829
21047
|
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
20830
21048
|
const paths = this.getSettablePaths({ global });
|
|
20831
|
-
|
|
20832
|
-
|
|
20833
|
-
|
|
20834
|
-
|
|
20835
|
-
|
|
20836
|
-
|
|
21049
|
+
let fileContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath));
|
|
21050
|
+
if (fileContent === null) {
|
|
21051
|
+
const legacyContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, DEVIN_CONFIG_FILE_NAME));
|
|
21052
|
+
if (legacyContent !== null) {
|
|
21053
|
+
const legacyJson = this.parseJsonOrThrow(legacyContent, paths.relativeDirPath, DEVIN_CONFIG_FILE_NAME);
|
|
21054
|
+
fileContent = JSON.stringify({ mcpServers: legacyJson.mcpServers ?? {} }, null, 2);
|
|
21055
|
+
}
|
|
21056
|
+
}
|
|
21057
|
+
const newJson = { mcpServers: this.parseJsonOrThrow(fileContent ?? "{\"mcpServers\":{}}", paths.relativeDirPath, paths.relativeFilePath).mcpServers ?? {} };
|
|
20837
21058
|
return new DevinMcp({
|
|
20838
21059
|
outputRoot,
|
|
20839
21060
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -20843,21 +21064,21 @@ var DevinMcp = class DevinMcp extends ToolMcp {
|
|
|
20843
21064
|
global
|
|
20844
21065
|
});
|
|
20845
21066
|
}
|
|
20846
|
-
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false }) {
|
|
21067
|
+
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false, logger }) {
|
|
20847
21068
|
const paths = this.getSettablePaths({ global });
|
|
20848
|
-
const
|
|
20849
|
-
|
|
21069
|
+
const existingContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath));
|
|
21070
|
+
if (existingContent !== null) {
|
|
21071
|
+
const existingJson = this.parseJsonOrThrow(existingContent, paths.relativeDirPath, paths.relativeFilePath);
|
|
21072
|
+
const existingServers = existingJson.mcpServers && typeof existingJson.mcpServers === "object" ? Object.keys(existingJson.mcpServers) : [];
|
|
21073
|
+
const managedServers = new Set(Object.keys(rulesyncMcp.getMcpServers()));
|
|
21074
|
+
const dropped = existingServers.filter((name) => !managedServers.has(name));
|
|
21075
|
+
if (dropped.length > 0) logger?.warn(`Devin MCP servers not managed by rulesync will be removed from ${join(paths.relativeDirPath, paths.relativeFilePath)}: ${dropped.join(", ")}. Run 'rulesync import' first to keep them, or move them to mcp_config.local.json.`);
|
|
21076
|
+
}
|
|
20850
21077
|
return new DevinMcp({
|
|
20851
21078
|
outputRoot,
|
|
20852
21079
|
relativeDirPath: paths.relativeDirPath,
|
|
20853
21080
|
relativeFilePath: paths.relativeFilePath,
|
|
20854
|
-
fileContent:
|
|
20855
|
-
fileKey: sharedConfigFileKey(paths),
|
|
20856
|
-
feature: "mcp",
|
|
20857
|
-
existingContent,
|
|
20858
|
-
patch: { mcpServers: rulesyncMcp.getMcpServers() },
|
|
20859
|
-
filePath
|
|
20860
|
-
}),
|
|
21081
|
+
fileContent: JSON.stringify({ mcpServers: rulesyncMcp.getMcpServers() }, null, 2),
|
|
20861
21082
|
validate,
|
|
20862
21083
|
global
|
|
20863
21084
|
});
|
|
@@ -22131,9 +22352,12 @@ var KiloMcp = class KiloMcp extends ToolMcp {
|
|
|
22131
22352
|
fileContent = await readFileContentOrNull(jsonPath);
|
|
22132
22353
|
if (fileContent) relativeFilePath = KILO_JSON_FILE_NAME;
|
|
22133
22354
|
}
|
|
22355
|
+
if (instructions.length === 0 && fileContent === null) return null;
|
|
22134
22356
|
const json = fileContent ? parse(fileContent) : {};
|
|
22135
22357
|
const existingInstructions = Array.isArray(json.instructions) ? json.instructions.filter((entry) => typeof entry === "string") : [];
|
|
22136
|
-
const
|
|
22358
|
+
const managedPrefix = `${toPosixPath(join(KILO_DIR, KILO_RULES_DIR_NAME))}/`;
|
|
22359
|
+
const preservedInstructions = existingInstructions.filter((entry) => !toPosixPath(entry).replace(/^\.\//, "").startsWith(managedPrefix));
|
|
22360
|
+
const mergedInstructions = Array.from(/* @__PURE__ */ new Set([...preservedInstructions, ...instructions])).toSorted();
|
|
22137
22361
|
return new KiloMcp({
|
|
22138
22362
|
outputRoot,
|
|
22139
22363
|
relativeDirPath: basePaths.relativeDirPath,
|
|
@@ -22142,7 +22366,7 @@ var KiloMcp = class KiloMcp extends ToolMcp {
|
|
|
22142
22366
|
fileKey: sharedConfigFileKey(basePaths),
|
|
22143
22367
|
feature: "rules",
|
|
22144
22368
|
existingContent: fileContent ?? "",
|
|
22145
|
-
patch: { instructions: mergedInstructions },
|
|
22369
|
+
patch: { instructions: mergedInstructions.length > 0 ? mergedInstructions : void 0 },
|
|
22146
22370
|
filePath: join(jsonDir, relativeFilePath)
|
|
22147
22371
|
}),
|
|
22148
22372
|
validate
|
|
@@ -22864,6 +23088,8 @@ var OpencodeMcp = class OpencodeMcp extends ToolMcp {
|
|
|
22864
23088
|
static async fromInstructions({ outputRoot = process.cwd(), instructions, validate = true, global = false }) {
|
|
22865
23089
|
const basePaths = this.getSettablePaths({ global });
|
|
22866
23090
|
const jsonDir = join(outputRoot, basePaths.relativeDirPath);
|
|
23091
|
+
const configDirPrefix = `${toPosixPath(basePaths.relativeDirPath).replace(/\/+$/, "")}/`;
|
|
23092
|
+
const normalizedInstructions = instructions.map((path) => global && path.startsWith(configDirPrefix) ? path.slice(configDirPrefix.length) : path);
|
|
22867
23093
|
let fileContent = null;
|
|
22868
23094
|
let relativeFilePath = OPENCODE_JSONC_FILE_NAME;
|
|
22869
23095
|
const jsoncPath = join(jsonDir, OPENCODE_JSONC_FILE_NAME);
|
|
@@ -22873,9 +23099,15 @@ var OpencodeMcp = class OpencodeMcp extends ToolMcp {
|
|
|
22873
23099
|
fileContent = await readFileContentOrNull(jsonPath);
|
|
22874
23100
|
if (fileContent) relativeFilePath = OPENCODE_JSON_FILE_NAME;
|
|
22875
23101
|
}
|
|
23102
|
+
if (instructions.length === 0 && fileContent === null) return null;
|
|
22876
23103
|
const json = fileContent ? parse(fileContent) : {};
|
|
22877
23104
|
const existingInstructions = Array.isArray(json.instructions) ? json.instructions.filter((entry) => typeof entry === "string") : [];
|
|
22878
|
-
const
|
|
23105
|
+
const managedPrefixes = global ? ["memories/", `${configDirPrefix}memories/`] : [`${toPosixPath(OPENCODE_DIR)}/memories/`];
|
|
23106
|
+
const preservedInstructions = existingInstructions.filter((entry) => {
|
|
23107
|
+
const normalized = toPosixPath(entry).replace(/^\.\//, "");
|
|
23108
|
+
return !managedPrefixes.some((prefix) => normalized.startsWith(prefix));
|
|
23109
|
+
});
|
|
23110
|
+
const mergedInstructions = Array.from(/* @__PURE__ */ new Set([...preservedInstructions, ...normalizedInstructions])).toSorted();
|
|
22879
23111
|
return new OpencodeMcp({
|
|
22880
23112
|
outputRoot,
|
|
22881
23113
|
relativeDirPath: basePaths.relativeDirPath,
|
|
@@ -22884,7 +23116,7 @@ var OpencodeMcp = class OpencodeMcp extends ToolMcp {
|
|
|
22884
23116
|
fileKey: sharedConfigFileKey(basePaths),
|
|
22885
23117
|
feature: "rules",
|
|
22886
23118
|
existingContent: fileContent ?? "",
|
|
22887
|
-
patch: { instructions: mergedInstructions },
|
|
23119
|
+
patch: { instructions: mergedInstructions.length > 0 ? mergedInstructions : void 0 },
|
|
22888
23120
|
filePath: join(jsonDir, relativeFilePath)
|
|
22889
23121
|
}),
|
|
22890
23122
|
validate
|
|
@@ -23324,9 +23556,13 @@ function parseRovodevMcpJson(fileContent, relativeDirPath, relativeFilePath) {
|
|
|
23324
23556
|
return parsed;
|
|
23325
23557
|
}
|
|
23326
23558
|
/**
|
|
23327
|
-
* Rovodev MCP:
|
|
23328
|
-
*
|
|
23329
|
-
*
|
|
23559
|
+
* Rovodev MCP: `~/.rovodev/mcp.json` (global) and the repo-committed project
|
|
23560
|
+
* `.rovodev/mcp.json` documented by the Bitbucket Cloud Agentic Pipelines
|
|
23561
|
+
* guide ("Register your MCP server in `.rovodev/mcp.json`", referenced via
|
|
23562
|
+
* `mcp.mcpConfigPath`). Same shape as Cursor: { mcpServers: { ... } }.
|
|
23563
|
+
* A server the canonical config marks `disabled: true` is still written here
|
|
23564
|
+
* and switched off through `mcp.disabledMcpServers` in the sibling
|
|
23565
|
+
* `config.yml` — the file Rovo Dev actually consults for disabling.
|
|
23330
23566
|
*/
|
|
23331
23567
|
/**
|
|
23332
23568
|
* Rovo Dev documents the per-server transport key as `transport`, with the
|
|
@@ -23352,11 +23588,7 @@ function lookupTransport(map, key) {
|
|
|
23352
23588
|
return Object.hasOwn(map, key) ? map[key] : void 0;
|
|
23353
23589
|
}
|
|
23354
23590
|
function toRovodevServer(name, server, logger) {
|
|
23355
|
-
const { type, transport, disabled, ...rest } = server;
|
|
23356
|
-
if (disabled === true) {
|
|
23357
|
-
logger?.warn(`Rovo Dev MCP: skipping "${name}" because it is disabled and mcp.json has no disable flag.`);
|
|
23358
|
-
return null;
|
|
23359
|
-
}
|
|
23591
|
+
const { type, transport, disabled: _disabled, ...rest } = server;
|
|
23360
23592
|
const declared = typeof transport === "string" ? transport : typeof type === "string" ? type : void 0;
|
|
23361
23593
|
if (declared === void 0) return rest;
|
|
23362
23594
|
const mapped = lookupTransport(CANONICAL_TO_ROVODEV_TRANSPORT, declared);
|
|
@@ -23378,6 +23610,44 @@ function fromRovodevServer(server) {
|
|
|
23378
23610
|
type: mapped
|
|
23379
23611
|
};
|
|
23380
23612
|
}
|
|
23613
|
+
/**
|
|
23614
|
+
* Read the sibling `config.yml` (same scope root as `mcp.json`) and return the
|
|
23615
|
+
* parsed document, `null` when the file does not exist. A malformed file
|
|
23616
|
+
* **throws**: the disable toggle lives here, so pretending a broken file says
|
|
23617
|
+
* nothing would silently re-enable servers on import and leave disabled ones
|
|
23618
|
+
* running on generate — both flip a security toggle the wrong way.
|
|
23619
|
+
*/
|
|
23620
|
+
async function readRovodevConfigYaml({ outputRoot }) {
|
|
23621
|
+
const content = await readFileContentOrNull(join(outputRoot, ROVODEV_DIR, ROVODEV_CONFIG_FILE_NAME));
|
|
23622
|
+
if (content === null) return null;
|
|
23623
|
+
return parseSharedConfig({
|
|
23624
|
+
format: "yaml",
|
|
23625
|
+
fileContent: content,
|
|
23626
|
+
filePath: join(ROVODEV_DIR, ROVODEV_CONFIG_FILE_NAME)
|
|
23627
|
+
});
|
|
23628
|
+
}
|
|
23629
|
+
function disabledNamesOf(config) {
|
|
23630
|
+
const mcpBlock = config && isRecord(config.mcp) ? config.mcp : {};
|
|
23631
|
+
return isStringArray$1(mcpBlock.disabledMcpServers) ? mcpBlock.disabledMcpServers : [];
|
|
23632
|
+
}
|
|
23633
|
+
/**
|
|
23634
|
+
* Auxiliary writer for the `mcp:` block of `.rovodev/config.yml` (project) /
|
|
23635
|
+
* `~/.rovodev/config.yml` (global). Carries `disabledMcpServers` — the key
|
|
23636
|
+
* Rovo Dev actually consults to switch a server off — recomputed from the
|
|
23637
|
+
* existing block so user keys (`mcpConfigPath`, `allowedMcpServers`, ...) and
|
|
23638
|
+
* disabled names for servers rulesync does not manage survive.
|
|
23639
|
+
*/
|
|
23640
|
+
var RovodevMcpConfigYaml = class extends ToolFile {
|
|
23641
|
+
isDeletable() {
|
|
23642
|
+
return false;
|
|
23643
|
+
}
|
|
23644
|
+
validate() {
|
|
23645
|
+
return {
|
|
23646
|
+
success: true,
|
|
23647
|
+
error: null
|
|
23648
|
+
};
|
|
23649
|
+
}
|
|
23650
|
+
};
|
|
23381
23651
|
var RovodevMcp = class RovodevMcp extends ToolMcp {
|
|
23382
23652
|
json;
|
|
23383
23653
|
constructor(params) {
|
|
@@ -23398,13 +23668,24 @@ var RovodevMcp = class RovodevMcp extends ToolMcp {
|
|
|
23398
23668
|
};
|
|
23399
23669
|
}
|
|
23400
23670
|
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
23401
|
-
if (!global) throw new Error("Rovodev MCP is global-only; use --global to sync ~/.rovodev/mcp.json");
|
|
23402
23671
|
const paths = this.getSettablePaths({ global });
|
|
23403
23672
|
const json = parseRovodevMcpJson(await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "{\"mcpServers\":{}}", paths.relativeDirPath, paths.relativeFilePath);
|
|
23404
23673
|
const newJson = {
|
|
23405
23674
|
...json,
|
|
23406
23675
|
mcpServers: json.mcpServers ?? {}
|
|
23407
23676
|
};
|
|
23677
|
+
const disabledNames = disabledNamesOf(await readRovodevConfigYaml({ outputRoot }));
|
|
23678
|
+
if (disabledNames.length > 0 && isMcpServers(newJson.mcpServers)) {
|
|
23679
|
+
const servers = newJson.mcpServers;
|
|
23680
|
+
for (const name of disabledNames) {
|
|
23681
|
+
if (!Object.hasOwn(servers, name)) continue;
|
|
23682
|
+
const server = servers[name];
|
|
23683
|
+
if (isPlainObject$1(server)) servers[name] = {
|
|
23684
|
+
...server,
|
|
23685
|
+
disabled: true
|
|
23686
|
+
};
|
|
23687
|
+
}
|
|
23688
|
+
}
|
|
23408
23689
|
return new RovodevMcp({
|
|
23409
23690
|
outputRoot,
|
|
23410
23691
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -23415,11 +23696,21 @@ var RovodevMcp = class RovodevMcp extends ToolMcp {
|
|
|
23415
23696
|
});
|
|
23416
23697
|
}
|
|
23417
23698
|
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false, logger }) {
|
|
23418
|
-
if (!global) throw new Error("Rovodev MCP is global-only; use --global to sync ~/.rovodev/mcp.json");
|
|
23419
23699
|
const paths = this.getSettablePaths({ global });
|
|
23420
23700
|
const json = parseRovodevMcpJson(await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? JSON.stringify({ mcpServers: {} }, null, 2), paths.relativeDirPath, paths.relativeFilePath);
|
|
23701
|
+
let canWriteDisableToggle = true;
|
|
23702
|
+
try {
|
|
23703
|
+
await readRovodevConfigYaml({ outputRoot });
|
|
23704
|
+
} catch {
|
|
23705
|
+
canWriteDisableToggle = false;
|
|
23706
|
+
}
|
|
23421
23707
|
const mcpServers = Object.fromEntries(Object.entries(rulesyncMcp.getMcpServers()).map(([name, server]) => {
|
|
23422
|
-
const
|
|
23708
|
+
const record = server;
|
|
23709
|
+
if (record.disabled === true && !canWriteDisableToggle) {
|
|
23710
|
+
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.`);
|
|
23711
|
+
return null;
|
|
23712
|
+
}
|
|
23713
|
+
const converted = toRovodevServer(name, record, logger);
|
|
23423
23714
|
return converted === null ? null : [name, converted];
|
|
23424
23715
|
}).filter((entry) => entry !== null));
|
|
23425
23716
|
const rovodevConfig = {
|
|
@@ -23435,6 +23726,64 @@ var RovodevMcp = class RovodevMcp extends ToolMcp {
|
|
|
23435
23726
|
global
|
|
23436
23727
|
});
|
|
23437
23728
|
}
|
|
23729
|
+
/**
|
|
23730
|
+
* `mcp.disabledMcpServers` lives in the shared `config.yml` the permissions
|
|
23731
|
+
* feature also writes. Declared here so the write-order derivation sees this
|
|
23732
|
+
* feature as one of that file's writers — it is not a settable path, since
|
|
23733
|
+
* the servers themselves live in `mcp.json`.
|
|
23734
|
+
*/
|
|
23735
|
+
static getExtraSharedWritePaths() {
|
|
23736
|
+
return [{
|
|
23737
|
+
relativeDirPath: ROVODEV_DIR,
|
|
23738
|
+
relativeFilePath: ROVODEV_CONFIG_FILE_NAME
|
|
23739
|
+
}];
|
|
23740
|
+
}
|
|
23741
|
+
static async getAuxiliaryFiles({ outputRoot = process.cwd(), global = false, rulesyncMcp, logger }) {
|
|
23742
|
+
const servers = rulesyncMcp.forTarget({
|
|
23743
|
+
toolTarget: "rovodev",
|
|
23744
|
+
logger
|
|
23745
|
+
}).getMcpServers();
|
|
23746
|
+
const managedNames = Object.keys(servers).filter((name) => toRovodevServer(name, servers[name]) !== null);
|
|
23747
|
+
const disabledNames = managedNames.filter((name) => {
|
|
23748
|
+
const server = servers[name];
|
|
23749
|
+
return isRecord(server) && server.disabled === true;
|
|
23750
|
+
});
|
|
23751
|
+
const existingContent = await readFileContentOrNull(join(outputRoot, ROVODEV_DIR, ROVODEV_CONFIG_FILE_NAME)) ?? "";
|
|
23752
|
+
let existingParsed;
|
|
23753
|
+
try {
|
|
23754
|
+
existingParsed = parseSharedConfig({
|
|
23755
|
+
format: "yaml",
|
|
23756
|
+
fileContent: existingContent,
|
|
23757
|
+
filePath: join(ROVODEV_DIR, ROVODEV_CONFIG_FILE_NAME)
|
|
23758
|
+
});
|
|
23759
|
+
} catch (error) {
|
|
23760
|
+
logger?.warn(`Skipping the Rovo Dev mcp.disabledMcpServers update: ${formatError(error)}`);
|
|
23761
|
+
return [];
|
|
23762
|
+
}
|
|
23763
|
+
const existingMcp = isRecord(existingParsed.mcp) ? { ...existingParsed.mcp } : {};
|
|
23764
|
+
const existingDisabled = isStringArray$1(existingMcp.disabledMcpServers) ? existingMcp.disabledMcpServers : [];
|
|
23765
|
+
const managedNameSet = new Set(managedNames);
|
|
23766
|
+
const reEnabled = existingDisabled.filter((name) => managedNameSet.has(name) && !disabledNames.includes(name));
|
|
23767
|
+
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.`);
|
|
23768
|
+
const mergedDisabled = [...existingDisabled.filter((name) => !managedNameSet.has(name)), ...disabledNames].toSorted();
|
|
23769
|
+
if (mergedDisabled.length > 0) existingMcp.disabledMcpServers = mergedDisabled;
|
|
23770
|
+
else delete existingMcp.disabledMcpServers;
|
|
23771
|
+
if (mergedDisabled.length === 0 && existingContent.trim() === "") return [];
|
|
23772
|
+
const fileContent = applySharedConfigPatch({
|
|
23773
|
+
fileKey: ROVODEV_CONFIG_SHARED_FILE_KEY,
|
|
23774
|
+
feature: "mcp",
|
|
23775
|
+
existingContent,
|
|
23776
|
+
patch: { mcp: Object.keys(existingMcp).length > 0 ? existingMcp : void 0 },
|
|
23777
|
+
filePath: join(ROVODEV_DIR, ROVODEV_CONFIG_FILE_NAME)
|
|
23778
|
+
});
|
|
23779
|
+
return [new RovodevMcpConfigYaml({
|
|
23780
|
+
outputRoot,
|
|
23781
|
+
relativeDirPath: ROVODEV_DIR,
|
|
23782
|
+
relativeFilePath: ROVODEV_CONFIG_FILE_NAME,
|
|
23783
|
+
fileContent,
|
|
23784
|
+
global
|
|
23785
|
+
})];
|
|
23786
|
+
}
|
|
23438
23787
|
toRulesyncMcp() {
|
|
23439
23788
|
const rawServers = isMcpServers(this.json.mcpServers) ? this.json.mcpServers : {};
|
|
23440
23789
|
const mcpServers = Object.fromEntries(Object.entries(rawServers).filter(([, server]) => isPlainObject$1(server)).map(([name, server]) => [name, fromRovodevServer(server)]));
|
|
@@ -23583,7 +23932,6 @@ function transportOf(server) {
|
|
|
23583
23932
|
case "http":
|
|
23584
23933
|
case "streamable-http":
|
|
23585
23934
|
case "ws": return "http";
|
|
23586
|
-
default: break;
|
|
23587
23935
|
}
|
|
23588
23936
|
if (server.command !== void 0) return "stdio";
|
|
23589
23937
|
if (server.url !== void 0 || server.httpUrl !== void 0) return "http";
|
|
@@ -23611,6 +23959,20 @@ function deriveTransportAllowlist(servers) {
|
|
|
23611
23959
|
return allowlist;
|
|
23612
23960
|
}
|
|
23613
23961
|
//#endregion
|
|
23962
|
+
//#region src/features/shared/vibe-config-scope.ts
|
|
23963
|
+
/**
|
|
23964
|
+
* Vibe selects exactly **one** persistence TOML and does not merge scopes: the
|
|
23965
|
+
* trusted project `.vibe/config.toml` when one is discovered, otherwise
|
|
23966
|
+
* `~/.vibe/config.toml` (single code path since v2.22.0's ConfigOrchestrator
|
|
23967
|
+
* migration). A `--global` run that writes the home file is therefore inert in
|
|
23968
|
+
* any project that has its own config — worth a heads-up, since the other Vibe
|
|
23969
|
+
* surfaces (rules, hooks, agents, skills) genuinely combine scopes.
|
|
23970
|
+
*/
|
|
23971
|
+
async function warnIfGlobalVibeConfigIsShadowed(logger) {
|
|
23972
|
+
if (!await fileExists(join(process.cwd(), ".vibe", "config.toml"))) return;
|
|
23973
|
+
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.");
|
|
23974
|
+
}
|
|
23975
|
+
//#endregion
|
|
23614
23976
|
//#region src/features/mcp/vibe-mcp.ts
|
|
23615
23977
|
const VIBE_MCP_SERVER_FIELDS = [
|
|
23616
23978
|
"transport",
|
|
@@ -23679,8 +24041,9 @@ var VibeMcp = class VibeMcp extends ToolMcp {
|
|
|
23679
24041
|
global
|
|
23680
24042
|
});
|
|
23681
24043
|
}
|
|
23682
|
-
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false }) {
|
|
24044
|
+
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, logger, global = false }) {
|
|
23683
24045
|
const paths = this.getSettablePaths({ global });
|
|
24046
|
+
if (global) await warnIfGlobalVibeConfigIsShadowed(logger);
|
|
23684
24047
|
const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
23685
24048
|
const existingContent = await readFileContentOrNull(filePath) ?? "";
|
|
23686
24049
|
const existingServers = normalizeMcpServersArray(parseSharedConfig({
|
|
@@ -23689,7 +24052,10 @@ var VibeMcp = class VibeMcp extends ToolMcp {
|
|
|
23689
24052
|
filePath
|
|
23690
24053
|
}).mcp_servers);
|
|
23691
24054
|
const existingByName = new Map(existingServers.map((server) => [server.name, server]));
|
|
23692
|
-
const
|
|
24055
|
+
const rulesyncServers = rulesyncMcp.getMcpServers();
|
|
24056
|
+
const managedNames = new Set(Object.keys(rulesyncServers));
|
|
24057
|
+
const mcpServers = Object.entries(rulesyncServers).map(([name, server]) => rulesyncMcpServerToVibe(name, server, existingByName.get(name)));
|
|
24058
|
+
const unmanagedServers = existingServers.filter((server) => !managedNames.has(server.name));
|
|
23693
24059
|
return new VibeMcp({
|
|
23694
24060
|
outputRoot,
|
|
23695
24061
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -23698,7 +24064,7 @@ var VibeMcp = class VibeMcp extends ToolMcp {
|
|
|
23698
24064
|
fileKey: sharedConfigFileKey(paths),
|
|
23699
24065
|
feature: "mcp",
|
|
23700
24066
|
existingContent,
|
|
23701
|
-
patch: { mcp_servers: mcpServers },
|
|
24067
|
+
patch: { mcp_servers: [...mcpServers, ...unmanagedServers] },
|
|
23702
24068
|
filePath
|
|
23703
24069
|
}),
|
|
23704
24070
|
validate,
|
|
@@ -24400,10 +24766,19 @@ const toolMcpFactories = /* @__PURE__ */ new Map([
|
|
|
24400
24766
|
supportsDisabledTools: false
|
|
24401
24767
|
}
|
|
24402
24768
|
}],
|
|
24769
|
+
["zoocode", {
|
|
24770
|
+
class: RooMcp,
|
|
24771
|
+
meta: {
|
|
24772
|
+
supportsProject: true,
|
|
24773
|
+
supportsGlobal: false,
|
|
24774
|
+
supportsEnabledTools: false,
|
|
24775
|
+
supportsDisabledTools: false
|
|
24776
|
+
}
|
|
24777
|
+
}],
|
|
24403
24778
|
["rovodev", {
|
|
24404
24779
|
class: RovodevMcp,
|
|
24405
24780
|
meta: {
|
|
24406
|
-
supportsProject:
|
|
24781
|
+
supportsProject: true,
|
|
24407
24782
|
supportsGlobal: true,
|
|
24408
24783
|
supportsEnabledTools: false,
|
|
24409
24784
|
supportsDisabledTools: false
|
|
@@ -25269,9 +25644,7 @@ function convertRulesyncToAntigravityCliPermissions(config) {
|
|
|
25269
25644
|
case "ask":
|
|
25270
25645
|
ask.push(entry);
|
|
25271
25646
|
break;
|
|
25272
|
-
case "deny":
|
|
25273
|
-
deny.push(entry);
|
|
25274
|
-
break;
|
|
25647
|
+
case "deny": deny.push(entry);
|
|
25275
25648
|
}
|
|
25276
25649
|
}
|
|
25277
25650
|
}
|
|
@@ -25482,9 +25855,7 @@ function convertRulesyncToAntigravityIdePermissions(config) {
|
|
|
25482
25855
|
case "ask":
|
|
25483
25856
|
ask.push(entry);
|
|
25484
25857
|
break;
|
|
25485
|
-
case "deny":
|
|
25486
|
-
deny.push(entry);
|
|
25487
|
-
break;
|
|
25858
|
+
case "deny": deny.push(entry);
|
|
25488
25859
|
}
|
|
25489
25860
|
}
|
|
25490
25861
|
}
|
|
@@ -26216,9 +26587,7 @@ function convertRulesyncToClaudePermissions({ config, logger }) {
|
|
|
26216
26587
|
case "ask":
|
|
26217
26588
|
ask.push(entry);
|
|
26218
26589
|
break;
|
|
26219
|
-
case "deny":
|
|
26220
|
-
deny.push(entry);
|
|
26221
|
-
break;
|
|
26590
|
+
case "deny": deny.push(entry);
|
|
26222
26591
|
}
|
|
26223
26592
|
}
|
|
26224
26593
|
}
|
|
@@ -27468,9 +27837,7 @@ function convertRulesyncToCursorPermissions(config, logger) {
|
|
|
27468
27837
|
case "ask":
|
|
27469
27838
|
logger?.warn(`Cursor CLI permissions do not support the "ask" action. Skipping ${category} rule: ${pattern}`);
|
|
27470
27839
|
break;
|
|
27471
|
-
case "deny":
|
|
27472
|
-
deny.push(entry);
|
|
27473
|
-
break;
|
|
27840
|
+
case "deny": deny.push(entry);
|
|
27474
27841
|
}
|
|
27475
27842
|
}
|
|
27476
27843
|
}
|
|
@@ -27575,9 +27942,10 @@ function buildDevinPermissionEntry(scope, pattern) {
|
|
|
27575
27942
|
* - Project scope: `.devin/config.json`
|
|
27576
27943
|
* - Global scope: `~/.config/devin/config.json`
|
|
27577
27944
|
*
|
|
27578
|
-
*
|
|
27579
|
-
*
|
|
27580
|
-
* the file is never deleted; only the managed
|
|
27945
|
+
* In global mode the config file is shared with the hooks (`hooks`) feature
|
|
27946
|
+
* (MCP moved to the dedicated mcp_config.json in v3000.3), so reads and writes
|
|
27947
|
+
* merge into the existing JSON and the file is never deleted; only the managed
|
|
27948
|
+
* `permissions` key is rewritten.
|
|
27581
27949
|
*
|
|
27582
27950
|
* @see https://docs.devin.ai/cli/reference/permissions
|
|
27583
27951
|
*/
|
|
@@ -27706,9 +28074,7 @@ function convertRulesyncToDevinPermissions(config) {
|
|
|
27706
28074
|
case "ask":
|
|
27707
28075
|
ask.push(entry);
|
|
27708
28076
|
break;
|
|
27709
|
-
case "deny":
|
|
27710
|
-
deny.push(entry);
|
|
27711
|
-
break;
|
|
28077
|
+
case "deny": deny.push(entry);
|
|
27712
28078
|
}
|
|
27713
28079
|
}
|
|
27714
28080
|
}
|
|
@@ -27896,10 +28262,7 @@ function convertRulesyncToFactorydroidPermissions({ config, logger }) {
|
|
|
27896
28262
|
case "allow":
|
|
27897
28263
|
allow.push(pattern);
|
|
27898
28264
|
break;
|
|
27899
|
-
case "deny":
|
|
27900
|
-
deny.push(pattern);
|
|
27901
|
-
break;
|
|
27902
|
-
case "ask": break;
|
|
28265
|
+
case "deny": deny.push(pattern);
|
|
27903
28266
|
}
|
|
27904
28267
|
}
|
|
27905
28268
|
return {
|
|
@@ -28018,10 +28381,11 @@ var GoosePermissions = class GoosePermissions extends ToolPermissions {
|
|
|
28018
28381
|
throw new Error(`Failed to parse existing Goose permission.yaml at ${filePath}: ${formatError(error)}`, { cause: error });
|
|
28019
28382
|
}
|
|
28020
28383
|
const config = isRecord(parsed) ? { ...parsed } : {};
|
|
28021
|
-
|
|
28384
|
+
const userPermission = convertRulesyncToGoosePermissionConfig({
|
|
28022
28385
|
config: rulesyncPermissions.getJson(),
|
|
28023
28386
|
logger
|
|
28024
28387
|
});
|
|
28388
|
+
config[GOOSE_USER_KEY] = userPermission;
|
|
28025
28389
|
return new GoosePermissions({
|
|
28026
28390
|
outputRoot,
|
|
28027
28391
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -30239,9 +30603,7 @@ function convertRulesyncToQwenPermissions(config) {
|
|
|
30239
30603
|
case "ask":
|
|
30240
30604
|
ask.push(entry);
|
|
30241
30605
|
break;
|
|
30242
|
-
case "deny":
|
|
30243
|
-
deny.push(entry);
|
|
30244
|
-
break;
|
|
30606
|
+
case "deny": deny.push(entry);
|
|
30245
30607
|
}
|
|
30246
30608
|
}
|
|
30247
30609
|
}
|
|
@@ -30314,6 +30676,20 @@ function toCanonicalToolName$2(reasonixName) {
|
|
|
30314
30676
|
return REASONIX_TO_CANONICAL_TOOL_NAMES[reasonixName] ?? reasonixName;
|
|
30315
30677
|
}
|
|
30316
30678
|
/**
|
|
30679
|
+
* Whether an entry uses the first-class `Tool=<literal>` exact-command form
|
|
30680
|
+
* (SPEC §3.7, v1.18.0): metacharacters in the literal are ordinary characters
|
|
30681
|
+
* and only the identical complete command matches. These entries have no
|
|
30682
|
+
* canonical tool→pattern→action equivalent — glob-style `Tool(...)` matches
|
|
30683
|
+
* differently by design — so they are preserved verbatim and round-trip
|
|
30684
|
+
* through the `reasonix` override's rawAllow/rawAsk/rawDeny arrays.
|
|
30685
|
+
*/
|
|
30686
|
+
function isReasonixExactCommandEntry(entry) {
|
|
30687
|
+
const eqIndex = entry.indexOf("=");
|
|
30688
|
+
if (eqIndex <= 0) return false;
|
|
30689
|
+
const parenIndex = entry.indexOf("(");
|
|
30690
|
+
return parenIndex === -1 || eqIndex < parenIndex;
|
|
30691
|
+
}
|
|
30692
|
+
/**
|
|
30317
30693
|
* Parse a Reasonix permission entry like "Bash(npm run *)" into tool name and pattern.
|
|
30318
30694
|
* If no parentheses, returns the tool name with "*" as the pattern.
|
|
30319
30695
|
*/
|
|
@@ -30402,10 +30778,22 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
30402
30778
|
const config = rulesyncPermissions.getJson();
|
|
30403
30779
|
const { allow, ask, deny } = convertRulesyncToReasonixPermissions(config);
|
|
30404
30780
|
const managedToolNames = new Set(Object.keys(config.permission).map((category) => toReasonixToolName(category)));
|
|
30781
|
+
const override = config.reasonix;
|
|
30782
|
+
const rawAllow = toReasonixStringArray(override?.rawAllow);
|
|
30783
|
+
const rawAsk = toReasonixStringArray(override?.rawAsk);
|
|
30784
|
+
const rawDeny = toReasonixStringArray(override?.rawDeny);
|
|
30785
|
+
const rawOwnedEntries = /* @__PURE__ */ new Set([
|
|
30786
|
+
...rawAllow,
|
|
30787
|
+
...rawAsk,
|
|
30788
|
+
...rawDeny
|
|
30789
|
+
]);
|
|
30405
30790
|
const existingPermissions = toReasonixTable(parsed.permissions);
|
|
30406
|
-
const preservedAllow
|
|
30407
|
-
|
|
30408
|
-
|
|
30791
|
+
const { allow: preservedAllow, ask: preservedAsk, deny: preservedDeny } = preserveExistingEntries({
|
|
30792
|
+
existingPermissions,
|
|
30793
|
+
managedToolNames,
|
|
30794
|
+
rawOwnedEntries,
|
|
30795
|
+
logger
|
|
30796
|
+
});
|
|
30409
30797
|
if (logger && managedToolNames.has("Read")) {
|
|
30410
30798
|
const droppedReadDenyEntries = toReasonixStringArray(existingPermissions.deny).filter((entry) => {
|
|
30411
30799
|
const { toolName } = parseReasonixPermissionEntry(entry);
|
|
@@ -30414,17 +30802,22 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
30414
30802
|
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.`);
|
|
30415
30803
|
}
|
|
30416
30804
|
const mergedPermissions = { ...existingPermissions };
|
|
30417
|
-
|
|
30418
|
-
|
|
30419
|
-
|
|
30420
|
-
|
|
30421
|
-
|
|
30422
|
-
|
|
30423
|
-
|
|
30424
|
-
|
|
30425
|
-
|
|
30805
|
+
setOrDeleteEntries(mergedPermissions, "allow", [
|
|
30806
|
+
...preservedAllow,
|
|
30807
|
+
...allow,
|
|
30808
|
+
...rawAllow
|
|
30809
|
+
]);
|
|
30810
|
+
setOrDeleteEntries(mergedPermissions, "ask", [
|
|
30811
|
+
...preservedAsk,
|
|
30812
|
+
...ask,
|
|
30813
|
+
...rawAsk
|
|
30814
|
+
]);
|
|
30815
|
+
setOrDeleteEntries(mergedPermissions, "deny", [
|
|
30816
|
+
...preservedDeny,
|
|
30817
|
+
...deny,
|
|
30818
|
+
...rawDeny
|
|
30819
|
+
]);
|
|
30426
30820
|
const patch = { permissions: mergedPermissions };
|
|
30427
|
-
const override = config.reasonix;
|
|
30428
30821
|
if (override?.sandbox !== void 0) patch.sandbox = {
|
|
30429
30822
|
...asReasonixRecord(parsed.sandbox),
|
|
30430
30823
|
...asReasonixRecord(override.sandbox)
|
|
@@ -30455,16 +30848,29 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
30455
30848
|
}
|
|
30456
30849
|
toRulesyncPermissions() {
|
|
30457
30850
|
const permissions = toReasonixTable(this.toml.permissions);
|
|
30851
|
+
const splitExact = (entries) => {
|
|
30852
|
+
const exact = entries.filter((entry) => isReasonixExactCommandEntry(entry));
|
|
30853
|
+
return {
|
|
30854
|
+
translated: entries.filter((entry) => !isReasonixExactCommandEntry(entry)),
|
|
30855
|
+
exact
|
|
30856
|
+
};
|
|
30857
|
+
};
|
|
30858
|
+
const allowSplit = splitExact(toReasonixStringArray(permissions.allow));
|
|
30859
|
+
const askSplit = splitExact(toReasonixStringArray(permissions.ask));
|
|
30860
|
+
const denySplit = splitExact(toReasonixStringArray(permissions.deny));
|
|
30458
30861
|
const config = convertReasonixToRulesyncPermissions({
|
|
30459
|
-
allow:
|
|
30460
|
-
ask:
|
|
30461
|
-
deny:
|
|
30862
|
+
allow: allowSplit.translated,
|
|
30863
|
+
ask: askSplit.translated,
|
|
30864
|
+
deny: denySplit.translated
|
|
30462
30865
|
});
|
|
30463
30866
|
const sandbox = asReasonixRecord(this.toml.sandbox);
|
|
30464
30867
|
const agentPlanMode = pickReasonixKeys(this.toml.agent, [...REASONIX_OVERRIDE_AGENT_KEYS, ...REASONIX_RETIRED_AGENT_KEYS]);
|
|
30465
30868
|
const reasonixOverride = {};
|
|
30466
30869
|
if (Object.keys(sandbox).length > 0) reasonixOverride.sandbox = sandbox;
|
|
30467
30870
|
if (Object.keys(agentPlanMode).length > 0) reasonixOverride.agent = agentPlanMode;
|
|
30871
|
+
if (allowSplit.exact.length > 0) reasonixOverride.rawAllow = allowSplit.exact;
|
|
30872
|
+
if (askSplit.exact.length > 0) reasonixOverride.rawAsk = askSplit.exact;
|
|
30873
|
+
if (denySplit.exact.length > 0) reasonixOverride.rawDeny = denySplit.exact;
|
|
30468
30874
|
const result = { ...config };
|
|
30469
30875
|
if (Object.keys(reasonixOverride).length > 0) result.reasonix = reasonixOverride;
|
|
30470
30876
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
|
|
@@ -30494,6 +30900,41 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
30494
30900
|
}
|
|
30495
30901
|
};
|
|
30496
30902
|
/**
|
|
30903
|
+
* Preserve existing permission entries rulesync does not manage: entries for
|
|
30904
|
+
* tools NOT in the permissions config, plus exact `Tool=<literal>` entries not
|
|
30905
|
+
* owned by the override's raw arrays — Reasonix writes those itself as
|
|
30906
|
+
* remembered approvals, and dropping them would revoke grants the canonical
|
|
30907
|
+
* glob rules cannot re-express. Remove one by deleting it from `reasonix.toml`
|
|
30908
|
+
* (or by naming it in a raw array, which takes ownership). Preserved exact
|
|
30909
|
+
* entries are logged so the kept-alive grants stay visible.
|
|
30910
|
+
*/
|
|
30911
|
+
function preserveExistingEntries({ existingPermissions, managedToolNames, rawOwnedEntries, logger }) {
|
|
30912
|
+
const isPreserved = (entry) => {
|
|
30913
|
+
if (isReasonixExactCommandEntry(entry)) return !rawOwnedEntries.has(entry);
|
|
30914
|
+
return !managedToolNames.has(parseReasonixPermissionEntry(entry).toolName);
|
|
30915
|
+
};
|
|
30916
|
+
const allow = toReasonixStringArray(existingPermissions.allow).filter(isPreserved);
|
|
30917
|
+
const ask = toReasonixStringArray(existingPermissions.ask).filter(isPreserved);
|
|
30918
|
+
const deny = toReasonixStringArray(existingPermissions.deny).filter(isPreserved);
|
|
30919
|
+
const preservedExact = [
|
|
30920
|
+
...allow,
|
|
30921
|
+
...ask,
|
|
30922
|
+
...deny
|
|
30923
|
+
].filter((entry) => isReasonixExactCommandEntry(entry));
|
|
30924
|
+
if (preservedExact.length > 0) logger?.info(`Preserving ${preservedExact.length} exact Reasonix permission entries (remembered approvals): ${preservedExact.join(", ")}`);
|
|
30925
|
+
return {
|
|
30926
|
+
allow,
|
|
30927
|
+
ask,
|
|
30928
|
+
deny
|
|
30929
|
+
};
|
|
30930
|
+
}
|
|
30931
|
+
/** Sort, dedupe and set a permissions array key, deleting it when empty. */
|
|
30932
|
+
function setOrDeleteEntries(table, key, entries) {
|
|
30933
|
+
const merged = uniq(entries.toSorted());
|
|
30934
|
+
if (merged.length > 0) table[key] = merged;
|
|
30935
|
+
else delete table[key];
|
|
30936
|
+
}
|
|
30937
|
+
/**
|
|
30497
30938
|
* Convert rulesync permissions config to Reasonix allow/ask/deny arrays.
|
|
30498
30939
|
*/
|
|
30499
30940
|
function convertRulesyncToReasonixPermissions(config) {
|
|
@@ -30511,9 +30952,7 @@ function convertRulesyncToReasonixPermissions(config) {
|
|
|
30511
30952
|
case "ask":
|
|
30512
30953
|
ask.push(entry);
|
|
30513
30954
|
break;
|
|
30514
|
-
case "deny":
|
|
30515
|
-
deny.push(entry);
|
|
30516
|
-
break;
|
|
30955
|
+
case "deny": deny.push(entry);
|
|
30517
30956
|
}
|
|
30518
30957
|
}
|
|
30519
30958
|
}
|
|
@@ -30543,7 +30982,6 @@ function convertReasonixToRulesyncPermissions(params) {
|
|
|
30543
30982
|
}
|
|
30544
30983
|
//#endregion
|
|
30545
30984
|
//#region src/features/permissions/rovodev-permissions.ts
|
|
30546
|
-
const ROVODEV_GLOBAL_ONLY_MESSAGE = "Rovodev permissions are global-only; use --global to sync ~/.rovodev/config.yml";
|
|
30547
30985
|
const CATCH_ALL_PATTERN$1 = "*";
|
|
30548
30986
|
const CATEGORY_TO_TOOL_KEYS = {
|
|
30549
30987
|
read: [
|
|
@@ -30603,9 +31041,12 @@ const OWNED_TOOL_PERMISSION_KEYS = [
|
|
|
30603
31041
|
/**
|
|
30604
31042
|
* Permissions adapter for Rovo Dev CLI.
|
|
30605
31043
|
*
|
|
30606
|
-
* Rovo Dev reads tool permissions from the `toolPermissions` block of
|
|
30607
|
-
*
|
|
30608
|
-
*
|
|
31044
|
+
* Rovo Dev reads tool permissions from the `toolPermissions` block of
|
|
31045
|
+
* `config.yml` — the global `~/.rovodev/config.yml`, and since the Bitbucket
|
|
31046
|
+
* Cloud Agentic Pipelines docs, also the repo-committed project
|
|
31047
|
+
* `.rovodev/config.yml` (referenced from `bitbucket-pipelines.yml` via
|
|
31048
|
+
* `config.path`, or the `--config-file` CLI flag). Both scopes use the
|
|
31049
|
+
* same relative path, resolved against the project root or home directory.
|
|
30609
31050
|
*
|
|
30610
31051
|
* Rovo Dev's three levels (`allow`/`ask`/`deny`) are an exact 1:1 with rulesync's
|
|
30611
31052
|
* canonical action enum, so action values pass through verbatim.
|
|
@@ -30652,7 +31093,6 @@ var RovodevPermissions = class RovodevPermissions extends ToolPermissions {
|
|
|
30652
31093
|
};
|
|
30653
31094
|
}
|
|
30654
31095
|
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
30655
|
-
if (!global) throw new Error(ROVODEV_GLOBAL_ONLY_MESSAGE);
|
|
30656
31096
|
const paths = RovodevPermissions.getSettablePaths({ global });
|
|
30657
31097
|
const fileContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "";
|
|
30658
31098
|
return new RovodevPermissions({
|
|
@@ -30661,11 +31101,10 @@ var RovodevPermissions = class RovodevPermissions extends ToolPermissions {
|
|
|
30661
31101
|
relativeFilePath: paths.relativeFilePath,
|
|
30662
31102
|
fileContent,
|
|
30663
31103
|
validate,
|
|
30664
|
-
global
|
|
31104
|
+
global
|
|
30665
31105
|
});
|
|
30666
31106
|
}
|
|
30667
31107
|
static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, logger, global = false }) {
|
|
30668
|
-
if (!global) throw new Error(ROVODEV_GLOBAL_ONLY_MESSAGE);
|
|
30669
31108
|
const paths = RovodevPermissions.getSettablePaths({ global });
|
|
30670
31109
|
const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
30671
31110
|
const existingContent = await readFileContentOrNull(filePath) ?? "";
|
|
@@ -30688,14 +31127,20 @@ var RovodevPermissions = class RovodevPermissions extends ToolPermissions {
|
|
|
30688
31127
|
filePath,
|
|
30689
31128
|
logger
|
|
30690
31129
|
});
|
|
30691
|
-
|
|
31130
|
+
const fileContent = resolvedToolPermissions !== void 0 ? applySharedConfigPatch({
|
|
31131
|
+
fileKey: ROVODEV_CONFIG_SHARED_FILE_KEY,
|
|
31132
|
+
feature: "permissions",
|
|
31133
|
+
existingContent,
|
|
31134
|
+
patch: { toolPermissions: resolvedToolPermissions },
|
|
31135
|
+
filePath
|
|
31136
|
+
}) : dump(config);
|
|
30692
31137
|
return new RovodevPermissions({
|
|
30693
31138
|
outputRoot,
|
|
30694
31139
|
relativeDirPath: paths.relativeDirPath,
|
|
30695
31140
|
relativeFilePath: paths.relativeFilePath,
|
|
30696
|
-
fileContent
|
|
31141
|
+
fileContent,
|
|
30697
31142
|
validate: true,
|
|
30698
|
-
global
|
|
31143
|
+
global
|
|
30699
31144
|
});
|
|
30700
31145
|
}
|
|
30701
31146
|
toRulesyncPermissions() {
|
|
@@ -31232,6 +31677,7 @@ const CANONICAL_TO_VIBE_TOOL_NAMES = {
|
|
|
31232
31677
|
write: "write_file",
|
|
31233
31678
|
webfetch: "web_fetch",
|
|
31234
31679
|
websearch: "web_search",
|
|
31680
|
+
grep: "grep",
|
|
31235
31681
|
agent: "task"
|
|
31236
31682
|
};
|
|
31237
31683
|
const VIBE_TO_CANONICAL_TOOL_NAMES = {
|
|
@@ -31241,6 +31687,7 @@ const VIBE_TO_CANONICAL_TOOL_NAMES = {
|
|
|
31241
31687
|
write_file: "write",
|
|
31242
31688
|
web_fetch: "webfetch",
|
|
31243
31689
|
web_search: "websearch",
|
|
31690
|
+
grep: "grep",
|
|
31244
31691
|
task: "agent"
|
|
31245
31692
|
};
|
|
31246
31693
|
var VibePermissions = class VibePermissions extends ToolPermissions {
|
|
@@ -31278,6 +31725,7 @@ var VibePermissions = class VibePermissions extends ToolPermissions {
|
|
|
31278
31725
|
}
|
|
31279
31726
|
static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, validate = true, logger, global = false }) {
|
|
31280
31727
|
const paths = this.getSettablePaths({ global });
|
|
31728
|
+
if (global) await warnIfGlobalVibeConfigIsShadowed(logger);
|
|
31281
31729
|
const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
31282
31730
|
const existingContent = await readFileContentOrNull(filePath) ?? "";
|
|
31283
31731
|
const config = parseVibeConfig(existingContent);
|
|
@@ -31285,46 +31733,31 @@ var VibePermissions = class VibePermissions extends ToolPermissions {
|
|
|
31285
31733
|
const tools = toVibeToolsRecord(config.tools);
|
|
31286
31734
|
const enabledTools = new Set(toStringArray(config.enabled_tools));
|
|
31287
31735
|
const disabledTools = new Set(toStringArray(config.disabled_tools));
|
|
31736
|
+
const removedEnabledTools = [];
|
|
31288
31737
|
for (const category of Object.keys(permission)) {
|
|
31738
|
+
if (!hasVibeToolName(category)) continue;
|
|
31289
31739
|
const vibeToolName = toVibeToolName(category);
|
|
31290
|
-
enabledTools.delete(vibeToolName);
|
|
31740
|
+
if (enabledTools.delete(vibeToolName)) removedEnabledTools.push(vibeToolName);
|
|
31291
31741
|
disabledTools.delete(vibeToolName);
|
|
31292
31742
|
}
|
|
31293
31743
|
for (const [category, rules] of Object.entries(permission)) {
|
|
31294
|
-
|
|
31295
|
-
|
|
31296
|
-
|
|
31297
|
-
|
|
31298
|
-
const deny = new Set(toStringArray(existingTool.deny ?? existingTool.denylist));
|
|
31299
|
-
for (const [pattern, action] of Object.entries(rules)) {
|
|
31300
|
-
if (pattern === "*") {
|
|
31301
|
-
applyWildcardPermission({
|
|
31302
|
-
action,
|
|
31303
|
-
toolConfig: nextTool
|
|
31304
|
-
});
|
|
31305
|
-
if (action === "deny") {
|
|
31306
|
-
disabledTools.add(vibeToolName);
|
|
31307
|
-
enabledTools.delete(vibeToolName);
|
|
31308
|
-
} else if (action === "allow") {
|
|
31309
|
-
enabledTools.add(vibeToolName);
|
|
31310
|
-
disabledTools.delete(vibeToolName);
|
|
31311
|
-
}
|
|
31312
|
-
continue;
|
|
31313
|
-
}
|
|
31314
|
-
if (action === "ask") {
|
|
31315
|
-
logger?.warn(`Vibe permissions do not support pattern-level "ask" rules. Skipping ${category}: ${pattern}`);
|
|
31316
|
-
continue;
|
|
31317
|
-
}
|
|
31318
|
-
if (action === "allow") allow.add(pattern);
|
|
31319
|
-
else deny.add(pattern);
|
|
31744
|
+
if (!hasVibeToolName(category)) {
|
|
31745
|
+
const ruleCount = Object.keys(rules).length;
|
|
31746
|
+
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.`);
|
|
31747
|
+
continue;
|
|
31320
31748
|
}
|
|
31321
|
-
|
|
31322
|
-
|
|
31323
|
-
|
|
31324
|
-
|
|
31325
|
-
|
|
31749
|
+
applyCategoryRules({
|
|
31750
|
+
category,
|
|
31751
|
+
rules,
|
|
31752
|
+
tools,
|
|
31753
|
+
enabledTools,
|
|
31754
|
+
disabledTools,
|
|
31755
|
+
logger
|
|
31756
|
+
});
|
|
31326
31757
|
}
|
|
31327
|
-
|
|
31758
|
+
const vibeOverride = rulesyncPermissions.getJson().vibe;
|
|
31759
|
+
applyVibeSensitivePatterns(tools, vibeOverride, logger);
|
|
31760
|
+
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.`);
|
|
31328
31761
|
return new VibePermissions({
|
|
31329
31762
|
outputRoot,
|
|
31330
31763
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -31335,7 +31768,10 @@ var VibePermissions = class VibePermissions extends ToolPermissions {
|
|
|
31335
31768
|
existingContent,
|
|
31336
31769
|
patch: {
|
|
31337
31770
|
tools,
|
|
31338
|
-
enabled_tools:
|
|
31771
|
+
enabled_tools: resolveEnabledToolsPatch({
|
|
31772
|
+
vibeOverride,
|
|
31773
|
+
enabledTools
|
|
31774
|
+
}),
|
|
31339
31775
|
disabled_tools: disabledTools.size > 0 ? [...disabledTools].toSorted() : void 0
|
|
31340
31776
|
},
|
|
31341
31777
|
filePath
|
|
@@ -31347,7 +31783,7 @@ var VibePermissions = class VibePermissions extends ToolPermissions {
|
|
|
31347
31783
|
toRulesyncPermissions() {
|
|
31348
31784
|
const permission = {};
|
|
31349
31785
|
const vibeOverridePermission = {};
|
|
31350
|
-
|
|
31786
|
+
const enabledToolsList = toStringArray(this.toml.enabled_tools);
|
|
31351
31787
|
for (const tool of toStringArray(this.toml.disabled_tools)) ensurePermission(permission, toCanonicalToolName$1(tool))["*"] = "deny";
|
|
31352
31788
|
for (const [vibeToolName, toolConfig] of Object.entries(toVibeToolsRecord(this.toml.tools))) {
|
|
31353
31789
|
const category = toCanonicalToolName$1(vibeToolName);
|
|
@@ -31360,9 +31796,12 @@ var VibePermissions = class VibePermissions extends ToolPermissions {
|
|
|
31360
31796
|
if (sensitivePatterns.length > 0) vibeOverridePermission[category] = { sensitive_patterns: sensitivePatterns };
|
|
31361
31797
|
}
|
|
31362
31798
|
for (const [category, rules] of Object.entries(permission)) if (Object.keys(rules).length === 0) delete permission[category];
|
|
31363
|
-
const
|
|
31799
|
+
const vibeOverride = {};
|
|
31800
|
+
if (Object.keys(vibeOverridePermission).length > 0) vibeOverride.permission = vibeOverridePermission;
|
|
31801
|
+
if (enabledToolsList.length > 0) vibeOverride.enabled_tools = enabledToolsList;
|
|
31802
|
+
const json = Object.keys(vibeOverride).length > 0 ? {
|
|
31364
31803
|
permission,
|
|
31365
|
-
vibe:
|
|
31804
|
+
vibe: vibeOverride
|
|
31366
31805
|
} : { permission };
|
|
31367
31806
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(json, null, 2) });
|
|
31368
31807
|
}
|
|
@@ -31397,8 +31836,12 @@ var VibePermissions = class VibePermissions extends ToolPermissions {
|
|
|
31397
31836
|
* list for any category the override names: a present list is set, an empty
|
|
31398
31837
|
* one clears it. Categories not named keep whatever the existing file had.
|
|
31399
31838
|
*/
|
|
31400
|
-
function applyVibeSensitivePatterns(tools, vibeOverride) {
|
|
31839
|
+
function applyVibeSensitivePatterns(tools, vibeOverride, logger) {
|
|
31401
31840
|
for (const [category, toolOverride] of Object.entries(vibeOverride?.permission ?? {})) {
|
|
31841
|
+
if (!hasVibeToolName(category)) {
|
|
31842
|
+
logger?.warn(`Vibe has no builtin tool for the '${category}' category; skipping its vibe.permission.${category}.sensitive_patterns override.`);
|
|
31843
|
+
continue;
|
|
31844
|
+
}
|
|
31402
31845
|
const vibeToolName = toVibeToolName(category);
|
|
31403
31846
|
const nextTool = toVibeToolConfig(tools[vibeToolName]);
|
|
31404
31847
|
const patterns = toStringArray(toolOverride.sensitive_patterns);
|
|
@@ -31407,6 +31850,57 @@ function applyVibeSensitivePatterns(tools, vibeOverride) {
|
|
|
31407
31850
|
tools[vibeToolName] = nextTool;
|
|
31408
31851
|
}
|
|
31409
31852
|
}
|
|
31853
|
+
/**
|
|
31854
|
+
* Translate one canonical category's rules into the tool's `[tools.<name>]`
|
|
31855
|
+
* table and the top-level `disabled_tools` filter, mutating `tools` and the
|
|
31856
|
+
* filter sets in place.
|
|
31857
|
+
*/
|
|
31858
|
+
function applyCategoryRules({ category, rules, tools, enabledTools, disabledTools, logger }) {
|
|
31859
|
+
const vibeToolName = toVibeToolName(category);
|
|
31860
|
+
const existingTool = toVibeToolConfig(tools[vibeToolName]);
|
|
31861
|
+
const nextTool = { ...existingTool };
|
|
31862
|
+
const allow = new Set(toStringArray(existingTool.allow ?? existingTool.allowlist));
|
|
31863
|
+
const deny = new Set(toStringArray(existingTool.deny ?? existingTool.denylist));
|
|
31864
|
+
for (const [pattern, action] of Object.entries(rules)) {
|
|
31865
|
+
if (pattern === "*") {
|
|
31866
|
+
applyWildcardPermission({
|
|
31867
|
+
action,
|
|
31868
|
+
toolConfig: nextTool
|
|
31869
|
+
});
|
|
31870
|
+
if (action === "deny") {
|
|
31871
|
+
disabledTools.add(vibeToolName);
|
|
31872
|
+
enabledTools.delete(vibeToolName);
|
|
31873
|
+
} else if (action === "allow") disabledTools.delete(vibeToolName);
|
|
31874
|
+
continue;
|
|
31875
|
+
}
|
|
31876
|
+
if (action === "ask") {
|
|
31877
|
+
logger?.warn(`Vibe permissions do not support pattern-level "ask" rules. Skipping ${category}: ${pattern}`);
|
|
31878
|
+
continue;
|
|
31879
|
+
}
|
|
31880
|
+
if (action === "allow") allow.add(pattern);
|
|
31881
|
+
else deny.add(pattern);
|
|
31882
|
+
}
|
|
31883
|
+
delete nextTool.allow;
|
|
31884
|
+
delete nextTool.deny;
|
|
31885
|
+
if (allow.size > 0) nextTool.allowlist = [...allow].toSorted();
|
|
31886
|
+
if (deny.size > 0) nextTool.denylist = [...deny].toSorted();
|
|
31887
|
+
tools[vibeToolName] = nextTool;
|
|
31888
|
+
}
|
|
31889
|
+
/**
|
|
31890
|
+
* `enabled_tools` is an exclusive allowlist and therefore only ever
|
|
31891
|
+
* rulesync-authored explicitly, through `vibe.enabled_tools`. When the
|
|
31892
|
+
* override declares it (even empty), rulesync owns the whole list; when it
|
|
31893
|
+
* does not, the on-disk list survives minus the entries the caller's cleanup
|
|
31894
|
+
* loop removed for rulesync-configured tools (which also migrates away the
|
|
31895
|
+
* exclusive entries earlier rulesync versions wrote for allow rules).
|
|
31896
|
+
*/
|
|
31897
|
+
function resolveEnabledToolsPatch({ vibeOverride, enabledTools }) {
|
|
31898
|
+
if (vibeOverride?.enabled_tools !== void 0) {
|
|
31899
|
+
const declared = [...new Set(toStringArray(vibeOverride.enabled_tools))];
|
|
31900
|
+
return declared.length > 0 ? declared.toSorted() : void 0;
|
|
31901
|
+
}
|
|
31902
|
+
return enabledTools.size > 0 ? [...enabledTools].toSorted() : void 0;
|
|
31903
|
+
}
|
|
31410
31904
|
function parseVibeConfig(fileContent) {
|
|
31411
31905
|
const parsed = smolToml.parse(fileContent || smolToml.stringify({}));
|
|
31412
31906
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
@@ -31415,6 +31909,17 @@ function parseVibeConfig(fileContent) {
|
|
|
31415
31909
|
function toVibeToolName(category) {
|
|
31416
31910
|
return CANONICAL_TO_VIBE_TOOL_NAMES[category] ?? category;
|
|
31417
31911
|
}
|
|
31912
|
+
/**
|
|
31913
|
+
* Whether a canonical category has a Vibe builtin tool to land on. Categories
|
|
31914
|
+
* without one (e.g. `glob`, `notebookedit`) must not emit a
|
|
31915
|
+
* `[tools.<category>]` table: Vibe has no such tool, so a `deny` authored
|
|
31916
|
+
* there would look applied while being silently inert — the dangerous
|
|
31917
|
+
* direction. Unlike `agent` → `task` there is no correct name to rename to,
|
|
31918
|
+
* so the category is skipped with a warning (the grokcli adapter's pattern).
|
|
31919
|
+
*/
|
|
31920
|
+
function hasVibeToolName(category) {
|
|
31921
|
+
return Object.hasOwn(CANONICAL_TO_VIBE_TOOL_NAMES, category);
|
|
31922
|
+
}
|
|
31418
31923
|
function toCanonicalToolName$1(vibeToolName) {
|
|
31419
31924
|
return VIBE_TO_CANONICAL_TOOL_NAMES[vibeToolName] ?? vibeToolName;
|
|
31420
31925
|
}
|
|
@@ -31432,7 +31937,7 @@ function toStringArray(value) {
|
|
|
31432
31937
|
function applyWildcardPermission({ action, toolConfig }) {
|
|
31433
31938
|
if (action === "allow") toolConfig.permission = "always";
|
|
31434
31939
|
else if (action === "ask") toolConfig.permission = "ask";
|
|
31435
|
-
else
|
|
31940
|
+
else toolConfig.permission = "never";
|
|
31436
31941
|
}
|
|
31437
31942
|
function fromVibePermission(value) {
|
|
31438
31943
|
if (value === "always" || value === "allow") return "allow";
|
|
@@ -31451,11 +31956,29 @@ function ensurePermission(permission, category) {
|
|
|
31451
31956
|
const WARP_GLOBAL_ONLY_MESSAGE = "Warp permissions are global-only; use --global to sync Warp's settings.toml";
|
|
31452
31957
|
const ALLOWLIST_KEY = "agent_mode_command_execution_allowlist";
|
|
31453
31958
|
const DENYLIST_KEY = "agent_mode_command_execution_denylist";
|
|
31959
|
+
const EXECUTION_PROFILES_KEY = "execution_profiles";
|
|
31960
|
+
const DEFAULT_PROFILE_KEY = "default";
|
|
31961
|
+
const PROFILE_ALLOWLIST_KEY = "command_allowlist";
|
|
31962
|
+
const PROFILE_DENYLIST_KEY = "command_denylist";
|
|
31454
31963
|
const WARP_OVERRIDE_KEYS = [
|
|
31455
31964
|
"agent_mode_coding_permissions",
|
|
31456
31965
|
"agent_mode_coding_file_read_allowlist",
|
|
31457
31966
|
"agent_mode_execute_readonly_commands"
|
|
31458
31967
|
];
|
|
31968
|
+
const WARP_EXECUTION_PROFILE_OVERRIDE_KEY = "execution_profile";
|
|
31969
|
+
const WARP_EXECUTION_PROFILE_KEYS = [
|
|
31970
|
+
"read_files",
|
|
31971
|
+
"apply_code_diffs",
|
|
31972
|
+
"execute_commands",
|
|
31973
|
+
"mcp_permissions",
|
|
31974
|
+
"write_to_pty",
|
|
31975
|
+
"ask_user_question",
|
|
31976
|
+
"run_agents",
|
|
31977
|
+
"computer_use",
|
|
31978
|
+
"directory_allowlist",
|
|
31979
|
+
"mcp_allowlist",
|
|
31980
|
+
"mcp_denylist"
|
|
31981
|
+
];
|
|
31459
31982
|
/**
|
|
31460
31983
|
* Warp's `settings.toml` lives in a different directory per platform (Stable
|
|
31461
31984
|
* channel). The home directory is resolved by the processor through
|
|
@@ -31478,11 +32001,21 @@ function warpSettingsDir() {
|
|
|
31478
32001
|
/**
|
|
31479
32002
|
* Permissions adapter for Warp.
|
|
31480
32003
|
*
|
|
31481
|
-
* Warp gates **shell command** execution through two regex arrays
|
|
31482
|
-
*
|
|
31483
|
-
* - `
|
|
31484
|
-
*
|
|
31485
|
-
*
|
|
32004
|
+
* Warp gates **shell command** execution through two regex arrays in the
|
|
32005
|
+
* global user `settings.toml`. Since file-backed execution profiles went
|
|
32006
|
+
* Stable (2026-07-28) the authoritative surface is the `default` record of the
|
|
32007
|
+
* `[agents.execution_profiles.<id>]` collection:
|
|
32008
|
+
* - `command_allowlist` — commands that auto-execute.
|
|
32009
|
+
* - `command_denylist` — commands that always require permission (the denylist
|
|
32010
|
+
* wins over the allowlist).
|
|
32011
|
+
*
|
|
32012
|
+
* The legacy `[agents.profiles]` keys
|
|
32013
|
+
* (`agent_mode_command_execution_allowlist` / `denylist`) are consumed only
|
|
32014
|
+
* once by Warp's one-shot migration and are ignored afterwards. Both surfaces
|
|
32015
|
+
* are written: the legacy block keeps un-migrated installs and old clients
|
|
32016
|
+
* working, and the `default` execution profile (merged in place, only when the
|
|
32017
|
+
* collection already exists) keeps migrated installs enforcing the lists.
|
|
32018
|
+
* Import prefers the execution profile and falls back to the legacy keys.
|
|
31486
32019
|
*
|
|
31487
32020
|
* This surface is **global only** — there is no project-scoped Warp permissions
|
|
31488
32021
|
* file. rulesync's canonical `permission.bash` patterns map directly (`allow` →
|
|
@@ -31500,7 +32033,14 @@ function warpSettingsDir() {
|
|
|
31500
32033
|
* through the `warp` override namespace (see `WarpPermissionsOverrideSchema`):
|
|
31501
32034
|
* on **import** they are lifted from `settings.toml` into the override, and on
|
|
31502
32035
|
* **export** they are merged back into `[agents.profiles]` (the override wins).
|
|
31503
|
-
*
|
|
32036
|
+
*
|
|
32037
|
+
* On migrated installs those legacy keys are inert; their execution-profile
|
|
32038
|
+
* counterparts (`read_files` / `apply_code_diffs` / `execute_commands`
|
|
32039
|
+
* action permissions, `directory_allowlist`, `mcp_allowlist` /
|
|
32040
|
+
* `mcp_denylist`) are authored through the nested `warp.execution_profile`
|
|
32041
|
+
* override, merged into the `default` record of
|
|
32042
|
+
* `[agents.execution_profiles.<id>]` under the same collection-exists guard as
|
|
32043
|
+
* the command lists, and lifted back from the `default` profile on import.
|
|
31504
32044
|
*
|
|
31505
32045
|
* The `settings.toml` file holds all of Warp's settings, so the
|
|
31506
32046
|
* `[agents.profiles]` block is merged in place and the file is never deleted.
|
|
@@ -31553,7 +32093,11 @@ var WarpPermissions = class WarpPermissions extends ToolPermissions {
|
|
|
31553
32093
|
const agents = isRecord(settings.agents) ? { ...settings.agents } : {};
|
|
31554
32094
|
const profiles = isRecord(agents.profiles) ? { ...agents.profiles } : {};
|
|
31555
32095
|
const override = config.warp;
|
|
31556
|
-
|
|
32096
|
+
const executionProfileOverride = isRecord(override) && isRecord(override[WARP_EXECUTION_PROFILE_OVERRIDE_KEY]) ? override[WARP_EXECUTION_PROFILE_OVERRIDE_KEY] : void 0;
|
|
32097
|
+
if (isRecord(override)) {
|
|
32098
|
+
const { [WARP_EXECUTION_PROFILE_OVERRIDE_KEY]: _executionProfile, ...legacyOverride } = override;
|
|
32099
|
+
Object.assign(profiles, legacyOverride);
|
|
32100
|
+
}
|
|
31557
32101
|
const mergedAllow = uniq(allow.toSorted());
|
|
31558
32102
|
const mergedDeny = uniq(deny.toSorted());
|
|
31559
32103
|
if (mergedAllow.length > 0) profiles[ALLOWLIST_KEY] = mergedAllow;
|
|
@@ -31561,6 +32105,13 @@ var WarpPermissions = class WarpPermissions extends ToolPermissions {
|
|
|
31561
32105
|
if (mergedDeny.length > 0) profiles[DENYLIST_KEY] = mergedDeny;
|
|
31562
32106
|
else delete profiles[DENYLIST_KEY];
|
|
31563
32107
|
agents.profiles = profiles;
|
|
32108
|
+
mergeIntoDefaultExecutionProfile({
|
|
32109
|
+
agents,
|
|
32110
|
+
mergedAllow,
|
|
32111
|
+
mergedDeny,
|
|
32112
|
+
executionProfileOverride,
|
|
32113
|
+
logger
|
|
32114
|
+
});
|
|
31564
32115
|
settings.agents = agents;
|
|
31565
32116
|
return new WarpPermissions({
|
|
31566
32117
|
outputRoot,
|
|
@@ -31580,12 +32131,19 @@ var WarpPermissions = class WarpPermissions extends ToolPermissions {
|
|
|
31580
32131
|
}
|
|
31581
32132
|
const agents = isRecord(settings.agents) ? settings.agents : {};
|
|
31582
32133
|
const profiles = isRecord(agents.profiles) ? agents.profiles : {};
|
|
32134
|
+
const executionProfiles = isRecord(agents[EXECUTION_PROFILES_KEY]) ? agents[EXECUTION_PROFILES_KEY] : void 0;
|
|
32135
|
+
const defaultProfile = executionProfiles && isRecord(executionProfiles[DEFAULT_PROFILE_KEY]) ? executionProfiles[DEFAULT_PROFILE_KEY] : void 0;
|
|
31583
32136
|
const config = convertWarpToRulesyncPermissions({
|
|
31584
|
-
allow: isStringArray$1(profiles[ALLOWLIST_KEY]) ? profiles[ALLOWLIST_KEY] : [],
|
|
31585
|
-
deny: isStringArray$1(profiles[DENYLIST_KEY]) ? profiles[DENYLIST_KEY] : []
|
|
32137
|
+
allow: defaultProfile ? isStringArray$1(defaultProfile[PROFILE_ALLOWLIST_KEY]) ? defaultProfile[PROFILE_ALLOWLIST_KEY] : [] : isStringArray$1(profiles[ALLOWLIST_KEY]) ? profiles[ALLOWLIST_KEY] : [],
|
|
32138
|
+
deny: defaultProfile ? isStringArray$1(defaultProfile[PROFILE_DENYLIST_KEY]) ? defaultProfile[PROFILE_DENYLIST_KEY] : [] : isStringArray$1(profiles[DENYLIST_KEY]) ? profiles[DENYLIST_KEY] : []
|
|
31586
32139
|
});
|
|
31587
32140
|
const warpOverride = {};
|
|
31588
32141
|
for (const key of WARP_OVERRIDE_KEYS) if (profiles[key] !== void 0) warpOverride[key] = profiles[key];
|
|
32142
|
+
if (defaultProfile) {
|
|
32143
|
+
const executionProfileOverride = {};
|
|
32144
|
+
for (const key of WARP_EXECUTION_PROFILE_KEYS) if (defaultProfile[key] !== void 0) executionProfileOverride[key] = defaultProfile[key];
|
|
32145
|
+
if (Object.keys(executionProfileOverride).length > 0) warpOverride[WARP_EXECUTION_PROFILE_OVERRIDE_KEY] = executionProfileOverride;
|
|
32146
|
+
}
|
|
31589
32147
|
const result = { ...config };
|
|
31590
32148
|
if (Object.keys(warpOverride).length > 0) result.warp = warpOverride;
|
|
31591
32149
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
|
|
@@ -31608,6 +32166,36 @@ var WarpPermissions = class WarpPermissions extends ToolPermissions {
|
|
|
31608
32166
|
}
|
|
31609
32167
|
};
|
|
31610
32168
|
/**
|
|
32169
|
+
* On migrated installs the legacy `[agents.profiles]` keys are inert: runtime
|
|
32170
|
+
* enforcement reads the active `[agents.execution_profiles.<id>]` record.
|
|
32171
|
+
* Merge the command lists (and the `warp.execution_profile` override's
|
|
32172
|
+
* autonomy keys) into the `default` profile in place so every other profile
|
|
32173
|
+
* key and profile ID survives. When the collection does not exist yet the
|
|
32174
|
+
* install is un-migrated — the legacy keys are still live there, and creating
|
|
32175
|
+
* the collection ourselves would mark Warp's one-shot migration complete early
|
|
32176
|
+
* and strand the user's other legacy settings (file-read allowlist, preferred
|
|
32177
|
+
* model), so it is deliberately left unset and the override is skipped with a
|
|
32178
|
+
* warning instead.
|
|
32179
|
+
*/
|
|
32180
|
+
function mergeIntoDefaultExecutionProfile({ agents, mergedAllow, mergedDeny, executionProfileOverride, logger }) {
|
|
32181
|
+
const hasOverrideKeys = executionProfileOverride !== void 0 && Object.keys(executionProfileOverride).length > 0;
|
|
32182
|
+
if (!isRecord(agents[EXECUTION_PROFILES_KEY])) {
|
|
32183
|
+
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.");
|
|
32184
|
+
return;
|
|
32185
|
+
}
|
|
32186
|
+
const executionProfiles = { ...agents[EXECUTION_PROFILES_KEY] };
|
|
32187
|
+
const defaultProfile = isRecord(executionProfiles[DEFAULT_PROFILE_KEY]) ? { ...executionProfiles[DEFAULT_PROFILE_KEY] } : {};
|
|
32188
|
+
if (executionProfileOverride) Object.assign(defaultProfile, executionProfileOverride);
|
|
32189
|
+
if (mergedAllow.length > 0) defaultProfile[PROFILE_ALLOWLIST_KEY] = mergedAllow;
|
|
32190
|
+
else delete defaultProfile[PROFILE_ALLOWLIST_KEY];
|
|
32191
|
+
if (mergedDeny.length > 0) defaultProfile[PROFILE_DENYLIST_KEY] = mergedDeny;
|
|
32192
|
+
else delete defaultProfile[PROFILE_DENYLIST_KEY];
|
|
32193
|
+
executionProfiles[DEFAULT_PROFILE_KEY] = defaultProfile;
|
|
32194
|
+
agents[EXECUTION_PROFILES_KEY] = executionProfiles;
|
|
32195
|
+
const otherProfileIds = Object.keys(executionProfiles).filter((id) => id !== DEFAULT_PROFILE_KEY);
|
|
32196
|
+
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.`);
|
|
32197
|
+
}
|
|
32198
|
+
/**
|
|
31611
32199
|
* Convert rulesync permissions config to Warp command allow/deny regex lists.
|
|
31612
32200
|
* Only the `bash` category maps; `ask` rules and non-`bash` categories are
|
|
31613
32201
|
* dropped (the latter with a warning when they carry `deny` rules).
|
|
@@ -31624,10 +32212,7 @@ function convertRulesyncToWarpPermissions({ config, logger }) {
|
|
|
31624
32212
|
case "allow":
|
|
31625
32213
|
allow.push(pattern);
|
|
31626
32214
|
break;
|
|
31627
|
-
case "deny":
|
|
31628
|
-
deny.push(pattern);
|
|
31629
|
-
break;
|
|
31630
|
-
case "ask": break;
|
|
32215
|
+
case "deny": deny.push(pattern);
|
|
31631
32216
|
}
|
|
31632
32217
|
}
|
|
31633
32218
|
return {
|
|
@@ -32082,7 +32667,7 @@ const toolPermissionsFactories = /* @__PURE__ */ new Map([
|
|
|
32082
32667
|
["rovodev", {
|
|
32083
32668
|
class: RovodevPermissions,
|
|
32084
32669
|
meta: {
|
|
32085
|
-
supportsProject:
|
|
32670
|
+
supportsProject: true,
|
|
32086
32671
|
supportsGlobal: true,
|
|
32087
32672
|
supportsImport: true
|
|
32088
32673
|
}
|
|
@@ -33279,6 +33864,38 @@ var AugmentcodeSkill = class AugmentcodeSkill extends ToolSkill {
|
|
|
33279
33864
|
}
|
|
33280
33865
|
};
|
|
33281
33866
|
//#endregion
|
|
33867
|
+
//#region src/features/rules/nested-scan-exclusions.ts
|
|
33868
|
+
/**
|
|
33869
|
+
* Shared exclusion lists for the nested rule-file scans (`AGENTS.md`,
|
|
33870
|
+
* `REASONIX.md`), which walk the whole project tree on import rather than a
|
|
33871
|
+
* rulesync-owned directory.
|
|
33872
|
+
*/
|
|
33873
|
+
/**
|
|
33874
|
+
* Dependency trees never scanned for nested rule files, at any depth. A rule
|
|
33875
|
+
* file there describes somebody else's project, and neither name is ever a
|
|
33876
|
+
* package name. Hidden directories are excluded separately, because a rule
|
|
33877
|
+
* file inside one is another tool's generated output (rulesync writes several
|
|
33878
|
+
* itself).
|
|
33879
|
+
*/
|
|
33880
|
+
const NESTED_SCAN_EXCLUDED_DIRS_ANY_DEPTH = ["node_modules", "__pycache__"];
|
|
33881
|
+
/**
|
|
33882
|
+
* Build, vendoring and scratch directories, excluded at the **project root
|
|
33883
|
+
* only**. A top-level `build/` is a build directory; `packages/build/` is a
|
|
33884
|
+
* package, and dropping it silently would lose a real subproject.
|
|
33885
|
+
*/
|
|
33886
|
+
const NESTED_SCAN_EXCLUDED_ROOT_DIRS = [
|
|
33887
|
+
"vendor",
|
|
33888
|
+
"third_party",
|
|
33889
|
+
"dist",
|
|
33890
|
+
"build",
|
|
33891
|
+
"out",
|
|
33892
|
+
"target",
|
|
33893
|
+
"coverage",
|
|
33894
|
+
"tmp",
|
|
33895
|
+
"temp",
|
|
33896
|
+
"venv"
|
|
33897
|
+
];
|
|
33898
|
+
//#endregion
|
|
33282
33899
|
//#region src/features/skills/claudecode-skill.ts
|
|
33283
33900
|
const ClaudecodeSkillFrontmatterSchema = z.looseObject({
|
|
33284
33901
|
name: z.string(),
|
|
@@ -33364,6 +33981,41 @@ var ClaudecodeSkill = class extends ToolSkill {
|
|
|
33364
33981
|
alternativeSkillRoots: [CLAUDECODE_SCHEDULED_TASKS_DIR_PATH]
|
|
33365
33982
|
};
|
|
33366
33983
|
}
|
|
33984
|
+
/**
|
|
33985
|
+
* Claude Code v2.1.178+ also loads skills from **nested** `.claude/skills/`
|
|
33986
|
+
* directories below the working directory (a skill in
|
|
33987
|
+
* `apps/web/.claude/skills/` becomes available when working on files
|
|
33988
|
+
* there). Discover those directories so `rulesync import` sees them —
|
|
33989
|
+
* import-only and lenient, like every configured root; the project-root
|
|
33990
|
+
* `.claude/skills/` stays the sole generation target. The scan reuses the
|
|
33991
|
+
* nested-rule-file exclusions (dependency trees at any depth, build/vendor
|
|
33992
|
+
* dirs at the root, hidden dirs other than the `.claude` being matched) and
|
|
33993
|
+
* never runs in global mode. On a name clash with a root skill, the root
|
|
33994
|
+
* one wins the import (Claude Code itself keeps both under a
|
|
33995
|
+
* directory-qualified name, which rulesync's flat skill namespace cannot
|
|
33996
|
+
* express).
|
|
33997
|
+
*
|
|
33998
|
+
* @see https://code.claude.com/docs/en/skills
|
|
33999
|
+
*/
|
|
34000
|
+
static async getConfiguredImportRoots({ outputRoot, global = false }) {
|
|
34001
|
+
if (global) return [];
|
|
34002
|
+
const root = toPosixPath(outputRoot);
|
|
34003
|
+
return filterOutPathsInGitIgnoredDirectories({
|
|
34004
|
+
rootDir: outputRoot,
|
|
34005
|
+
filePaths: await findFilesByGlobs([`${root}/*/**/${toPosixPath(CLAUDECODE_SKILLS_DIR_PATH)}`], {
|
|
34006
|
+
type: "dir",
|
|
34007
|
+
followSymbolicLinks: false,
|
|
34008
|
+
ignore: [
|
|
34009
|
+
`${root}/**/.*/**/${toPosixPath(CLAUDECODE_SKILLS_DIR_PATH)}`,
|
|
34010
|
+
...NESTED_SCAN_EXCLUDED_DIRS_ANY_DEPTH.map((dir) => `${root}/**/${dir}/**`),
|
|
34011
|
+
...NESTED_SCAN_EXCLUDED_ROOT_DIRS.map((dir) => `${root}/${dir}/**`)
|
|
34012
|
+
]
|
|
34013
|
+
})
|
|
34014
|
+
}).toSorted().map((dirPath) => ({
|
|
34015
|
+
outputRoot,
|
|
34016
|
+
relativeDirPath: relative(outputRoot, dirPath)
|
|
34017
|
+
}));
|
|
34018
|
+
}
|
|
33367
34019
|
getFrontmatter() {
|
|
33368
34020
|
return ClaudecodeSkillFrontmatterSchema.parse(this.requireMainFileFrontmatter());
|
|
33369
34021
|
}
|
|
@@ -36762,7 +37414,8 @@ var TaktSkill = class TaktSkill extends ToolSkill {
|
|
|
36762
37414
|
getDirPath() {
|
|
36763
37415
|
const fullPath = join(this.outputRoot, this.relativeDirPath);
|
|
36764
37416
|
const resolvedFull = resolve(fullPath);
|
|
36765
|
-
const
|
|
37417
|
+
const resolvedBase = resolve(this.outputRoot);
|
|
37418
|
+
const rel = relative(resolvedBase, resolvedFull);
|
|
36766
37419
|
if (rel.startsWith("..") || path.isAbsolute(rel)) throw new Error(`Path traversal detected: Final path escapes outputRoot. outputRoot="${this.outputRoot}", relativeDirPath="${this.relativeDirPath}"`);
|
|
36767
37420
|
return fullPath;
|
|
36768
37421
|
}
|
|
@@ -37312,6 +37965,23 @@ var ZedSkill = class ZedSkill extends ToolSkill {
|
|
|
37312
37965
|
}
|
|
37313
37966
|
};
|
|
37314
37967
|
//#endregion
|
|
37968
|
+
//#region src/features/skills/zoocode-skill.ts
|
|
37969
|
+
/**
|
|
37970
|
+
* Skill generator for **Zoo Code** (the community continuation of Roo Code).
|
|
37971
|
+
* Zoo Code keeps Roo's skills layout (`.roo/skills/` project,
|
|
37972
|
+
* `~/.roo/skills/` global) and the `roo:` frontmatter section (`modeSlugs`),
|
|
37973
|
+
* so this target reuses {@link RooSkill} verbatim and only narrows the
|
|
37974
|
+
* targeting.
|
|
37975
|
+
*
|
|
37976
|
+
* @see https://docs.zoocode.dev/features/skills
|
|
37977
|
+
*/
|
|
37978
|
+
var ZoocodeSkill = class extends RooSkill {
|
|
37979
|
+
static isTargetedByRulesyncSkill(rulesyncSkill) {
|
|
37980
|
+
const targets = rulesyncSkill.getFrontmatter().targets;
|
|
37981
|
+
return targets.includes("*") || targets.includes("zoocode");
|
|
37982
|
+
}
|
|
37983
|
+
};
|
|
37984
|
+
//#endregion
|
|
37315
37985
|
//#region src/features/skills/skills-processor.ts
|
|
37316
37986
|
const SkillsProcessorToolTargetSchema = z.enum(skillsProcessorToolTargetTuple);
|
|
37317
37987
|
/**
|
|
@@ -37332,7 +38002,8 @@ const toolSkillFactories = /* @__PURE__ */ new Map([
|
|
|
37332
38002
|
meta: {
|
|
37333
38003
|
supportsProject: true,
|
|
37334
38004
|
supportsSimulated: false,
|
|
37335
|
-
supportsGlobal: true
|
|
38005
|
+
supportsGlobal: true,
|
|
38006
|
+
lenientImport: true
|
|
37336
38007
|
}
|
|
37337
38008
|
}],
|
|
37338
38009
|
["aiassistant", {
|
|
@@ -37583,6 +38254,14 @@ const toolSkillFactories = /* @__PURE__ */ new Map([
|
|
|
37583
38254
|
supportsGlobal: true
|
|
37584
38255
|
}
|
|
37585
38256
|
}],
|
|
38257
|
+
["zoocode", {
|
|
38258
|
+
class: ZoocodeSkill,
|
|
38259
|
+
meta: {
|
|
38260
|
+
supportsProject: true,
|
|
38261
|
+
supportsSimulated: false,
|
|
38262
|
+
supportsGlobal: true
|
|
38263
|
+
}
|
|
38264
|
+
}],
|
|
37586
38265
|
["rovodev", {
|
|
37587
38266
|
class: RovodevSkill,
|
|
37588
38267
|
meta: {
|
|
@@ -37745,7 +38424,7 @@ var SkillsProcessor = class extends DirFeatureProcessor {
|
|
|
37745
38424
|
for (const root of roots) {
|
|
37746
38425
|
const rootOutputRoot = typeof root === "string" ? this.outputRoot : root.outputRoot;
|
|
37747
38426
|
const relativeDirPath = typeof root === "string" ? root : root.relativeDirPath;
|
|
37748
|
-
const
|
|
38427
|
+
const isLenientRoot = configuredRootPaths.has(relativeDirPath) || factory.meta.lenientImport === true || isAgentSkillsInteropRoot(relativeDirPath);
|
|
37749
38428
|
const skillsDirPath = join(rootOutputRoot, relativeDirPath);
|
|
37750
38429
|
if (!await directoryExists(skillsDirPath)) continue;
|
|
37751
38430
|
const dirPaths = await findFilesByGlobs(join(skillsDirPath, "*"), { type: "dir" });
|
|
@@ -37769,7 +38448,7 @@ var SkillsProcessor = class extends DirFeatureProcessor {
|
|
|
37769
38448
|
global: this.global
|
|
37770
38449
|
});
|
|
37771
38450
|
} catch (error) {
|
|
37772
|
-
if (!
|
|
38451
|
+
if (!isLenientRoot) throw error;
|
|
37773
38452
|
this.logger.warn(`Skipping ${join(relativeDirPath, dirName)}: ${formatError(error)}`);
|
|
37774
38453
|
return null;
|
|
37775
38454
|
}
|
|
@@ -37784,12 +38463,20 @@ var SkillsProcessor = class extends DirFeatureProcessor {
|
|
|
37784
38463
|
const fromFlatFile = factory.class.fromFlatFile;
|
|
37785
38464
|
const directoryStems = new Set(ownedDirNames);
|
|
37786
38465
|
const flatFilePaths = (await findFilesByGlobs(join(skillsDirPath, "*.md"), { type: "file" })).filter((filePath) => !directoryStems.has(basename(filePath, ".md")));
|
|
37787
|
-
const flatSkills = await Promise.all(flatFilePaths.map((filePath) =>
|
|
37788
|
-
|
|
37789
|
-
|
|
37790
|
-
|
|
37791
|
-
|
|
37792
|
-
|
|
38466
|
+
const flatSkills = (await Promise.all(flatFilePaths.map(async (filePath) => {
|
|
38467
|
+
try {
|
|
38468
|
+
return await fromFlatFile({
|
|
38469
|
+
outputRoot: rootOutputRoot,
|
|
38470
|
+
relativeDirPath,
|
|
38471
|
+
relativeFilePath: basename(filePath),
|
|
38472
|
+
global: this.global
|
|
38473
|
+
});
|
|
38474
|
+
} catch (error) {
|
|
38475
|
+
if (!isLenientRoot) throw error;
|
|
38476
|
+
this.logger.warn(`Skipping ${join(relativeDirPath, basename(filePath))}: ${formatError(error)}`);
|
|
38477
|
+
return null;
|
|
38478
|
+
}
|
|
38479
|
+
}))).filter((skill) => skill !== null);
|
|
37793
38480
|
for (const skill of flatSkills) {
|
|
37794
38481
|
const skillName = skill.getImportIdentity();
|
|
37795
38482
|
if (seenSkillNames.has(skillName)) continue;
|
|
@@ -37862,14 +38549,15 @@ var SkillsProcessor = class extends DirFeatureProcessor {
|
|
|
37862
38549
|
*/
|
|
37863
38550
|
static getSimulatedConventionSection({ skillList }) {
|
|
37864
38551
|
if (!skillList || skillList.length === 0) return "";
|
|
38552
|
+
const skillListWithAtPrefix = skillList.map((skill) => ({
|
|
38553
|
+
...skill,
|
|
38554
|
+
path: `@${skill.path}`
|
|
38555
|
+
}));
|
|
37865
38556
|
return `## Simulated Skills
|
|
37866
38557
|
|
|
37867
38558
|
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.
|
|
37868
38559
|
|
|
37869
|
-
${encode({ skillList:
|
|
37870
|
-
...skill,
|
|
37871
|
-
path: `@${skill.path}`
|
|
37872
|
-
})) })}`;
|
|
38560
|
+
${encode({ skillList: skillListWithAtPrefix })}`;
|
|
37873
38561
|
}
|
|
37874
38562
|
/**
|
|
37875
38563
|
* Return the tool targets that this processor supports in global mode
|
|
@@ -40473,7 +41161,8 @@ var HermesagentSubagent = class HermesagentSubagent extends ToolSubagent {
|
|
|
40473
41161
|
return getHermesagentSharedConfigWritePaths();
|
|
40474
41162
|
}
|
|
40475
41163
|
static getSettablePathsForRulesyncSubagent(rulesyncSubagent) {
|
|
40476
|
-
|
|
41164
|
+
const slug = subagentSlug(rulesyncSubagent.getRelativePathFromCwd());
|
|
41165
|
+
return [join(HERMESAGENT_RULESYNC_SUBAGENTS_DIR_PATH, `${slug}.json`)];
|
|
40477
41166
|
}
|
|
40478
41167
|
toRulesyncSubagent() {
|
|
40479
41168
|
const slug = basename(this.getRelativeFilePath(), ".json");
|
|
@@ -41708,7 +42397,7 @@ function stringifyRooModes(modes) {
|
|
|
41708
42397
|
*
|
|
41709
42398
|
* @see https://roocodeinc.github.io/Roo-Code/features/custom-modes
|
|
41710
42399
|
*/
|
|
41711
|
-
var RooSubagent = class
|
|
42400
|
+
var RooSubagent = class extends ToolSubagent {
|
|
41712
42401
|
modes;
|
|
41713
42402
|
constructor({ modes, ...rest }) {
|
|
41714
42403
|
if (rest.validate !== false) {
|
|
@@ -41764,7 +42453,7 @@ var RooSubagent = class RooSubagent extends ToolSubagent {
|
|
|
41764
42453
|
const mode = this.toRooMode(rulesyncSubagent);
|
|
41765
42454
|
bySlug.set(mode.slug, mode);
|
|
41766
42455
|
}
|
|
41767
|
-
return new
|
|
42456
|
+
return new this({
|
|
41768
42457
|
outputRoot,
|
|
41769
42458
|
relativeDirPath: this.getSettablePaths().relativeDirPath,
|
|
41770
42459
|
relativeFilePath: ROO_MODES_FILE_NAME,
|
|
@@ -41843,7 +42532,7 @@ var RooSubagent = class RooSubagent extends ToolSubagent {
|
|
|
41843
42532
|
}
|
|
41844
42533
|
const result = RooModesFileSchema.safeParse(parsed ?? { customModes: [] });
|
|
41845
42534
|
if (!result.success) throw new Error(`Invalid .roomodes in ${filePath}: ${formatError(result.error)}`);
|
|
41846
|
-
return new
|
|
42535
|
+
return new this({
|
|
41847
42536
|
outputRoot,
|
|
41848
42537
|
relativeDirPath: paths.relativeDirPath,
|
|
41849
42538
|
relativeFilePath: basename(relativeFilePath),
|
|
@@ -41852,7 +42541,7 @@ var RooSubagent = class RooSubagent extends ToolSubagent {
|
|
|
41852
42541
|
});
|
|
41853
42542
|
}
|
|
41854
42543
|
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
41855
|
-
return new
|
|
42544
|
+
return new this({
|
|
41856
42545
|
outputRoot,
|
|
41857
42546
|
relativeDirPath,
|
|
41858
42547
|
relativeFilePath,
|
|
@@ -42098,6 +42787,60 @@ var VibeSubagent = class VibeSubagent extends ToolSubagent {
|
|
|
42098
42787
|
}
|
|
42099
42788
|
};
|
|
42100
42789
|
//#endregion
|
|
42790
|
+
//#region src/features/subagents/zoocode-subagent.ts
|
|
42791
|
+
/**
|
|
42792
|
+
* Subagent (custom-mode) generator for **Zoo Code**, the community
|
|
42793
|
+
* continuation of Roo Code. Zoo Code keeps the aggregated `.roomodes` file and
|
|
42794
|
+
* Roo's mode schema, and the shared mode fields keep riding the `roo:`
|
|
42795
|
+
* frontmatter section — one rulesync source must not produce two different
|
|
42796
|
+
* spellings of the same field.
|
|
42797
|
+
*
|
|
42798
|
+
* The post-fork divergence is carried by the `zoocode:` section:
|
|
42799
|
+
* `allowedMcpServers` (Zoo Code v3.60.0+) — a per-mode MCP server allowlist.
|
|
42800
|
+
* "When omitted, all servers are available. When set, only the listed servers
|
|
42801
|
+
* are injected." On import the key is lifted back into `zoocode:` so it
|
|
42802
|
+
* round-trips (and never leaks into the `roo:` section, where the archived
|
|
42803
|
+
* Roo Code would not understand it).
|
|
42804
|
+
*
|
|
42805
|
+
* @see https://github.com/Zoo-Code-Org/Zoo-Code
|
|
42806
|
+
* @see https://docs.zoocode.dev/features/custom-modes
|
|
42807
|
+
*/
|
|
42808
|
+
var ZoocodeSubagent = class extends RooSubagent {
|
|
42809
|
+
static isTargetedByRulesyncSubagent(rulesyncSubagent) {
|
|
42810
|
+
return this.isTargetedByRulesyncSubagentDefault({
|
|
42811
|
+
rulesyncSubagent,
|
|
42812
|
+
toolTarget: "zoocode"
|
|
42813
|
+
});
|
|
42814
|
+
}
|
|
42815
|
+
static toRooMode(rulesyncSubagent) {
|
|
42816
|
+
const mode = super.toRooMode(rulesyncSubagent);
|
|
42817
|
+
const frontmatter = rulesyncSubagent.getFrontmatter();
|
|
42818
|
+
const zoocodeSection = isRecord(frontmatter.zoocode) ? frontmatter.zoocode : {};
|
|
42819
|
+
if (isStringArray$1(zoocodeSection.allowedMcpServers)) mode.allowedMcpServers = zoocodeSection.allowedMcpServers;
|
|
42820
|
+
return mode;
|
|
42821
|
+
}
|
|
42822
|
+
toRulesyncSubagents() {
|
|
42823
|
+
return super.toRulesyncSubagents().map((subagent) => {
|
|
42824
|
+
const frontmatter = subagent.getFrontmatter();
|
|
42825
|
+
const { allowedMcpServers, ...restRooSection } = isRecord(frontmatter.roo) ? { ...frontmatter.roo } : {};
|
|
42826
|
+
const rebuilt = {
|
|
42827
|
+
...frontmatter,
|
|
42828
|
+
targets: ["zoocode"],
|
|
42829
|
+
roo: restRooSection
|
|
42830
|
+
};
|
|
42831
|
+
if (isStringArray$1(allowedMcpServers)) rebuilt.zoocode = { allowedMcpServers };
|
|
42832
|
+
return new RulesyncSubagent({
|
|
42833
|
+
outputRoot: ".",
|
|
42834
|
+
frontmatter: rebuilt,
|
|
42835
|
+
body: subagent.getBody(),
|
|
42836
|
+
relativeDirPath: subagent.getRelativeDirPath(),
|
|
42837
|
+
relativeFilePath: subagent.getRelativeFilePath(),
|
|
42838
|
+
validate: true
|
|
42839
|
+
});
|
|
42840
|
+
});
|
|
42841
|
+
}
|
|
42842
|
+
};
|
|
42843
|
+
//#endregion
|
|
42101
42844
|
//#region src/features/subagents/subagents-processor.ts
|
|
42102
42845
|
const SubagentsProcessorToolTargetSchema = z.enum(subagentsProcessorToolTargetTuple);
|
|
42103
42846
|
/**
|
|
@@ -42337,6 +43080,14 @@ const toolSubagentFactories = /* @__PURE__ */ new Map([
|
|
|
42337
43080
|
filePattern: ".roomodes"
|
|
42338
43081
|
}
|
|
42339
43082
|
}],
|
|
43083
|
+
["zoocode", {
|
|
43084
|
+
class: ZoocodeSubagent,
|
|
43085
|
+
meta: {
|
|
43086
|
+
supportsSimulated: false,
|
|
43087
|
+
supportsGlobal: false,
|
|
43088
|
+
filePattern: ".roomodes"
|
|
43089
|
+
}
|
|
43090
|
+
}],
|
|
42340
43091
|
["rovodev", {
|
|
42341
43092
|
class: RovodevSubagent,
|
|
42342
43093
|
meta: {
|
|
@@ -42597,11 +43348,13 @@ For example, if the user instructs \`Call planner subagent to plan the refactori
|
|
|
42597
43348
|
//#region src/features/rules/tool-rule.ts
|
|
42598
43349
|
var ToolRule = class extends ToolFile {
|
|
42599
43350
|
root;
|
|
43351
|
+
localRoot;
|
|
42600
43352
|
description;
|
|
42601
43353
|
globs;
|
|
42602
|
-
constructor({ root = false, description, globs, ...rest }) {
|
|
43354
|
+
constructor({ root = false, localRoot = false, description, globs, ...rest }) {
|
|
42603
43355
|
super(rest);
|
|
42604
43356
|
this.root = root;
|
|
43357
|
+
this.localRoot = localRoot;
|
|
42605
43358
|
this.description = description;
|
|
42606
43359
|
this.globs = globs;
|
|
42607
43360
|
}
|
|
@@ -42684,6 +43437,38 @@ var ToolRule = class extends ToolFile {
|
|
|
42684
43437
|
isRoot() {
|
|
42685
43438
|
return this.root;
|
|
42686
43439
|
}
|
|
43440
|
+
isLocalRoot() {
|
|
43441
|
+
return this.localRoot;
|
|
43442
|
+
}
|
|
43443
|
+
/**
|
|
43444
|
+
* Convert a tool's separate personal local-root file (see
|
|
43445
|
+
* {@link ToolRuleParams.localRoot}) back to a canonical `localRoot: true`
|
|
43446
|
+
* rulesync rule. The rulesync file keeps the tool-side basename
|
|
43447
|
+
* (`CLAUDE.local.md`, `AGENTS.local.md`, ...), which the derived `.gitignore`
|
|
43448
|
+
* already covers via `.rulesync/rules/*.local.md`, so personal content stays
|
|
43449
|
+
* untracked after import.
|
|
43450
|
+
*
|
|
43451
|
+
* `targets` is scoped to the importing tool rather than `"*"`. A wildcard
|
|
43452
|
+
* would spread the personal content to every tool on the next generate —
|
|
43453
|
+
* including being appended into the committed root file of tools without a
|
|
43454
|
+
* separate local file — and a multi-target import would produce several
|
|
43455
|
+
* wildcard localRoot rules, which the per-target validation rejects.
|
|
43456
|
+
*/
|
|
43457
|
+
toLocalRootRulesyncRule({ targets }) {
|
|
43458
|
+
return new RulesyncRule({
|
|
43459
|
+
outputRoot: this.getOutputRoot(),
|
|
43460
|
+
relativeDirPath: RULESYNC_RULES_RELATIVE_DIR_PATH,
|
|
43461
|
+
relativeFilePath: this.getRelativeFilePath(),
|
|
43462
|
+
frontmatter: {
|
|
43463
|
+
root: false,
|
|
43464
|
+
localRoot: true,
|
|
43465
|
+
targets,
|
|
43466
|
+
globs: []
|
|
43467
|
+
},
|
|
43468
|
+
body: this.getFileContent(),
|
|
43469
|
+
validate: true
|
|
43470
|
+
});
|
|
43471
|
+
}
|
|
42687
43472
|
/**
|
|
42688
43473
|
* Whether this rule must be left out of the root rule's reference/MCP
|
|
42689
43474
|
* instruction listings even though it is a non-root survivor. Used by files
|
|
@@ -42716,31 +43501,6 @@ function buildToolPath(toolDir, subDir, excludeToolDir) {
|
|
|
42716
43501
|
}
|
|
42717
43502
|
//#endregion
|
|
42718
43503
|
//#region src/features/rules/agentsmd-rule.ts
|
|
42719
|
-
/**
|
|
42720
|
-
* Dependency trees never scanned for nested `AGENTS.md` files, at any depth. An
|
|
42721
|
-
* `AGENTS.md` there describes somebody else's project, and neither name is ever
|
|
42722
|
-
* a package name. Hidden directories are excluded separately, because an
|
|
42723
|
-
* `AGENTS.md` inside one is another tool's generated output (rulesync writes
|
|
42724
|
-
* several itself).
|
|
42725
|
-
*/
|
|
42726
|
-
const NESTED_SCAN_EXCLUDED_DIRS_ANY_DEPTH = ["node_modules", "__pycache__"];
|
|
42727
|
-
/**
|
|
42728
|
-
* Build, vendoring and scratch directories, excluded at the **project root
|
|
42729
|
-
* only**. A top-level `build/` is a build directory; `packages/build/` is a
|
|
42730
|
-
* package, and dropping it silently would lose a real subproject.
|
|
42731
|
-
*/
|
|
42732
|
-
const NESTED_SCAN_EXCLUDED_ROOT_DIRS = [
|
|
42733
|
-
"vendor",
|
|
42734
|
-
"third_party",
|
|
42735
|
-
"dist",
|
|
42736
|
-
"build",
|
|
42737
|
-
"out",
|
|
42738
|
-
"target",
|
|
42739
|
-
"coverage",
|
|
42740
|
-
"tmp",
|
|
42741
|
-
"temp",
|
|
42742
|
-
"venv"
|
|
42743
|
-
];
|
|
42744
43504
|
var AgentsMdRule = class AgentsMdRule extends ToolRule {
|
|
42745
43505
|
constructor({ fileContent, root, ...rest }) {
|
|
42746
43506
|
super({
|
|
@@ -42797,7 +43557,8 @@ var AgentsMdRule = class AgentsMdRule extends ToolRule {
|
|
|
42797
43557
|
const normalizedDirPath = relativeDirPath === void 0 ? "." : toPosixPath(relativeDirPath);
|
|
42798
43558
|
const isNested = relativeFilePath === "AGENTS.md" && normalizedDirPath !== "." && normalizedDirPath !== "" && !normalizedDirPath.startsWith(".");
|
|
42799
43559
|
const isRoot = !isNested && relativeFilePath === "AGENTS.md" && (normalizedDirPath === "." || normalizedDirPath === "");
|
|
42800
|
-
const
|
|
43560
|
+
const relativePath = isNested ? join(normalizedDirPath, relativeFilePath) : isRoot ? AGENTSMD_RULE_FILE_NAME : join(AGENTSMD_MEMORIES_DIR_PATH, relativeFilePath);
|
|
43561
|
+
const fileContent = await readFileContent(join(outputRoot, relativePath));
|
|
42801
43562
|
return new AgentsMdRule({
|
|
42802
43563
|
outputRoot,
|
|
42803
43564
|
relativeDirPath: isNested ? join(normalizedDirPath) : isRoot ? this.getSettablePaths().root.relativeDirPath : this.getSettablePaths().nonRoot.relativeDirPath,
|
|
@@ -43104,7 +43865,8 @@ var AntigravityCliRule = class AntigravityCliRule extends ToolRule {
|
|
|
43104
43865
|
});
|
|
43105
43866
|
}
|
|
43106
43867
|
if (!paths.nonRoot) throw new Error(`nonRoot path is not set for ${relativeFilePath}`);
|
|
43107
|
-
const
|
|
43868
|
+
const relativePath = join(paths.nonRoot.relativeDirPath, relativeFilePath);
|
|
43869
|
+
const fileContent = await readFileContent(join(outputRoot, relativePath));
|
|
43108
43870
|
return new AntigravityCliRule({
|
|
43109
43871
|
outputRoot,
|
|
43110
43872
|
relativeDirPath: paths.nonRoot.relativeDirPath,
|
|
@@ -43565,7 +44327,8 @@ var AugmentcodeLegacyRule = class AugmentcodeLegacyRule extends ToolRule {
|
|
|
43565
44327
|
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true }) {
|
|
43566
44328
|
const settablePaths = this.getSettablePaths();
|
|
43567
44329
|
const isRoot = relativeFilePath === settablePaths.root.relativeFilePath;
|
|
43568
|
-
const
|
|
44330
|
+
const relativePath = isRoot ? settablePaths.root.relativeFilePath : join(settablePaths.nonRoot.relativeDirPath, relativeFilePath);
|
|
44331
|
+
const fileContent = await readFileContent(join(outputRoot, relativePath));
|
|
43569
44332
|
return new AugmentcodeLegacyRule({
|
|
43570
44333
|
outputRoot,
|
|
43571
44334
|
relativeDirPath: isRoot ? settablePaths.root.relativeDirPath : settablePaths.nonRoot.relativeDirPath,
|
|
@@ -43789,7 +44552,8 @@ var ClaudecodeLegacyRule = class ClaudecodeLegacyRule extends ToolRule {
|
|
|
43789
44552
|
});
|
|
43790
44553
|
}
|
|
43791
44554
|
if (!paths.nonRoot) throw new Error(`nonRoot path is not set for ${relativeFilePath}`);
|
|
43792
|
-
const
|
|
44555
|
+
const relativePath = join(paths.nonRoot.relativeDirPath, relativeFilePath);
|
|
44556
|
+
const fileContent = await readFileContent(join(outputRoot, relativePath));
|
|
43793
44557
|
return new ClaudecodeLegacyRule({
|
|
43794
44558
|
outputRoot,
|
|
43795
44559
|
relativeDirPath: paths.nonRoot.relativeDirPath,
|
|
@@ -43910,7 +44674,8 @@ var ClaudecodeRule = class ClaudecodeRule extends ToolRule {
|
|
|
43910
44674
|
});
|
|
43911
44675
|
}
|
|
43912
44676
|
if (!paths.nonRoot) throw new Error(`nonRoot path is not set for ${relativeFilePath}`);
|
|
43913
|
-
const
|
|
44677
|
+
const relativePath = join(paths.nonRoot.relativeDirPath, relativeFilePath);
|
|
44678
|
+
const filePath = join(outputRoot, relativePath);
|
|
43914
44679
|
const { frontmatter, body: content } = parseFrontmatter(await readFileContent(filePath), filePath);
|
|
43915
44680
|
const result = ClaudecodeRuleFrontmatterSchema.safeParse(frontmatter);
|
|
43916
44681
|
if (!result.success) throw new Error(`Invalid frontmatter in ${filePath}: ${formatError(result.error)}`);
|
|
@@ -44297,7 +45062,8 @@ var CodexcliRule = class CodexcliRule extends ToolRule {
|
|
|
44297
45062
|
}
|
|
44298
45063
|
static async fromFile({ outputRoot = process.cwd(), relativeFilePath: _relativeFilePath, validate = true, global = false }) {
|
|
44299
45064
|
const { root } = this.getSettablePaths({ global });
|
|
44300
|
-
const
|
|
45065
|
+
const relativePath = join(root.relativeDirPath, root.relativeFilePath);
|
|
45066
|
+
const fileContent = await readFileContent(join(outputRoot, relativePath));
|
|
44301
45067
|
return new CodexcliRule({
|
|
44302
45068
|
outputRoot,
|
|
44303
45069
|
relativeDirPath: root.relativeDirPath,
|
|
@@ -44466,7 +45232,8 @@ var CopilotRule = class CopilotRule extends ToolRule {
|
|
|
44466
45232
|
}) : relativeFilePath === paths.root.relativeFilePath;
|
|
44467
45233
|
const resolvedRelativeDirPath = relativeDirPath ?? (isRoot ? paths.root.relativeDirPath : paths.nonRoot?.relativeDirPath ?? paths.root.relativeDirPath);
|
|
44468
45234
|
if (isRoot) {
|
|
44469
|
-
const
|
|
45235
|
+
const relativePath = join(paths.root.relativeDirPath, paths.root.relativeFilePath);
|
|
45236
|
+
const fileContent = await readFileContent(join(outputRoot, relativePath));
|
|
44470
45237
|
return new CopilotRule({
|
|
44471
45238
|
outputRoot,
|
|
44472
45239
|
relativeDirPath: paths.root.relativeDirPath,
|
|
@@ -44478,7 +45245,8 @@ var CopilotRule = class CopilotRule extends ToolRule {
|
|
|
44478
45245
|
});
|
|
44479
45246
|
}
|
|
44480
45247
|
if (!paths.nonRoot) throw new Error(`nonRoot path is not set for ${relativeFilePath}`);
|
|
44481
|
-
const
|
|
45248
|
+
const relativePath = join(resolvedRelativeDirPath, relativeFilePath);
|
|
45249
|
+
const filePath = join(outputRoot, relativePath);
|
|
44482
45250
|
const { frontmatter, body: content } = parseFrontmatter(await readFileContent(filePath), filePath);
|
|
44483
45251
|
const result = CopilotRuleFrontmatterSchema.safeParse(frontmatter);
|
|
44484
45252
|
if (!result.success) throw new Error(`Invalid frontmatter in ${filePath}: ${formatError(result.error)}`);
|
|
@@ -44750,7 +45518,8 @@ var DeepagentsRule = class DeepagentsRule extends ToolRule {
|
|
|
44750
45518
|
}
|
|
44751
45519
|
static async fromFile({ outputRoot = process.cwd(), relativeFilePath: _relativeFilePath, validate = true, global = false }) {
|
|
44752
45520
|
const settablePaths = this.getSettablePaths({ global });
|
|
44753
|
-
const
|
|
45521
|
+
const relativePath = join(settablePaths.root.relativeDirPath, settablePaths.root.relativeFilePath);
|
|
45522
|
+
const fileContent = await readFileContent(join(outputRoot, relativePath));
|
|
44754
45523
|
return new DeepagentsRule({
|
|
44755
45524
|
outputRoot,
|
|
44756
45525
|
relativeDirPath: settablePaths.root.relativeDirPath,
|
|
@@ -45177,7 +45946,8 @@ var FactorydroidRule = class FactorydroidRule extends ToolRule {
|
|
|
45177
45946
|
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
|
|
45178
45947
|
const paths = this.getSettablePaths({ global });
|
|
45179
45948
|
if (relativeFilePath === paths.root.relativeFilePath) {
|
|
45180
|
-
const
|
|
45949
|
+
const relativePath = join(paths.root.relativeDirPath, paths.root.relativeFilePath);
|
|
45950
|
+
const fileContent = await readFileContent(join(outputRoot, relativePath));
|
|
45181
45951
|
return new FactorydroidRule({
|
|
45182
45952
|
outputRoot,
|
|
45183
45953
|
relativeDirPath: paths.root.relativeDirPath,
|
|
@@ -45188,7 +45958,8 @@ var FactorydroidRule = class FactorydroidRule extends ToolRule {
|
|
|
45188
45958
|
});
|
|
45189
45959
|
}
|
|
45190
45960
|
if (!paths.nonRoot) throw new Error(`nonRoot path is not set for ${relativeFilePath}`);
|
|
45191
|
-
const
|
|
45961
|
+
const relativePath = join(paths.nonRoot.relativeDirPath, relativeFilePath);
|
|
45962
|
+
const fileContent = await readFileContent(join(outputRoot, relativePath));
|
|
45192
45963
|
return new FactorydroidRule({
|
|
45193
45964
|
outputRoot,
|
|
45194
45965
|
relativeDirPath: paths.nonRoot.relativeDirPath,
|
|
@@ -45418,7 +46189,8 @@ var HermesagentRule = class HermesagentRule extends ToolRule {
|
|
|
45418
46189
|
}
|
|
45419
46190
|
static async fromFile({ outputRoot = process.cwd(), relativeFilePath: _relativeFilePath, validate = true }) {
|
|
45420
46191
|
const { root } = this.getSettablePaths();
|
|
45421
|
-
const
|
|
46192
|
+
const relativePath = join(root.relativeDirPath, root.relativeFilePath);
|
|
46193
|
+
const fileContent = await readFileContent(join(outputRoot, relativePath));
|
|
45422
46194
|
return new HermesagentRule({
|
|
45423
46195
|
outputRoot,
|
|
45424
46196
|
relativeDirPath: root.relativeDirPath,
|
|
@@ -45529,7 +46301,8 @@ var JunieRule = class JunieRule extends ToolRule {
|
|
|
45529
46301
|
});
|
|
45530
46302
|
}
|
|
45531
46303
|
const relativeDirPath = this.getSettablePaths().root.relativeDirPath;
|
|
45532
|
-
const
|
|
46304
|
+
const relativePath = join(relativeDirPath, relativeFilePath);
|
|
46305
|
+
const fileContent = await readFileContent(join(outputRoot, relativePath));
|
|
45533
46306
|
return new JunieRule({
|
|
45534
46307
|
outputRoot,
|
|
45535
46308
|
relativeDirPath,
|
|
@@ -45629,7 +46402,8 @@ var KiloRule = class KiloRule extends ToolRule {
|
|
|
45629
46402
|
});
|
|
45630
46403
|
}
|
|
45631
46404
|
if (!paths.nonRoot) throw new Error(`nonRoot path is not set for ${relativeFilePath}`);
|
|
45632
|
-
const
|
|
46405
|
+
const relativePath = join(paths.nonRoot.relativeDirPath, relativeFilePath);
|
|
46406
|
+
const fileContent = await readFileContent(join(outputRoot, relativePath));
|
|
45633
46407
|
return new KiloRule({
|
|
45634
46408
|
outputRoot,
|
|
45635
46409
|
relativeDirPath: paths.nonRoot.relativeDirPath,
|
|
@@ -45966,10 +46740,13 @@ var KiroIdeRule = class extends KiroRule {
|
|
|
45966
46740
|
//#region src/features/rules/opencode-rule.ts
|
|
45967
46741
|
var OpenCodeRule = class OpenCodeRule extends ToolRule {
|
|
45968
46742
|
static getSettablePaths({ global, excludeToolDir } = {}) {
|
|
45969
|
-
if (global) return {
|
|
45970
|
-
|
|
45971
|
-
|
|
45972
|
-
|
|
46743
|
+
if (global) return {
|
|
46744
|
+
root: {
|
|
46745
|
+
relativeDirPath: buildToolPath(OPENCODE_GLOBAL_DIR, ".", excludeToolDir),
|
|
46746
|
+
relativeFilePath: OPENCODE_RULE_FILE_NAME
|
|
46747
|
+
},
|
|
46748
|
+
nonRoot: { relativeDirPath: buildToolPath(OPENCODE_GLOBAL_DIR, "memories", excludeToolDir) }
|
|
46749
|
+
};
|
|
45973
46750
|
return {
|
|
45974
46751
|
root: {
|
|
45975
46752
|
relativeDirPath: ".",
|
|
@@ -45979,7 +46756,7 @@ var OpenCodeRule = class OpenCodeRule extends ToolRule {
|
|
|
45979
46756
|
};
|
|
45980
46757
|
}
|
|
45981
46758
|
static getExtraSharedWritePaths({ global = false } = {}) {
|
|
45982
|
-
return
|
|
46759
|
+
return [OpencodeMcp.getSettablePaths({ global })];
|
|
45983
46760
|
}
|
|
45984
46761
|
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
|
|
45985
46762
|
const paths = this.getSettablePaths({ global });
|
|
@@ -45996,7 +46773,8 @@ var OpenCodeRule = class OpenCodeRule extends ToolRule {
|
|
|
45996
46773
|
});
|
|
45997
46774
|
}
|
|
45998
46775
|
if (!paths.nonRoot) throw new Error(`nonRoot path is not set for ${relativeFilePath}`);
|
|
45999
|
-
const
|
|
46776
|
+
const relativePath = join(paths.nonRoot.relativeDirPath, relativeFilePath);
|
|
46777
|
+
const fileContent = await readFileContent(join(outputRoot, relativePath));
|
|
46000
46778
|
return new OpenCodeRule({
|
|
46001
46779
|
outputRoot,
|
|
46002
46780
|
relativeDirPath: paths.nonRoot.relativeDirPath,
|
|
@@ -46111,7 +46889,8 @@ var PiRule = class PiRule extends ToolRule {
|
|
|
46111
46889
|
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
|
|
46112
46890
|
const { root, appendSystemPrompt } = this.getSettablePaths({ global });
|
|
46113
46891
|
if (relativeFilePath === "APPEND_SYSTEM.md") {
|
|
46114
|
-
const
|
|
46892
|
+
const relativePath = join(appendSystemPrompt.relativeDirPath, appendSystemPrompt.relativeFilePath);
|
|
46893
|
+
const fileContent = await readFileContent(join(outputRoot, relativePath));
|
|
46115
46894
|
return new PiRule({
|
|
46116
46895
|
outputRoot,
|
|
46117
46896
|
relativeDirPath: appendSystemPrompt.relativeDirPath,
|
|
@@ -46122,7 +46901,8 @@ var PiRule = class PiRule extends ToolRule {
|
|
|
46122
46901
|
appendSystemPrompt: true
|
|
46123
46902
|
});
|
|
46124
46903
|
}
|
|
46125
|
-
const
|
|
46904
|
+
const relativePath = join(root.relativeDirPath, root.relativeFilePath);
|
|
46905
|
+
const fileContent = await readFileContent(join(outputRoot, relativePath));
|
|
46126
46906
|
return new PiRule({
|
|
46127
46907
|
outputRoot,
|
|
46128
46908
|
relativeDirPath: root.relativeDirPath,
|
|
@@ -46418,6 +47198,13 @@ var QwencodeRule = class QwencodeRule extends ToolRule {
|
|
|
46418
47198
|
toolTarget: "qwencode"
|
|
46419
47199
|
});
|
|
46420
47200
|
}
|
|
47201
|
+
/**
|
|
47202
|
+
* The personal local context file lives under `.qwen/`, not at the project
|
|
47203
|
+
* root where the settable root path points, so override the deletion glob.
|
|
47204
|
+
*/
|
|
47205
|
+
static getLocalRootFileGlob({ outputRoot, fileName }) {
|
|
47206
|
+
return join(outputRoot, QWENCODE_DIR, fileName);
|
|
47207
|
+
}
|
|
46421
47208
|
};
|
|
46422
47209
|
//#endregion
|
|
46423
47210
|
//#region src/features/rules/reasonix-rule.ts
|
|
@@ -46435,9 +47222,51 @@ var ReasonixRule = class ReasonixRule extends ToolRule {
|
|
|
46435
47222
|
relativeFilePath: REASONIX_RULE_FILE_NAME
|
|
46436
47223
|
} };
|
|
46437
47224
|
}
|
|
46438
|
-
|
|
47225
|
+
/**
|
|
47226
|
+
* Context Engine v2 (v1.18.0) walks from the workspace root to the target
|
|
47227
|
+
* path loading per-directory instruction files, so nested `REASONIX.md`
|
|
47228
|
+
* files are a real scoping surface: "Deeper directories beat broader
|
|
47229
|
+
* directories." The scan mirrors the AGENTS.md standard's nested discovery
|
|
47230
|
+
* (same exclusions, import-only, project scope).
|
|
47231
|
+
* @see https://github.com/esengine/DeepSeek-Reasonix/blob/v1.18.0/docs/SESSION_MEMORY_RETRIEVAL.md
|
|
47232
|
+
*/
|
|
47233
|
+
static getNestedFilePatterns({ outputRoot }) {
|
|
47234
|
+
const root = toPosixPath(outputRoot);
|
|
47235
|
+
return {
|
|
47236
|
+
include: [`${root}/**/${REASONIX_RULE_FILE_NAME}`],
|
|
47237
|
+
ignore: [
|
|
47238
|
+
`${root}/${REASONIX_RULE_FILE_NAME}`,
|
|
47239
|
+
`${root}/**/.*/**`,
|
|
47240
|
+
...NESTED_SCAN_EXCLUDED_DIRS_ANY_DEPTH.map((dir) => `${root}/**/${dir}/**`),
|
|
47241
|
+
...NESTED_SCAN_EXCLUDED_ROOT_DIRS.map((dir) => `${root}/${dir}/**`)
|
|
47242
|
+
]
|
|
47243
|
+
};
|
|
47244
|
+
}
|
|
47245
|
+
/**
|
|
47246
|
+
* The subproject directory this rule scopes, or `undefined` for the root
|
|
47247
|
+
* file (project or global).
|
|
47248
|
+
*/
|
|
47249
|
+
getSubprojectPath() {
|
|
47250
|
+
if (this.isRoot()) return;
|
|
47251
|
+
const relativeDirPath = toPosixPath(this.getRelativeDirPath());
|
|
47252
|
+
if (relativeDirPath === "." || relativeDirPath === "" || relativeDirPath.startsWith(".")) return;
|
|
47253
|
+
return relativeDirPath;
|
|
47254
|
+
}
|
|
47255
|
+
static async fromFile({ outputRoot = process.cwd(), relativeFilePath: _relativeFilePath, relativeDirPath: overrideDirPath, validate = true, global = false }) {
|
|
46439
47256
|
const { root } = this.getSettablePaths({ global });
|
|
46440
|
-
|
|
47257
|
+
if (overrideDirPath !== void 0 && overrideDirPath !== root.relativeDirPath && overrideDirPath !== ".") {
|
|
47258
|
+
const fileContent = await readFileContent(join(outputRoot, overrideDirPath, REASONIX_RULE_FILE_NAME));
|
|
47259
|
+
return new ReasonixRule({
|
|
47260
|
+
outputRoot,
|
|
47261
|
+
relativeDirPath: overrideDirPath,
|
|
47262
|
+
relativeFilePath: REASONIX_RULE_FILE_NAME,
|
|
47263
|
+
fileContent,
|
|
47264
|
+
validate,
|
|
47265
|
+
root: false
|
|
47266
|
+
});
|
|
47267
|
+
}
|
|
47268
|
+
const relativePath = join(root.relativeDirPath, root.relativeFilePath);
|
|
47269
|
+
const fileContent = await readFileContent(join(outputRoot, relativePath));
|
|
46441
47270
|
return new ReasonixRule({
|
|
46442
47271
|
outputRoot,
|
|
46443
47272
|
relativeDirPath: root.relativeDirPath,
|
|
@@ -46449,7 +47278,17 @@ var ReasonixRule = class ReasonixRule extends ToolRule {
|
|
|
46449
47278
|
}
|
|
46450
47279
|
static fromRulesyncRule({ outputRoot = process.cwd(), rulesyncRule, validate = true, global = false }) {
|
|
46451
47280
|
const { root } = this.getSettablePaths({ global });
|
|
46452
|
-
const
|
|
47281
|
+
const frontmatter = rulesyncRule.getFrontmatter();
|
|
47282
|
+
const isRoot = frontmatter.root ?? false;
|
|
47283
|
+
const subprojectPath = frontmatter.agentsmd?.subprojectPath;
|
|
47284
|
+
if (!global && !isRoot && subprojectPath) return new ReasonixRule({
|
|
47285
|
+
outputRoot,
|
|
47286
|
+
relativeDirPath: join(subprojectPath),
|
|
47287
|
+
relativeFilePath: REASONIX_RULE_FILE_NAME,
|
|
47288
|
+
fileContent: rulesyncRule.getBody(),
|
|
47289
|
+
validate,
|
|
47290
|
+
root: false
|
|
47291
|
+
});
|
|
46453
47292
|
return new ReasonixRule({
|
|
46454
47293
|
outputRoot,
|
|
46455
47294
|
relativeDirPath: root.relativeDirPath,
|
|
@@ -46460,7 +47299,22 @@ var ReasonixRule = class ReasonixRule extends ToolRule {
|
|
|
46460
47299
|
});
|
|
46461
47300
|
}
|
|
46462
47301
|
toRulesyncRule() {
|
|
46463
|
-
|
|
47302
|
+
const subprojectPath = this.getSubprojectPath();
|
|
47303
|
+
if (subprojectPath === void 0) return this.toRulesyncRuleDefault();
|
|
47304
|
+
const slug = subprojectPath.replaceAll("/", "-");
|
|
47305
|
+
return new RulesyncRule({
|
|
47306
|
+
outputRoot: this.getOutputRoot(),
|
|
47307
|
+
relativeDirPath: RULESYNC_RULES_RELATIVE_DIR_PATH,
|
|
47308
|
+
relativeFilePath: `${slug}-reasonix.md`,
|
|
47309
|
+
frontmatter: {
|
|
47310
|
+
targets: ["reasonix"],
|
|
47311
|
+
root: false,
|
|
47312
|
+
globs: [`${subprojectPath}/**/*`],
|
|
47313
|
+
agentsmd: { subprojectPath }
|
|
47314
|
+
},
|
|
47315
|
+
body: this.getFileContent(),
|
|
47316
|
+
validate: true
|
|
47317
|
+
});
|
|
46464
47318
|
}
|
|
46465
47319
|
validate() {
|
|
46466
47320
|
return {
|
|
@@ -46630,7 +47484,7 @@ var RooRule = class RooRule extends ToolRule {
|
|
|
46630
47484
|
* Glob for the `separate-local-file` deletion; Roo reads `AGENTS.local.md`
|
|
46631
47485
|
* at the project root, not under `.roo/` (mirrors rovodev).
|
|
46632
47486
|
*/
|
|
46633
|
-
static
|
|
47487
|
+
static getLocalRootFileGlob({ outputRoot, fileName }) {
|
|
46634
47488
|
return join(outputRoot, fileName);
|
|
46635
47489
|
}
|
|
46636
47490
|
};
|
|
@@ -46762,19 +47616,6 @@ var RovodevRule = class RovodevRule extends ToolRule {
|
|
|
46762
47616
|
}));
|
|
46763
47617
|
}
|
|
46764
47618
|
toRulesyncRule() {
|
|
46765
|
-
if (this.getRelativeFilePath() === "AGENTS.local.md") return new RulesyncRule({
|
|
46766
|
-
outputRoot: this.getOutputRoot(),
|
|
46767
|
-
relativeDirPath: RULESYNC_RULES_RELATIVE_DIR_PATH,
|
|
46768
|
-
relativeFilePath: ROVODEV_LEGACY_RULE_FILE_NAME,
|
|
46769
|
-
frontmatter: {
|
|
46770
|
-
targets: ["rovodev"],
|
|
46771
|
-
root: false,
|
|
46772
|
-
localRoot: true,
|
|
46773
|
-
globs: []
|
|
46774
|
-
},
|
|
46775
|
-
body: this.getFileContent(),
|
|
46776
|
-
validate: true
|
|
46777
|
-
});
|
|
46778
47619
|
if (!this.isRoot()) return new RulesyncRule({
|
|
46779
47620
|
outputRoot: this.getOutputRoot(),
|
|
46780
47621
|
relativeDirPath: RULESYNC_RULES_RELATIVE_DIR_PATH,
|
|
@@ -46831,7 +47672,7 @@ var RovodevRule = class RovodevRule extends ToolRule {
|
|
|
46831
47672
|
};
|
|
46832
47673
|
}
|
|
46833
47674
|
/** Glob for the `separate-local-file` deletion; rovodev writes it at project root, not under `.rovodev/`. */
|
|
46834
|
-
static
|
|
47675
|
+
static getLocalRootFileGlob({ outputRoot, fileName }) {
|
|
46835
47676
|
return join(outputRoot, fileName);
|
|
46836
47677
|
}
|
|
46837
47678
|
};
|
|
@@ -47021,15 +47862,20 @@ var WarpRule = class WarpRule extends ToolRule {
|
|
|
47021
47862
|
root: root ?? false
|
|
47022
47863
|
});
|
|
47023
47864
|
}
|
|
47024
|
-
static getSettablePaths(
|
|
47865
|
+
static getSettablePaths({ global = false } = {}) {
|
|
47866
|
+
if (global) return { root: {
|
|
47867
|
+
relativeDirPath: WARP_GLOBAL_RULE_DIR,
|
|
47868
|
+
relativeFilePath: WARP_RULE_FILE_NAME
|
|
47869
|
+
} };
|
|
47025
47870
|
return { root: {
|
|
47026
47871
|
relativeDirPath: ".",
|
|
47027
47872
|
relativeFilePath: WARP_RULE_FILE_NAME
|
|
47028
47873
|
} };
|
|
47029
47874
|
}
|
|
47030
|
-
static async fromFile({ outputRoot = process.cwd(), relativeFilePath: _relativeFilePath, validate = true }) {
|
|
47031
|
-
const { root } = this.getSettablePaths();
|
|
47032
|
-
const
|
|
47875
|
+
static async fromFile({ outputRoot = process.cwd(), relativeFilePath: _relativeFilePath, validate = true, global = false }) {
|
|
47876
|
+
const { root } = this.getSettablePaths({ global });
|
|
47877
|
+
const relativePath = join(root.relativeDirPath, root.relativeFilePath);
|
|
47878
|
+
const fileContent = await readFileContent(join(outputRoot, relativePath));
|
|
47033
47879
|
return new WarpRule({
|
|
47034
47880
|
outputRoot,
|
|
47035
47881
|
relativeDirPath: root.relativeDirPath,
|
|
@@ -47039,8 +47885,8 @@ var WarpRule = class WarpRule extends ToolRule {
|
|
|
47039
47885
|
root: true
|
|
47040
47886
|
});
|
|
47041
47887
|
}
|
|
47042
|
-
static fromRulesyncRule({ outputRoot = process.cwd(), rulesyncRule, validate = true }) {
|
|
47043
|
-
const { root } = this.getSettablePaths();
|
|
47888
|
+
static fromRulesyncRule({ outputRoot = process.cwd(), rulesyncRule, validate = true, global = false }) {
|
|
47889
|
+
const { root } = this.getSettablePaths({ global });
|
|
47044
47890
|
const isRoot = rulesyncRule.getFrontmatter().root ?? false;
|
|
47045
47891
|
return new WarpRule({
|
|
47046
47892
|
outputRoot,
|
|
@@ -47060,8 +47906,8 @@ var WarpRule = class WarpRule extends ToolRule {
|
|
|
47060
47906
|
error: null
|
|
47061
47907
|
};
|
|
47062
47908
|
}
|
|
47063
|
-
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
47064
|
-
const { root } = this.getSettablePaths();
|
|
47909
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
47910
|
+
const { root } = this.getSettablePaths({ global });
|
|
47065
47911
|
const isRoot = relativeFilePath === root.relativeFilePath && relativeDirPath === root.relativeDirPath;
|
|
47066
47912
|
return new WarpRule({
|
|
47067
47913
|
outputRoot,
|
|
@@ -47154,6 +48000,27 @@ var ZedRule = class ZedRule extends ToolRule {
|
|
|
47154
48000
|
}
|
|
47155
48001
|
};
|
|
47156
48002
|
//#endregion
|
|
48003
|
+
//#region src/features/rules/zoocode-rule.ts
|
|
48004
|
+
/**
|
|
48005
|
+
* Rule generator for **Zoo Code**, the community continuation of Roo Code
|
|
48006
|
+
* (named by the Roo shutdown notice; releases continue Roo's numbering from
|
|
48007
|
+
* v3.54.0). Zoo Code still resolves `~/.roo` and the project `.roo/` layout —
|
|
48008
|
+
* the `.zoo` renaming is confined to provider/auth code — so this target
|
|
48009
|
+
* reuses {@link RooRule}'s behavior verbatim and only narrows the targeting to
|
|
48010
|
+
* the `zoocode` tool name.
|
|
48011
|
+
*
|
|
48012
|
+
* @see https://github.com/Zoo-Code-Org/Zoo-Code
|
|
48013
|
+
* @see https://docs.zoocode.dev
|
|
48014
|
+
*/
|
|
48015
|
+
var ZoocodeRule = class extends RooRule {
|
|
48016
|
+
static isTargetedByRulesyncRule(rulesyncRule) {
|
|
48017
|
+
return this.isTargetedByRulesyncRuleDefault({
|
|
48018
|
+
rulesyncRule,
|
|
48019
|
+
toolTarget: "zoocode"
|
|
48020
|
+
});
|
|
48021
|
+
}
|
|
48022
|
+
};
|
|
48023
|
+
//#endregion
|
|
47157
48024
|
//#region src/features/rules/rules-processor.ts
|
|
47158
48025
|
const RulesProcessorToolTargetSchema = z.enum(rulesProcessorToolTargetTuple);
|
|
47159
48026
|
const formatRulePaths = (rules) => rules.map((r) => join(r.getRelativeDirPath(), r.getRelativeFilePath())).join(", ");
|
|
@@ -47416,6 +48283,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
47416
48283
|
supportsGlobal: true,
|
|
47417
48284
|
ruleDiscoveryMode: "toon",
|
|
47418
48285
|
mcpInstructionsRegistrar: OpencodeMcp,
|
|
48286
|
+
mcpInstructionsRegistrarGlobal: true,
|
|
47419
48287
|
collisionPolicy: "compose"
|
|
47420
48288
|
}
|
|
47421
48289
|
}],
|
|
@@ -47434,6 +48302,8 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
47434
48302
|
extension: "md",
|
|
47435
48303
|
supportsGlobal: true,
|
|
47436
48304
|
ruleDiscoveryMode: "auto",
|
|
48305
|
+
localRootMode: "separate-local-file",
|
|
48306
|
+
localRootFileName: QWENCODE_LOCAL_RULE_FILE_NAME,
|
|
47437
48307
|
additionalConventions: { subagents: { subagentClass: QwencodeSubagent } }
|
|
47438
48308
|
}
|
|
47439
48309
|
}],
|
|
@@ -47478,6 +48348,16 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
47478
48348
|
localRootFileName: "AGENTS.local.md"
|
|
47479
48349
|
}
|
|
47480
48350
|
}],
|
|
48351
|
+
["zoocode", {
|
|
48352
|
+
class: ZoocodeRule,
|
|
48353
|
+
meta: {
|
|
48354
|
+
extension: "md",
|
|
48355
|
+
supportsGlobal: true,
|
|
48356
|
+
ruleDiscoveryMode: "auto",
|
|
48357
|
+
localRootMode: "separate-local-file",
|
|
48358
|
+
localRootFileName: "AGENTS.local.md"
|
|
48359
|
+
}
|
|
48360
|
+
}],
|
|
47481
48361
|
["takt", {
|
|
47482
48362
|
class: TaktRule,
|
|
47483
48363
|
meta: {
|
|
@@ -47498,7 +48378,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
47498
48378
|
class: WarpRule,
|
|
47499
48379
|
meta: {
|
|
47500
48380
|
extension: "md",
|
|
47501
|
-
supportsGlobal:
|
|
48381
|
+
supportsGlobal: true,
|
|
47502
48382
|
ruleDiscoveryMode: "toon",
|
|
47503
48383
|
collisionPolicy: "fold"
|
|
47504
48384
|
}
|
|
@@ -47650,18 +48530,19 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
47650
48530
|
/**
|
|
47651
48531
|
* Non-root rules of some tools are not auto-loaded; the tool's MCP feature
|
|
47652
48532
|
* registers them in its shared config's `instructions` key. The root rule is
|
|
47653
|
-
* auto-loaded and never registered.
|
|
48533
|
+
* auto-loaded and never registered. Global scope opt-in via
|
|
48534
|
+
* `mcpInstructionsRegistrarGlobal`.
|
|
47654
48535
|
*/
|
|
47655
48536
|
async buildMcpInstructionFiles({ toolRules, meta }) {
|
|
47656
|
-
if (!meta.mcpInstructionsRegistrar || this.global) return [];
|
|
48537
|
+
if (!meta.mcpInstructionsRegistrar || this.global && !meta.mcpInstructionsRegistrarGlobal) return [];
|
|
47657
48538
|
const instructionPaths = toolRules.filter((rule) => !rule.isRoot() && !rule.isExcludedFromRootReferences()).map((rule) => toPosixPath(join(rule.getRelativeDirPath(), rule.getRelativeFilePath())));
|
|
47658
|
-
|
|
47659
|
-
return [await meta.mcpInstructionsRegistrar.fromInstructions({
|
|
48539
|
+
const registered = await meta.mcpInstructionsRegistrar.fromInstructions({
|
|
47660
48540
|
outputRoot: this.outputRoot,
|
|
47661
48541
|
instructions: instructionPaths,
|
|
47662
48542
|
validate: true,
|
|
47663
48543
|
global: this.global
|
|
47664
|
-
})
|
|
48544
|
+
});
|
|
48545
|
+
return registered ? [registered] : [];
|
|
47665
48546
|
}
|
|
47666
48547
|
/**
|
|
47667
48548
|
* For tools that don't create a separate conventions rule, prepend the
|
|
@@ -47792,45 +48673,58 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
47792
48673
|
const rootRule = toolRules.find((rule) => rule.isRoot());
|
|
47793
48674
|
if (rootRule) rootRule.setFileContent(rootRule.getFileContent() + "\n\n" + localRootBody);
|
|
47794
48675
|
}
|
|
47795
|
-
buildLocalRootFile({ factory, fileName, body }) {
|
|
48676
|
+
buildLocalRootFile({ factory, fileName, body, relativeDirPath, localRoot = false }) {
|
|
47796
48677
|
if (factory.class === ClaudecodeRule) {
|
|
47797
48678
|
const paths = ClaudecodeRule.getSettablePaths({ global: this.global });
|
|
47798
48679
|
return new ClaudecodeRule({
|
|
47799
48680
|
outputRoot: this.outputRoot,
|
|
47800
|
-
relativeDirPath: paths.root.relativeDirPath,
|
|
48681
|
+
relativeDirPath: relativeDirPath ?? paths.root.relativeDirPath,
|
|
47801
48682
|
relativeFilePath: fileName,
|
|
47802
48683
|
frontmatter: {},
|
|
47803
48684
|
body,
|
|
47804
48685
|
validate: true,
|
|
47805
|
-
root: true
|
|
48686
|
+
root: true,
|
|
48687
|
+
localRoot
|
|
47806
48688
|
});
|
|
47807
48689
|
}
|
|
47808
48690
|
if (factory.class === ClaudecodeLegacyRule) {
|
|
47809
48691
|
const paths = ClaudecodeLegacyRule.getSettablePaths({ global: this.global });
|
|
47810
48692
|
return new ClaudecodeLegacyRule({
|
|
47811
48693
|
outputRoot: this.outputRoot,
|
|
47812
|
-
relativeDirPath: paths.root.relativeDirPath,
|
|
48694
|
+
relativeDirPath: relativeDirPath ?? paths.root.relativeDirPath,
|
|
47813
48695
|
relativeFilePath: fileName,
|
|
47814
48696
|
fileContent: body,
|
|
47815
48697
|
validate: true,
|
|
47816
|
-
root: true
|
|
48698
|
+
root: true,
|
|
48699
|
+
localRoot
|
|
47817
48700
|
});
|
|
47818
48701
|
}
|
|
47819
48702
|
if (factory.class === RovodevRule) return new RovodevRule({
|
|
47820
48703
|
outputRoot: this.outputRoot,
|
|
47821
|
-
relativeDirPath: ".",
|
|
48704
|
+
relativeDirPath: relativeDirPath ?? ".",
|
|
47822
48705
|
relativeFilePath: fileName,
|
|
47823
48706
|
fileContent: body,
|
|
47824
48707
|
validate: true,
|
|
47825
|
-
root: true
|
|
48708
|
+
root: true,
|
|
48709
|
+
localRoot
|
|
47826
48710
|
});
|
|
47827
48711
|
if (factory.class === RooRule) return new RooRule({
|
|
47828
48712
|
outputRoot: this.outputRoot,
|
|
47829
|
-
relativeDirPath: ".",
|
|
48713
|
+
relativeDirPath: relativeDirPath ?? ".",
|
|
47830
48714
|
relativeFilePath: fileName,
|
|
47831
48715
|
fileContent: body,
|
|
47832
48716
|
validate: true,
|
|
47833
|
-
root: true
|
|
48717
|
+
root: true,
|
|
48718
|
+
localRoot
|
|
48719
|
+
});
|
|
48720
|
+
if (factory.class === QwencodeRule) return new QwencodeRule({
|
|
48721
|
+
outputRoot: this.outputRoot,
|
|
48722
|
+
relativeDirPath: relativeDirPath ?? ".qwen",
|
|
48723
|
+
relativeFilePath: fileName,
|
|
48724
|
+
fileContent: body,
|
|
48725
|
+
validate: true,
|
|
48726
|
+
root: true,
|
|
48727
|
+
localRoot
|
|
47834
48728
|
});
|
|
47835
48729
|
return null;
|
|
47836
48730
|
}
|
|
@@ -47868,6 +48762,7 @@ As this project's AI coding tool, you must follow the additional conventions bel
|
|
|
47868
48762
|
async convertToolFilesToRulesyncFiles(toolFiles) {
|
|
47869
48763
|
const toolRules = toolFiles.filter((file) => file instanceof ToolRule);
|
|
47870
48764
|
const rulesyncRules = toolRules.map((toolRule) => {
|
|
48765
|
+
if (toolRule.isLocalRoot()) return toolRule.toLocalRootRulesyncRule({ targets: [this.toolTarget] });
|
|
47871
48766
|
return toolRule.toRulesyncRule();
|
|
47872
48767
|
});
|
|
47873
48768
|
const claimedBy = /* @__PURE__ */ new Map();
|
|
@@ -48001,20 +48896,34 @@ As this project's AI coding tool, you must follow the additional conventions bel
|
|
|
48001
48896
|
})();
|
|
48002
48897
|
this.logger.debug(`Found ${rootToolRules.length} root tool rule files`);
|
|
48003
48898
|
const localRootToolRules = await (async () => {
|
|
48004
|
-
if (
|
|
48899
|
+
if (this.global || factory.meta.localRootMode !== "separate-local-file" || !factory.meta.localRootFileName) return [];
|
|
48005
48900
|
const fileName = factory.meta.localRootFileName;
|
|
48006
|
-
|
|
48007
|
-
|
|
48901
|
+
const filePaths = await (async () => {
|
|
48902
|
+
if (factory.class.getLocalRootFileGlob) return await findFilesByGlobs(factory.class.getLocalRootFileGlob({
|
|
48008
48903
|
outputRoot: this.outputRoot,
|
|
48009
48904
|
fileName
|
|
48010
48905
|
}));
|
|
48011
|
-
|
|
48012
|
-
|
|
48013
|
-
|
|
48014
|
-
|
|
48015
|
-
return
|
|
48906
|
+
if (!settablePaths.root) return [];
|
|
48907
|
+
return await findFilesWithFallback(join(this.outputRoot, settablePaths.root.relativeDirPath ?? ".", fileName), settablePaths.alternativeRoots, (alt) => join(this.outputRoot, alt.relativeDirPath, fileName));
|
|
48908
|
+
})();
|
|
48909
|
+
if (forDeletion) return buildDeletionRulesFromPaths(filePaths);
|
|
48910
|
+
return (await Promise.all(filePaths.map(async (filePath) => {
|
|
48911
|
+
const relativeDirPath = resolveRelativeDirPath(filePath);
|
|
48912
|
+
checkPathTraversal({
|
|
48913
|
+
relativePath: relativeDirPath,
|
|
48914
|
+
intendedRootDir: this.outputRoot
|
|
48915
|
+
});
|
|
48916
|
+
const body = await readFileContent(filePath);
|
|
48917
|
+
return this.buildLocalRootFile({
|
|
48918
|
+
factory,
|
|
48919
|
+
fileName: basename(filePath),
|
|
48920
|
+
body,
|
|
48921
|
+
relativeDirPath,
|
|
48922
|
+
localRoot: true
|
|
48923
|
+
});
|
|
48924
|
+
}))).filter((rule) => rule !== null);
|
|
48016
48925
|
})();
|
|
48017
|
-
this.logger.debug(`Found ${localRootToolRules.length} local root tool rule files
|
|
48926
|
+
this.logger.debug(`Found ${localRootToolRules.length} local root tool rule files`);
|
|
48018
48927
|
const rootMirrorDeletionRules = await (async () => {
|
|
48019
48928
|
const rootMirror = factory.class.getRootMirror?.();
|
|
48020
48929
|
if (!forDeletion || this.global || !rootMirror) return [];
|
|
@@ -48140,13 +49049,14 @@ As this project's AI coding tool, you must follow the additional conventions bel
|
|
|
48140
49049
|
const lines = [];
|
|
48141
49050
|
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.");
|
|
48142
49051
|
lines.push("");
|
|
48143
|
-
const
|
|
49052
|
+
const rules = toolRulesWithoutRoot.map((toolRule) => {
|
|
48144
49053
|
const frontmatter = toolRule.toRulesyncRule().getFrontmatter();
|
|
48145
49054
|
const rule = { path: `@${toolRule.getRelativePathFromCwd()}` };
|
|
48146
49055
|
if (frontmatter.description) rule.description = frontmatter.description;
|
|
48147
49056
|
if (frontmatter.globs && frontmatter.globs.length > 0) rule.applyTo = frontmatter.globs;
|
|
48148
49057
|
return rule;
|
|
48149
|
-
})
|
|
49058
|
+
});
|
|
49059
|
+
const toonContent = encode({ rules });
|
|
48150
49060
|
lines.push(toonContent);
|
|
48151
49061
|
return lines.join("\n") + "\n\n";
|
|
48152
49062
|
}
|
|
@@ -49308,8 +50218,8 @@ async function generateIgnoreCore(params) {
|
|
|
49308
50218
|
allPaths.push(...result.paths);
|
|
49309
50219
|
if (result.hasDiff) hasDiff = true;
|
|
49310
50220
|
} catch (error) {
|
|
49311
|
-
logger.
|
|
49312
|
-
|
|
50221
|
+
logger.error(`Failed to generate ${toolTarget} ignore files for ${outputRoot}: ${formatError(error)}`);
|
|
50222
|
+
throw error;
|
|
49313
50223
|
}
|
|
49314
50224
|
}
|
|
49315
50225
|
return {
|
|
@@ -49581,8 +50491,8 @@ async function generatePermissionsCore(params) {
|
|
|
49581
50491
|
allPaths.push(...result.paths);
|
|
49582
50492
|
if (result.hasDiff) hasDiff = true;
|
|
49583
50493
|
} catch (error) {
|
|
49584
|
-
logger.
|
|
49585
|
-
|
|
50494
|
+
logger.error(`Failed to generate ${toolTarget} permissions files for ${outputRoot}: ${formatError(error)}`);
|
|
50495
|
+
throw error;
|
|
49586
50496
|
}
|
|
49587
50497
|
}
|
|
49588
50498
|
return {
|
|
@@ -49984,6 +50894,6 @@ async function importChecksCore(params) {
|
|
|
49984
50894
|
return writtenCount;
|
|
49985
50895
|
}
|
|
49986
50896
|
//#endregion
|
|
49987
|
-
export {
|
|
50897
|
+
export { JsonLogger as $, RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH as $t, RulesyncRuleFrontmatterSchema as A, PACKAGING_TOOL_TARGETS as At, RulesyncCheck as B, RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH as Bt, CODEXCLI_DIR as C, removeTempDirectory as Ct, RulesyncSkill as D, writeFileContent as Dt, RulesyncSubagentFrontmatterSchema as E, toPosixPath as Et, getRulesyncSourceCandidates as F, RULESYNC_CHECKS_RELATIVE_DIR_PATH as Ft, SKILL_FILE_NAME as G, RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH as Gt, stringifyFrontmatter as H, RULESYNC_HOOKS_LEGACY_FILE_NAME as Ht, resolveRulesyncSourceWritePath as I, RULESYNC_COMMANDS_RELATIVE_DIR_PATH as It, ConfigFileSchema as J, RULESYNC_MCP_RELATIVE_FILE_PATH as Jt, ConfigResolver as K, RULESYNC_MCP_FILE_NAME as Kt, parseJsonc as L, RULESYNC_CONFIG_RELATIVE_FILE_PATH as Lt, RulesyncMcp as M, MAX_FILE_SIZE as Mt, RulesyncIgnore as N, RULESYNC_AIIGNORE_FILE_NAME as Nt, RulesyncSkillFrontmatterSchema as O, ALL_TOOL_TARGETS as Ot, RulesyncHooks as P, RULESYNC_AIIGNORE_RELATIVE_FILE_PATH as Pt, ConsoleLogger as Q, RULESYNC_PERMISSIONS_LEGACY_FILE_NAME as Qt, RulesyncCommand as R, RULESYNC_CONFIG_SCHEMA_URL as Rt, CODEXCLI_BASH_RULES_FILE_NAME as S, removeFileStrict as St, RulesyncSubagent as T, runWithDirectoryRollback as Tt, loadYaml as U, RULESYNC_HOOKS_RELATIVE_FILE_PATH as Ut, RulesyncCheckFrontmatterSchema as V, RULESYNC_HOOKS_FILE_NAME as Vt, SHARED_USER_MANAGED_CONFIG_PATHS as W, RULESYNC_IGNORE_RELATIVE_FILE_PATH as Wt, SourceEntrySchema as X, RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH as Xt, GITIGNORE_DESTINATION_KEY as Y, RULESYNC_MCP_SCHEMA_URL as Yt, findControlCharacter as Z, RULESYNC_PERMISSIONS_FILE_NAME as Zt, CLAUDECODE_LOCAL_RULE_FILE_NAME as _, readFileContent as _t, convertFromTool as a, RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH as an, assertTreeContainsNoSymlinks as at, CLAUDECODE_SKILLS_DIR_PATH as b, removeDirectoryStrict as bt, SubagentsProcessor as c, DEPRECATED_FEATURE_REPLACEMENTS as cn, createTempDirectory as ct, IgnoreProcessor as d, fileExists as dt, RULESYNC_PERMISSIONS_SCHEMA_URL as en, fallbackLogger as et, HooksProcessor as f, findFilesByGlobs as ft, CLAUDECODE_DIR as g, listDirectoryFiles as gt, QWENCODE_LOCAL_RULE_FILE_NAME as h, isSymlink as ht, getProcessorRegistryEntry as i, RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH as in, assertDirectoryIfExists as it, RulesyncPermissions as j, ToolTargetSchema as jt, RulesyncRule as k, ALL_TOOL_TARGETS_WITH_WILDCARD as kt, SkillsProcessor as l, formatError as ln, directoryExists as lt, QWENCODE_DIR as m, getHomeDirectory as mt, checkRulesyncDirExists as n, RULESYNC_RULES_RELATIVE_DIR_PATH as nn, CLIError as nt, isPackagingToolTarget as o, ALL_FEATURES as on, assertWritablePathInsideRoot as ot, CommandsProcessor as p, getFileSize as pt, CONFLICTING_TARGET_PAIRS as q, RULESYNC_MCP_LEGACY_FILE_NAME as qt, generate as r, RULESYNC_SKILLS_RELATIVE_DIR_PATH as rn, ErrorCodes as rt, RulesProcessor as s, ALL_FEATURES_WITH_WILDCARD as sn, checkPathTraversal as st, importFromTool as t, RULESYNC_RELATIVE_DIR_PATH as tn, warnOnConflictingFlags as tt, McpProcessor as u, ensureDir as ut, CLAUDECODE_MEMORIES_DIR_NAME as v, readFileContentOrNull as vt, getLocalSkillDirNames as w, resolvePath as wt, ChecksProcessor as x, removeFile as xt, CLAUDECODE_SETTINGS_LOCAL_FILE_NAME as y, removeDirectory as yt, RulesyncCommandFrontmatterSchema as z, RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH as zt };
|
|
49988
50898
|
|
|
49989
|
-
//# sourceMappingURL=import-
|
|
50899
|
+
//# sourceMappingURL=import-BlwypG9v.js.map
|