rulesync 16.4.0 → 16.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/cli/index.cjs +116 -68
- package/dist/cli/index.js +116 -68
- package/dist/cli/index.js.map +1 -1
- package/dist/{import-BwakMyyf.js → import-BlwypG9v.js} +1126 -286
- package/dist/import-BlwypG9v.js.map +1 -0
- package/dist/{import-C9wxpZey.cjs → import-D87qlRUj.cjs} +1141 -301
- package/dist/index.cjs +17 -15
- package/dist/index.d.cts +3 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -15
- package/dist/index.js.map +1 -1
- package/package.json +16 -16
- package/dist/import-BwakMyyf.js.map +0 -1
|
@@ -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",
|
|
@@ -3030,7 +3044,6 @@ const CANONICAL_TO_OPENCODE_EVENT_NAMES = {
|
|
|
3030
3044
|
postToolUse: "tool.execute.after",
|
|
3031
3045
|
stop: "session.idle",
|
|
3032
3046
|
afterFileEdit: "file.edited",
|
|
3033
|
-
afterShellExecution: "command.executed",
|
|
3034
3047
|
permissionRequest: "permission.asked",
|
|
3035
3048
|
preCompact: "experimental.session.compacting",
|
|
3036
3049
|
postCompact: "session.compacted",
|
|
@@ -4097,10 +4110,22 @@ const ClaudecodePermissionsOverrideSchema = z.looseObject({
|
|
|
4097
4110
|
* category; the shared `permission` block still drives the base permission and
|
|
4098
4111
|
* allow/deny lists. Keyed by canonical category (e.g. `bash`, `edit`).
|
|
4099
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
|
+
*
|
|
4100
4120
|
* @example
|
|
4101
4121
|
* { "permission": { "bash": { "sensitive_patterns": ["rm *", "sudo *"] } } }
|
|
4122
|
+
* @example
|
|
4123
|
+
* { "enabled_tools": ["bash", "read_file", "grep"] }
|
|
4102
4124
|
*/
|
|
4103
|
-
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
|
+
});
|
|
4104
4129
|
/**
|
|
4105
4130
|
* Tool-scoped override block for Cursor CLI. Cursor's `cli.json` carries scalar
|
|
4106
4131
|
* autonomy settings with no canonical permission category — `approvalMode`
|
|
@@ -4172,7 +4197,10 @@ const QwencodePermissionsOverrideSchema = z.looseObject({
|
|
|
4172
4197
|
const ReasonixPermissionsOverrideSchema = z.looseObject({
|
|
4173
4198
|
permission: z.optional(ToolScopedPermissionSchema),
|
|
4174
4199
|
sandbox: z.optional(z.looseObject({})),
|
|
4175
|
-
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()))
|
|
4176
4204
|
});
|
|
4177
4205
|
/**
|
|
4178
4206
|
* Tool-scoped override block for Factory Droid. Factory Droid's `settings.json`
|
|
@@ -4203,12 +4231,69 @@ const FactorydroidPermissionsOverrideSchema = z.looseObject({
|
|
|
4203
4231
|
* `agent_mode_execute_readonly_commands` (a read-only auto-execution boolean).
|
|
4204
4232
|
* Fields placed here are merged into `[agents.profiles]` of Warp's global
|
|
4205
4233
|
* `settings.toml`, while the shared `permission` block continues to drive the
|
|
4206
|
-
*
|
|
4207
|
-
*
|
|
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.
|
|
4208
4240
|
*
|
|
4209
4241
|
* @example
|
|
4210
|
-
* {
|
|
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).
|
|
4211
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
|
+
});
|
|
4212
4297
|
const WarpPermissionsOverrideSchema = z.looseObject({
|
|
4213
4298
|
permission: z.optional(ToolScopedPermissionSchema),
|
|
4214
4299
|
agent_mode_coding_permissions: z.optional(z.enum([
|
|
@@ -4217,7 +4302,8 @@ const WarpPermissionsOverrideSchema = z.looseObject({
|
|
|
4217
4302
|
"allow_reading_specific_files"
|
|
4218
4303
|
])),
|
|
4219
4304
|
agent_mode_coding_file_read_allowlist: z.optional(z.array(z.string())),
|
|
4220
|
-
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)
|
|
4221
4307
|
});
|
|
4222
4308
|
/**
|
|
4223
4309
|
* The actions Junie's allowlist accepts. Verified against the shipped Junie
|
|
@@ -4949,7 +5035,8 @@ var AiDir = class {
|
|
|
4949
5035
|
getDirPath() {
|
|
4950
5036
|
const fullPath = path.join(this.outputRoot, this.relativeDirPath, this.dirName);
|
|
4951
5037
|
const resolvedFull = resolve(fullPath);
|
|
4952
|
-
const
|
|
5038
|
+
const resolvedBase = resolve(this.outputRoot);
|
|
5039
|
+
const rel = relative(resolvedBase, resolvedFull);
|
|
4953
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}"`);
|
|
4954
5041
|
return fullPath;
|
|
4955
5042
|
}
|
|
@@ -5232,6 +5319,9 @@ const RulesyncSubagentFrontmatterSchema = z.looseObject({
|
|
|
5232
5319
|
customInstructions: z.optional(z.string()),
|
|
5233
5320
|
groups: z.optional(z.array(z.unknown()))
|
|
5234
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())) })),
|
|
5235
5325
|
vibe: z.optional(z.looseObject({
|
|
5236
5326
|
agent_type: z.optional(z.enum(["agent", "subagent"])),
|
|
5237
5327
|
display_name: z.optional(z.string()),
|
|
@@ -5371,7 +5461,6 @@ const ROVODEV_SKILLS_DIR_PATH = join(ROVODEV_DIR, "skills");
|
|
|
5371
5461
|
const ROVODEV_SUBAGENTS_DIR_PATH = join(ROVODEV_DIR, "subagents");
|
|
5372
5462
|
const ROVODEV_MODULAR_RULES_DIR_PATH = join(ROVODEV_DIR, ".rulesync", "modular-rules");
|
|
5373
5463
|
const ROVODEV_RULE_FILE_NAME = "AGENTS.md";
|
|
5374
|
-
const ROVODEV_LEGACY_RULE_FILE_NAME = "AGENTS.local.md";
|
|
5375
5464
|
const ROVODEV_MCP_FILE_NAME = "mcp.json";
|
|
5376
5465
|
const ROVODEV_CONFIG_FILE_NAME = "config.yml";
|
|
5377
5466
|
const ROVODEV_AGENTS_SKILLS_DIR_PATH = join(".agents", "skills");
|
|
@@ -6590,6 +6679,7 @@ const KIMI_CODE_CONFIG_SHARED_FILE_KEY = ".kimi-code/config.toml";
|
|
|
6590
6679
|
const KIMI_CODE_HOME_CONFIG_SHARED_FILE_KEY = "config.toml";
|
|
6591
6680
|
const REASONIX_PROJECT_CONFIG_SHARED_FILE_KEY = "reasonix.toml";
|
|
6592
6681
|
const REASONIX_GLOBAL_CONFIG_SHARED_FILE_KEY = ".reasonix/config.toml";
|
|
6682
|
+
const ROVODEV_CONFIG_SHARED_FILE_KEY = ".rovodev/config.yml";
|
|
6593
6683
|
/**
|
|
6594
6684
|
* Build the `SHARED_CONFIG_OWNERSHIP` lookup key from a tool's settable paths.
|
|
6595
6685
|
* Mirrors `sharedFileKey` in `src/lib/shared-file-derive.ts` (kept separate so
|
|
@@ -6929,6 +7019,10 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
6929
7019
|
permissions: {
|
|
6930
7020
|
kind: "replace-owned-keys",
|
|
6931
7021
|
ownedKeys: ["permission"]
|
|
7022
|
+
},
|
|
7023
|
+
rules: {
|
|
7024
|
+
kind: "replace-owned-keys",
|
|
7025
|
+
ownedKeys: ["instructions"]
|
|
6932
7026
|
}
|
|
6933
7027
|
}
|
|
6934
7028
|
},
|
|
@@ -7046,6 +7140,19 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
7046
7140
|
]
|
|
7047
7141
|
}
|
|
7048
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
|
+
}
|
|
7049
7156
|
}
|
|
7050
7157
|
};
|
|
7051
7158
|
/**
|
|
@@ -7467,7 +7574,8 @@ const toolCheckFactories = /* @__PURE__ */ new Map([
|
|
|
7467
7574
|
class: CursorCheck,
|
|
7468
7575
|
meta: {
|
|
7469
7576
|
supportsGlobal: false,
|
|
7470
|
-
filePattern: CURSOR_BUGBOT_FILE_NAME
|
|
7577
|
+
filePattern: CURSOR_BUGBOT_FILE_NAME,
|
|
7578
|
+
committedOutput: true
|
|
7471
7579
|
}
|
|
7472
7580
|
}],
|
|
7473
7581
|
["hermesagent", {
|
|
@@ -7481,7 +7589,8 @@ const toolCheckFactories = /* @__PURE__ */ new Map([
|
|
|
7481
7589
|
class: RovodevCheck,
|
|
7482
7590
|
meta: {
|
|
7483
7591
|
supportsGlobal: false,
|
|
7484
|
-
filePattern: ROVODEV_REVIEW_AGENT_FILE_NAME
|
|
7592
|
+
filePattern: ROVODEV_REVIEW_AGENT_FILE_NAME,
|
|
7593
|
+
committedOutput: true
|
|
7485
7594
|
}
|
|
7486
7595
|
}],
|
|
7487
7596
|
["takt", {
|
|
@@ -9643,7 +9752,8 @@ var GrokcliCommand = class GrokcliCommand extends ToolCommand {
|
|
|
9643
9752
|
static async validateRulesyncCommands({ inputRoot, rulesyncCommands, logger }) {
|
|
9644
9753
|
const commandNames = new Set(rulesyncCommands.filter((command) => this.isTargetedByRulesyncCommand(command)).map((command) => basename(command.getRelativeFilePath(), ".md")));
|
|
9645
9754
|
if (commandNames.size === 0) return;
|
|
9646
|
-
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));
|
|
9647
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.`);
|
|
9648
9758
|
}
|
|
9649
9759
|
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
|
|
@@ -9678,6 +9788,20 @@ var GrokcliCommand = class GrokcliCommand extends ToolCommand {
|
|
|
9678
9788
|
function toolSkillSearchRoots(paths) {
|
|
9679
9789
|
return [paths.relativeDirPath, ...paths.alternativeSkillRoots ?? []];
|
|
9680
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
|
+
}
|
|
9681
9805
|
/** Ordered import roots: managed roots first, followed by read-only discovery roots. */
|
|
9682
9806
|
function toolSkillImportRoots(paths) {
|
|
9683
9807
|
return [...toolSkillSearchRoots(paths), ...paths.importOnlySkillRoots ?? []];
|
|
@@ -11621,7 +11745,7 @@ var RooCommand = class RooCommand extends ToolCommand {
|
|
|
11621
11745
|
};
|
|
11622
11746
|
const body = rulesyncCommand.getBody();
|
|
11623
11747
|
const fileContent = stringifyFrontmatter(body, rooFrontmatter);
|
|
11624
|
-
return new
|
|
11748
|
+
return new this({
|
|
11625
11749
|
outputRoot,
|
|
11626
11750
|
frontmatter: rooFrontmatter,
|
|
11627
11751
|
body,
|
|
@@ -11658,7 +11782,7 @@ var RooCommand = class RooCommand extends ToolCommand {
|
|
|
11658
11782
|
const { frontmatter, body: content } = parseFrontmatter(fileContent, filePath);
|
|
11659
11783
|
const result = RooCommandFrontmatterSchema.safeParse(frontmatter);
|
|
11660
11784
|
if (!result.success) throw new Error(`Invalid frontmatter in ${filePath}: ${formatError(result.error)}`);
|
|
11661
|
-
return new
|
|
11785
|
+
return new this({
|
|
11662
11786
|
outputRoot,
|
|
11663
11787
|
relativeDirPath: RooCommand.getSettablePaths().relativeDirPath,
|
|
11664
11788
|
relativeFilePath,
|
|
@@ -11669,7 +11793,7 @@ var RooCommand = class RooCommand extends ToolCommand {
|
|
|
11669
11793
|
});
|
|
11670
11794
|
}
|
|
11671
11795
|
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
11672
|
-
return new
|
|
11796
|
+
return new this({
|
|
11673
11797
|
outputRoot,
|
|
11674
11798
|
relativeDirPath,
|
|
11675
11799
|
relativeFilePath,
|
|
@@ -12127,6 +12251,39 @@ var WarpCommand = class WarpCommand extends ToolCommand {
|
|
|
12127
12251
|
}
|
|
12128
12252
|
};
|
|
12129
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
|
|
12130
12287
|
//#region src/features/commands/commands-processor.ts
|
|
12131
12288
|
const CommandsProcessorToolTargetSchema = z.enum(commandsProcessorToolTargetTuple);
|
|
12132
12289
|
/**
|
|
@@ -12386,6 +12543,16 @@ const toolCommandFactories = /* @__PURE__ */ new Map([
|
|
|
12386
12543
|
supportsSubdirectory: true
|
|
12387
12544
|
}
|
|
12388
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
|
+
}],
|
|
12389
12556
|
["rovodev", {
|
|
12390
12557
|
class: RovodevCommand,
|
|
12391
12558
|
meta: {
|
|
@@ -13765,16 +13932,17 @@ var ClaudecodeHooks = class extends ToolHooks {
|
|
|
13765
13932
|
const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
13766
13933
|
const existingContent = await readFileContentOrNull(filePath) ?? JSON.stringify({}, null, 2);
|
|
13767
13934
|
const config = rulesyncHooks.getJson();
|
|
13935
|
+
const claudeHooks = canonicalToToolHooks({
|
|
13936
|
+
config,
|
|
13937
|
+
toolOverrideHooks: config.claudecode?.hooks,
|
|
13938
|
+
converterConfig: CLAUDE_CONVERTER_CONFIG,
|
|
13939
|
+
logger
|
|
13940
|
+
});
|
|
13768
13941
|
const fileContent = applySharedConfigPatch({
|
|
13769
13942
|
fileKey: CLAUDE_SETTINGS_SHARED_FILE_KEY,
|
|
13770
13943
|
feature: "hooks",
|
|
13771
13944
|
existingContent,
|
|
13772
|
-
patch: { hooks:
|
|
13773
|
-
config,
|
|
13774
|
-
toolOverrideHooks: config.claudecode?.hooks,
|
|
13775
|
-
converterConfig: CLAUDE_CONVERTER_CONFIG,
|
|
13776
|
-
logger
|
|
13777
|
-
}) },
|
|
13945
|
+
patch: { hooks: claudeHooks },
|
|
13778
13946
|
filePath
|
|
13779
13947
|
});
|
|
13780
13948
|
return new this({
|
|
@@ -15528,6 +15696,30 @@ const NAMED_HOOK_MATCHER_SUBJECTS = {
|
|
|
15528
15696
|
"tool.execute.after": "input.tool",
|
|
15529
15697
|
"experimental.session.compacting": null
|
|
15530
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
|
+
};
|
|
15531
15723
|
function escapeForTemplateLiteral(command) {
|
|
15532
15724
|
return command.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
|
|
15533
15725
|
}
|
|
@@ -15548,15 +15740,19 @@ function validateAndSanitizeMatcher(matcher) {
|
|
|
15548
15740
|
*/
|
|
15549
15741
|
function collectOpencodeStyleHandlers({ effectiveHooks, eventMap, namedEventHandlers, genericEventHandlers }) {
|
|
15550
15742
|
for (const [canonicalEvent, definitions] of Object.entries(effectiveHooks)) {
|
|
15551
|
-
const
|
|
15743
|
+
const shellGate = SHELL_EVENT_TOOL_GATES[canonicalEvent];
|
|
15744
|
+
const toolEvent = shellGate?.toolEvent ?? eventMap[canonicalEvent];
|
|
15552
15745
|
if (!toolEvent) continue;
|
|
15746
|
+
const matcherSupported = !shellGate && Object.hasOwn(NAMED_HOOK_MATCHER_SUBJECTS, toolEvent) && NAMED_HOOK_MATCHER_SUBJECTS[toolEvent] !== null;
|
|
15553
15747
|
const handlers = [];
|
|
15554
15748
|
for (const def of definitions) {
|
|
15555
15749
|
if ((def.type ?? "command") !== "command") continue;
|
|
15556
15750
|
if (!def.command) continue;
|
|
15751
|
+
if (def.matcher && !matcherSupported) continue;
|
|
15557
15752
|
handlers.push({
|
|
15558
15753
|
command: def.command,
|
|
15559
|
-
matcher: def.matcher ? def.matcher : void 0
|
|
15754
|
+
matcher: def.matcher ? def.matcher : void 0,
|
|
15755
|
+
...shellGate ? { toolGate: shellGate.tool } : {}
|
|
15560
15756
|
});
|
|
15561
15757
|
}
|
|
15562
15758
|
if (handlers.length > 0) {
|
|
@@ -15593,7 +15789,11 @@ function buildNamedEventBodyLines(namedEventHandlers) {
|
|
|
15593
15789
|
bodyLines.push(` "${eventName}": async (input) => {`);
|
|
15594
15790
|
for (const handler of handlers) {
|
|
15595
15791
|
const escapedCommand = escapeForTemplateLiteral(handler.command);
|
|
15596
|
-
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) {
|
|
15597
15797
|
const safeMatcher = validateAndSanitizeMatcher(handler.matcher);
|
|
15598
15798
|
bodyLines.push(" {");
|
|
15599
15799
|
bodyLines.push(` const __re = new RegExp("${safeMatcher}");`);
|
|
@@ -17323,7 +17523,8 @@ const toolHooksFactories = /* @__PURE__ */ new Map([
|
|
|
17323
17523
|
},
|
|
17324
17524
|
supportedEvents: KILO_HOOK_EVENTS,
|
|
17325
17525
|
supportedHookTypes: ["command"],
|
|
17326
|
-
supportsMatcher: true
|
|
17526
|
+
supportsMatcher: true,
|
|
17527
|
+
matcherEvents: ["preToolUse", "postToolUse"]
|
|
17327
17528
|
}],
|
|
17328
17529
|
["opencode", {
|
|
17329
17530
|
class: OpencodeHooks,
|
|
@@ -17334,7 +17535,8 @@ const toolHooksFactories = /* @__PURE__ */ new Map([
|
|
|
17334
17535
|
},
|
|
17335
17536
|
supportedEvents: OPENCODE_HOOK_EVENTS,
|
|
17336
17537
|
supportedHookTypes: ["command"],
|
|
17337
|
-
supportsMatcher: true
|
|
17538
|
+
supportsMatcher: true,
|
|
17539
|
+
matcherEvents: ["preToolUse", "postToolUse"]
|
|
17338
17540
|
}],
|
|
17339
17541
|
["pi", {
|
|
17340
17542
|
class: PiHooks,
|
|
@@ -19008,6 +19210,7 @@ const toolIgnoreFactories = /* @__PURE__ */ new Map([
|
|
|
19008
19210
|
["qwencode", { class: QwencodeIgnore }],
|
|
19009
19211
|
["reasonix", { class: ReasonixIgnore }],
|
|
19010
19212
|
["roo", { class: RooIgnore }],
|
|
19213
|
+
["zoocode", { class: RooIgnore }],
|
|
19011
19214
|
["devin", { class: DevinIgnore }],
|
|
19012
19215
|
["vibe", { class: VibeIgnore }],
|
|
19013
19216
|
["warp", { class: WarpIgnore }],
|
|
@@ -20377,7 +20580,6 @@ const resolveCopilotcliServerType = (server) => {
|
|
|
20377
20580
|
case "streamable-http": return "http";
|
|
20378
20581
|
case "stdio":
|
|
20379
20582
|
case "local": return declared;
|
|
20380
|
-
default: break;
|
|
20381
20583
|
}
|
|
20382
20584
|
if (server.command !== void 0) return "stdio";
|
|
20383
20585
|
return isRemoteMcpServer(server) ? "http" : "stdio";
|
|
@@ -22150,9 +22352,12 @@ var KiloMcp = class KiloMcp extends ToolMcp {
|
|
|
22150
22352
|
fileContent = await readFileContentOrNull(jsonPath);
|
|
22151
22353
|
if (fileContent) relativeFilePath = KILO_JSON_FILE_NAME;
|
|
22152
22354
|
}
|
|
22355
|
+
if (instructions.length === 0 && fileContent === null) return null;
|
|
22153
22356
|
const json = fileContent ? parse(fileContent) : {};
|
|
22154
22357
|
const existingInstructions = Array.isArray(json.instructions) ? json.instructions.filter((entry) => typeof entry === "string") : [];
|
|
22155
|
-
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();
|
|
22156
22361
|
return new KiloMcp({
|
|
22157
22362
|
outputRoot,
|
|
22158
22363
|
relativeDirPath: basePaths.relativeDirPath,
|
|
@@ -22161,7 +22366,7 @@ var KiloMcp = class KiloMcp extends ToolMcp {
|
|
|
22161
22366
|
fileKey: sharedConfigFileKey(basePaths),
|
|
22162
22367
|
feature: "rules",
|
|
22163
22368
|
existingContent: fileContent ?? "",
|
|
22164
|
-
patch: { instructions: mergedInstructions },
|
|
22369
|
+
patch: { instructions: mergedInstructions.length > 0 ? mergedInstructions : void 0 },
|
|
22165
22370
|
filePath: join(jsonDir, relativeFilePath)
|
|
22166
22371
|
}),
|
|
22167
22372
|
validate
|
|
@@ -22883,6 +23088,8 @@ var OpencodeMcp = class OpencodeMcp extends ToolMcp {
|
|
|
22883
23088
|
static async fromInstructions({ outputRoot = process.cwd(), instructions, validate = true, global = false }) {
|
|
22884
23089
|
const basePaths = this.getSettablePaths({ global });
|
|
22885
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);
|
|
22886
23093
|
let fileContent = null;
|
|
22887
23094
|
let relativeFilePath = OPENCODE_JSONC_FILE_NAME;
|
|
22888
23095
|
const jsoncPath = join(jsonDir, OPENCODE_JSONC_FILE_NAME);
|
|
@@ -22892,9 +23099,15 @@ var OpencodeMcp = class OpencodeMcp extends ToolMcp {
|
|
|
22892
23099
|
fileContent = await readFileContentOrNull(jsonPath);
|
|
22893
23100
|
if (fileContent) relativeFilePath = OPENCODE_JSON_FILE_NAME;
|
|
22894
23101
|
}
|
|
23102
|
+
if (instructions.length === 0 && fileContent === null) return null;
|
|
22895
23103
|
const json = fileContent ? parse(fileContent) : {};
|
|
22896
23104
|
const existingInstructions = Array.isArray(json.instructions) ? json.instructions.filter((entry) => typeof entry === "string") : [];
|
|
22897
|
-
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();
|
|
22898
23111
|
return new OpencodeMcp({
|
|
22899
23112
|
outputRoot,
|
|
22900
23113
|
relativeDirPath: basePaths.relativeDirPath,
|
|
@@ -22903,7 +23116,7 @@ var OpencodeMcp = class OpencodeMcp extends ToolMcp {
|
|
|
22903
23116
|
fileKey: sharedConfigFileKey(basePaths),
|
|
22904
23117
|
feature: "rules",
|
|
22905
23118
|
existingContent: fileContent ?? "",
|
|
22906
|
-
patch: { instructions: mergedInstructions },
|
|
23119
|
+
patch: { instructions: mergedInstructions.length > 0 ? mergedInstructions : void 0 },
|
|
22907
23120
|
filePath: join(jsonDir, relativeFilePath)
|
|
22908
23121
|
}),
|
|
22909
23122
|
validate
|
|
@@ -23343,9 +23556,13 @@ function parseRovodevMcpJson(fileContent, relativeDirPath, relativeFilePath) {
|
|
|
23343
23556
|
return parsed;
|
|
23344
23557
|
}
|
|
23345
23558
|
/**
|
|
23346
|
-
* Rovodev MCP:
|
|
23347
|
-
*
|
|
23348
|
-
*
|
|
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.
|
|
23349
23566
|
*/
|
|
23350
23567
|
/**
|
|
23351
23568
|
* Rovo Dev documents the per-server transport key as `transport`, with the
|
|
@@ -23371,11 +23588,7 @@ function lookupTransport(map, key) {
|
|
|
23371
23588
|
return Object.hasOwn(map, key) ? map[key] : void 0;
|
|
23372
23589
|
}
|
|
23373
23590
|
function toRovodevServer(name, server, logger) {
|
|
23374
|
-
const { type, transport, disabled, ...rest } = server;
|
|
23375
|
-
if (disabled === true) {
|
|
23376
|
-
logger?.warn(`Rovo Dev MCP: skipping "${name}" because it is disabled and mcp.json has no disable flag.`);
|
|
23377
|
-
return null;
|
|
23378
|
-
}
|
|
23591
|
+
const { type, transport, disabled: _disabled, ...rest } = server;
|
|
23379
23592
|
const declared = typeof transport === "string" ? transport : typeof type === "string" ? type : void 0;
|
|
23380
23593
|
if (declared === void 0) return rest;
|
|
23381
23594
|
const mapped = lookupTransport(CANONICAL_TO_ROVODEV_TRANSPORT, declared);
|
|
@@ -23397,6 +23610,44 @@ function fromRovodevServer(server) {
|
|
|
23397
23610
|
type: mapped
|
|
23398
23611
|
};
|
|
23399
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
|
+
};
|
|
23400
23651
|
var RovodevMcp = class RovodevMcp extends ToolMcp {
|
|
23401
23652
|
json;
|
|
23402
23653
|
constructor(params) {
|
|
@@ -23417,13 +23668,24 @@ var RovodevMcp = class RovodevMcp extends ToolMcp {
|
|
|
23417
23668
|
};
|
|
23418
23669
|
}
|
|
23419
23670
|
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
23420
|
-
if (!global) throw new Error("Rovodev MCP is global-only; use --global to sync ~/.rovodev/mcp.json");
|
|
23421
23671
|
const paths = this.getSettablePaths({ global });
|
|
23422
23672
|
const json = parseRovodevMcpJson(await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "{\"mcpServers\":{}}", paths.relativeDirPath, paths.relativeFilePath);
|
|
23423
23673
|
const newJson = {
|
|
23424
23674
|
...json,
|
|
23425
23675
|
mcpServers: json.mcpServers ?? {}
|
|
23426
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
|
+
}
|
|
23427
23689
|
return new RovodevMcp({
|
|
23428
23690
|
outputRoot,
|
|
23429
23691
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -23434,11 +23696,21 @@ var RovodevMcp = class RovodevMcp extends ToolMcp {
|
|
|
23434
23696
|
});
|
|
23435
23697
|
}
|
|
23436
23698
|
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false, logger }) {
|
|
23437
|
-
if (!global) throw new Error("Rovodev MCP is global-only; use --global to sync ~/.rovodev/mcp.json");
|
|
23438
23699
|
const paths = this.getSettablePaths({ global });
|
|
23439
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
|
+
}
|
|
23440
23707
|
const mcpServers = Object.fromEntries(Object.entries(rulesyncMcp.getMcpServers()).map(([name, server]) => {
|
|
23441
|
-
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);
|
|
23442
23714
|
return converted === null ? null : [name, converted];
|
|
23443
23715
|
}).filter((entry) => entry !== null));
|
|
23444
23716
|
const rovodevConfig = {
|
|
@@ -23454,6 +23726,64 @@ var RovodevMcp = class RovodevMcp extends ToolMcp {
|
|
|
23454
23726
|
global
|
|
23455
23727
|
});
|
|
23456
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
|
+
}
|
|
23457
23787
|
toRulesyncMcp() {
|
|
23458
23788
|
const rawServers = isMcpServers(this.json.mcpServers) ? this.json.mcpServers : {};
|
|
23459
23789
|
const mcpServers = Object.fromEntries(Object.entries(rawServers).filter(([, server]) => isPlainObject$1(server)).map(([name, server]) => [name, fromRovodevServer(server)]));
|
|
@@ -23602,7 +23932,6 @@ function transportOf(server) {
|
|
|
23602
23932
|
case "http":
|
|
23603
23933
|
case "streamable-http":
|
|
23604
23934
|
case "ws": return "http";
|
|
23605
|
-
default: break;
|
|
23606
23935
|
}
|
|
23607
23936
|
if (server.command !== void 0) return "stdio";
|
|
23608
23937
|
if (server.url !== void 0 || server.httpUrl !== void 0) return "http";
|
|
@@ -23630,6 +23959,20 @@ function deriveTransportAllowlist(servers) {
|
|
|
23630
23959
|
return allowlist;
|
|
23631
23960
|
}
|
|
23632
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
|
|
23633
23976
|
//#region src/features/mcp/vibe-mcp.ts
|
|
23634
23977
|
const VIBE_MCP_SERVER_FIELDS = [
|
|
23635
23978
|
"transport",
|
|
@@ -23698,8 +24041,9 @@ var VibeMcp = class VibeMcp extends ToolMcp {
|
|
|
23698
24041
|
global
|
|
23699
24042
|
});
|
|
23700
24043
|
}
|
|
23701
|
-
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false }) {
|
|
24044
|
+
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, logger, global = false }) {
|
|
23702
24045
|
const paths = this.getSettablePaths({ global });
|
|
24046
|
+
if (global) await warnIfGlobalVibeConfigIsShadowed(logger);
|
|
23703
24047
|
const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
23704
24048
|
const existingContent = await readFileContentOrNull(filePath) ?? "";
|
|
23705
24049
|
const existingServers = normalizeMcpServersArray(parseSharedConfig({
|
|
@@ -23708,7 +24052,10 @@ var VibeMcp = class VibeMcp extends ToolMcp {
|
|
|
23708
24052
|
filePath
|
|
23709
24053
|
}).mcp_servers);
|
|
23710
24054
|
const existingByName = new Map(existingServers.map((server) => [server.name, server]));
|
|
23711
|
-
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));
|
|
23712
24059
|
return new VibeMcp({
|
|
23713
24060
|
outputRoot,
|
|
23714
24061
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -23717,7 +24064,7 @@ var VibeMcp = class VibeMcp extends ToolMcp {
|
|
|
23717
24064
|
fileKey: sharedConfigFileKey(paths),
|
|
23718
24065
|
feature: "mcp",
|
|
23719
24066
|
existingContent,
|
|
23720
|
-
patch: { mcp_servers: mcpServers },
|
|
24067
|
+
patch: { mcp_servers: [...mcpServers, ...unmanagedServers] },
|
|
23721
24068
|
filePath
|
|
23722
24069
|
}),
|
|
23723
24070
|
validate,
|
|
@@ -24419,10 +24766,19 @@ const toolMcpFactories = /* @__PURE__ */ new Map([
|
|
|
24419
24766
|
supportsDisabledTools: false
|
|
24420
24767
|
}
|
|
24421
24768
|
}],
|
|
24769
|
+
["zoocode", {
|
|
24770
|
+
class: RooMcp,
|
|
24771
|
+
meta: {
|
|
24772
|
+
supportsProject: true,
|
|
24773
|
+
supportsGlobal: false,
|
|
24774
|
+
supportsEnabledTools: false,
|
|
24775
|
+
supportsDisabledTools: false
|
|
24776
|
+
}
|
|
24777
|
+
}],
|
|
24422
24778
|
["rovodev", {
|
|
24423
24779
|
class: RovodevMcp,
|
|
24424
24780
|
meta: {
|
|
24425
|
-
supportsProject:
|
|
24781
|
+
supportsProject: true,
|
|
24426
24782
|
supportsGlobal: true,
|
|
24427
24783
|
supportsEnabledTools: false,
|
|
24428
24784
|
supportsDisabledTools: false
|
|
@@ -25288,9 +25644,7 @@ function convertRulesyncToAntigravityCliPermissions(config) {
|
|
|
25288
25644
|
case "ask":
|
|
25289
25645
|
ask.push(entry);
|
|
25290
25646
|
break;
|
|
25291
|
-
case "deny":
|
|
25292
|
-
deny.push(entry);
|
|
25293
|
-
break;
|
|
25647
|
+
case "deny": deny.push(entry);
|
|
25294
25648
|
}
|
|
25295
25649
|
}
|
|
25296
25650
|
}
|
|
@@ -25501,9 +25855,7 @@ function convertRulesyncToAntigravityIdePermissions(config) {
|
|
|
25501
25855
|
case "ask":
|
|
25502
25856
|
ask.push(entry);
|
|
25503
25857
|
break;
|
|
25504
|
-
case "deny":
|
|
25505
|
-
deny.push(entry);
|
|
25506
|
-
break;
|
|
25858
|
+
case "deny": deny.push(entry);
|
|
25507
25859
|
}
|
|
25508
25860
|
}
|
|
25509
25861
|
}
|
|
@@ -26235,9 +26587,7 @@ function convertRulesyncToClaudePermissions({ config, logger }) {
|
|
|
26235
26587
|
case "ask":
|
|
26236
26588
|
ask.push(entry);
|
|
26237
26589
|
break;
|
|
26238
|
-
case "deny":
|
|
26239
|
-
deny.push(entry);
|
|
26240
|
-
break;
|
|
26590
|
+
case "deny": deny.push(entry);
|
|
26241
26591
|
}
|
|
26242
26592
|
}
|
|
26243
26593
|
}
|
|
@@ -27487,9 +27837,7 @@ function convertRulesyncToCursorPermissions(config, logger) {
|
|
|
27487
27837
|
case "ask":
|
|
27488
27838
|
logger?.warn(`Cursor CLI permissions do not support the "ask" action. Skipping ${category} rule: ${pattern}`);
|
|
27489
27839
|
break;
|
|
27490
|
-
case "deny":
|
|
27491
|
-
deny.push(entry);
|
|
27492
|
-
break;
|
|
27840
|
+
case "deny": deny.push(entry);
|
|
27493
27841
|
}
|
|
27494
27842
|
}
|
|
27495
27843
|
}
|
|
@@ -27726,9 +28074,7 @@ function convertRulesyncToDevinPermissions(config) {
|
|
|
27726
28074
|
case "ask":
|
|
27727
28075
|
ask.push(entry);
|
|
27728
28076
|
break;
|
|
27729
|
-
case "deny":
|
|
27730
|
-
deny.push(entry);
|
|
27731
|
-
break;
|
|
28077
|
+
case "deny": deny.push(entry);
|
|
27732
28078
|
}
|
|
27733
28079
|
}
|
|
27734
28080
|
}
|
|
@@ -27916,10 +28262,7 @@ function convertRulesyncToFactorydroidPermissions({ config, logger }) {
|
|
|
27916
28262
|
case "allow":
|
|
27917
28263
|
allow.push(pattern);
|
|
27918
28264
|
break;
|
|
27919
|
-
case "deny":
|
|
27920
|
-
deny.push(pattern);
|
|
27921
|
-
break;
|
|
27922
|
-
case "ask": break;
|
|
28265
|
+
case "deny": deny.push(pattern);
|
|
27923
28266
|
}
|
|
27924
28267
|
}
|
|
27925
28268
|
return {
|
|
@@ -28038,10 +28381,11 @@ var GoosePermissions = class GoosePermissions extends ToolPermissions {
|
|
|
28038
28381
|
throw new Error(`Failed to parse existing Goose permission.yaml at ${filePath}: ${formatError(error)}`, { cause: error });
|
|
28039
28382
|
}
|
|
28040
28383
|
const config = isRecord(parsed) ? { ...parsed } : {};
|
|
28041
|
-
|
|
28384
|
+
const userPermission = convertRulesyncToGoosePermissionConfig({
|
|
28042
28385
|
config: rulesyncPermissions.getJson(),
|
|
28043
28386
|
logger
|
|
28044
28387
|
});
|
|
28388
|
+
config[GOOSE_USER_KEY] = userPermission;
|
|
28045
28389
|
return new GoosePermissions({
|
|
28046
28390
|
outputRoot,
|
|
28047
28391
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -30259,9 +30603,7 @@ function convertRulesyncToQwenPermissions(config) {
|
|
|
30259
30603
|
case "ask":
|
|
30260
30604
|
ask.push(entry);
|
|
30261
30605
|
break;
|
|
30262
|
-
case "deny":
|
|
30263
|
-
deny.push(entry);
|
|
30264
|
-
break;
|
|
30606
|
+
case "deny": deny.push(entry);
|
|
30265
30607
|
}
|
|
30266
30608
|
}
|
|
30267
30609
|
}
|
|
@@ -30334,6 +30676,20 @@ function toCanonicalToolName$2(reasonixName) {
|
|
|
30334
30676
|
return REASONIX_TO_CANONICAL_TOOL_NAMES[reasonixName] ?? reasonixName;
|
|
30335
30677
|
}
|
|
30336
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
|
+
/**
|
|
30337
30693
|
* Parse a Reasonix permission entry like "Bash(npm run *)" into tool name and pattern.
|
|
30338
30694
|
* If no parentheses, returns the tool name with "*" as the pattern.
|
|
30339
30695
|
*/
|
|
@@ -30422,10 +30778,22 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
30422
30778
|
const config = rulesyncPermissions.getJson();
|
|
30423
30779
|
const { allow, ask, deny } = convertRulesyncToReasonixPermissions(config);
|
|
30424
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
|
+
]);
|
|
30425
30790
|
const existingPermissions = toReasonixTable(parsed.permissions);
|
|
30426
|
-
const preservedAllow
|
|
30427
|
-
|
|
30428
|
-
|
|
30791
|
+
const { allow: preservedAllow, ask: preservedAsk, deny: preservedDeny } = preserveExistingEntries({
|
|
30792
|
+
existingPermissions,
|
|
30793
|
+
managedToolNames,
|
|
30794
|
+
rawOwnedEntries,
|
|
30795
|
+
logger
|
|
30796
|
+
});
|
|
30429
30797
|
if (logger && managedToolNames.has("Read")) {
|
|
30430
30798
|
const droppedReadDenyEntries = toReasonixStringArray(existingPermissions.deny).filter((entry) => {
|
|
30431
30799
|
const { toolName } = parseReasonixPermissionEntry(entry);
|
|
@@ -30434,17 +30802,22 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
30434
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.`);
|
|
30435
30803
|
}
|
|
30436
30804
|
const mergedPermissions = { ...existingPermissions };
|
|
30437
|
-
|
|
30438
|
-
|
|
30439
|
-
|
|
30440
|
-
|
|
30441
|
-
|
|
30442
|
-
|
|
30443
|
-
|
|
30444
|
-
|
|
30445
|
-
|
|
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
|
+
]);
|
|
30446
30820
|
const patch = { permissions: mergedPermissions };
|
|
30447
|
-
const override = config.reasonix;
|
|
30448
30821
|
if (override?.sandbox !== void 0) patch.sandbox = {
|
|
30449
30822
|
...asReasonixRecord(parsed.sandbox),
|
|
30450
30823
|
...asReasonixRecord(override.sandbox)
|
|
@@ -30475,16 +30848,29 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
30475
30848
|
}
|
|
30476
30849
|
toRulesyncPermissions() {
|
|
30477
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));
|
|
30478
30861
|
const config = convertReasonixToRulesyncPermissions({
|
|
30479
|
-
allow:
|
|
30480
|
-
ask:
|
|
30481
|
-
deny:
|
|
30862
|
+
allow: allowSplit.translated,
|
|
30863
|
+
ask: askSplit.translated,
|
|
30864
|
+
deny: denySplit.translated
|
|
30482
30865
|
});
|
|
30483
30866
|
const sandbox = asReasonixRecord(this.toml.sandbox);
|
|
30484
30867
|
const agentPlanMode = pickReasonixKeys(this.toml.agent, [...REASONIX_OVERRIDE_AGENT_KEYS, ...REASONIX_RETIRED_AGENT_KEYS]);
|
|
30485
30868
|
const reasonixOverride = {};
|
|
30486
30869
|
if (Object.keys(sandbox).length > 0) reasonixOverride.sandbox = sandbox;
|
|
30487
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;
|
|
30488
30874
|
const result = { ...config };
|
|
30489
30875
|
if (Object.keys(reasonixOverride).length > 0) result.reasonix = reasonixOverride;
|
|
30490
30876
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
|
|
@@ -30514,6 +30900,41 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
30514
30900
|
}
|
|
30515
30901
|
};
|
|
30516
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
|
+
/**
|
|
30517
30938
|
* Convert rulesync permissions config to Reasonix allow/ask/deny arrays.
|
|
30518
30939
|
*/
|
|
30519
30940
|
function convertRulesyncToReasonixPermissions(config) {
|
|
@@ -30531,9 +30952,7 @@ function convertRulesyncToReasonixPermissions(config) {
|
|
|
30531
30952
|
case "ask":
|
|
30532
30953
|
ask.push(entry);
|
|
30533
30954
|
break;
|
|
30534
|
-
case "deny":
|
|
30535
|
-
deny.push(entry);
|
|
30536
|
-
break;
|
|
30955
|
+
case "deny": deny.push(entry);
|
|
30537
30956
|
}
|
|
30538
30957
|
}
|
|
30539
30958
|
}
|
|
@@ -30563,7 +30982,6 @@ function convertReasonixToRulesyncPermissions(params) {
|
|
|
30563
30982
|
}
|
|
30564
30983
|
//#endregion
|
|
30565
30984
|
//#region src/features/permissions/rovodev-permissions.ts
|
|
30566
|
-
const ROVODEV_GLOBAL_ONLY_MESSAGE = "Rovodev permissions are global-only; use --global to sync ~/.rovodev/config.yml";
|
|
30567
30985
|
const CATCH_ALL_PATTERN$1 = "*";
|
|
30568
30986
|
const CATEGORY_TO_TOOL_KEYS = {
|
|
30569
30987
|
read: [
|
|
@@ -30623,9 +31041,12 @@ const OWNED_TOOL_PERMISSION_KEYS = [
|
|
|
30623
31041
|
/**
|
|
30624
31042
|
* Permissions adapter for Rovo Dev CLI.
|
|
30625
31043
|
*
|
|
30626
|
-
* Rovo Dev reads tool permissions from the `toolPermissions` block of
|
|
30627
|
-
*
|
|
30628
|
-
*
|
|
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.
|
|
30629
31050
|
*
|
|
30630
31051
|
* Rovo Dev's three levels (`allow`/`ask`/`deny`) are an exact 1:1 with rulesync's
|
|
30631
31052
|
* canonical action enum, so action values pass through verbatim.
|
|
@@ -30672,7 +31093,6 @@ var RovodevPermissions = class RovodevPermissions extends ToolPermissions {
|
|
|
30672
31093
|
};
|
|
30673
31094
|
}
|
|
30674
31095
|
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
30675
|
-
if (!global) throw new Error(ROVODEV_GLOBAL_ONLY_MESSAGE);
|
|
30676
31096
|
const paths = RovodevPermissions.getSettablePaths({ global });
|
|
30677
31097
|
const fileContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "";
|
|
30678
31098
|
return new RovodevPermissions({
|
|
@@ -30681,11 +31101,10 @@ var RovodevPermissions = class RovodevPermissions extends ToolPermissions {
|
|
|
30681
31101
|
relativeFilePath: paths.relativeFilePath,
|
|
30682
31102
|
fileContent,
|
|
30683
31103
|
validate,
|
|
30684
|
-
global
|
|
31104
|
+
global
|
|
30685
31105
|
});
|
|
30686
31106
|
}
|
|
30687
31107
|
static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, logger, global = false }) {
|
|
30688
|
-
if (!global) throw new Error(ROVODEV_GLOBAL_ONLY_MESSAGE);
|
|
30689
31108
|
const paths = RovodevPermissions.getSettablePaths({ global });
|
|
30690
31109
|
const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
30691
31110
|
const existingContent = await readFileContentOrNull(filePath) ?? "";
|
|
@@ -30708,14 +31127,20 @@ var RovodevPermissions = class RovodevPermissions extends ToolPermissions {
|
|
|
30708
31127
|
filePath,
|
|
30709
31128
|
logger
|
|
30710
31129
|
});
|
|
30711
|
-
|
|
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);
|
|
30712
31137
|
return new RovodevPermissions({
|
|
30713
31138
|
outputRoot,
|
|
30714
31139
|
relativeDirPath: paths.relativeDirPath,
|
|
30715
31140
|
relativeFilePath: paths.relativeFilePath,
|
|
30716
|
-
fileContent
|
|
31141
|
+
fileContent,
|
|
30717
31142
|
validate: true,
|
|
30718
|
-
global
|
|
31143
|
+
global
|
|
30719
31144
|
});
|
|
30720
31145
|
}
|
|
30721
31146
|
toRulesyncPermissions() {
|
|
@@ -31252,6 +31677,7 @@ const CANONICAL_TO_VIBE_TOOL_NAMES = {
|
|
|
31252
31677
|
write: "write_file",
|
|
31253
31678
|
webfetch: "web_fetch",
|
|
31254
31679
|
websearch: "web_search",
|
|
31680
|
+
grep: "grep",
|
|
31255
31681
|
agent: "task"
|
|
31256
31682
|
};
|
|
31257
31683
|
const VIBE_TO_CANONICAL_TOOL_NAMES = {
|
|
@@ -31261,6 +31687,7 @@ const VIBE_TO_CANONICAL_TOOL_NAMES = {
|
|
|
31261
31687
|
write_file: "write",
|
|
31262
31688
|
web_fetch: "webfetch",
|
|
31263
31689
|
web_search: "websearch",
|
|
31690
|
+
grep: "grep",
|
|
31264
31691
|
task: "agent"
|
|
31265
31692
|
};
|
|
31266
31693
|
var VibePermissions = class VibePermissions extends ToolPermissions {
|
|
@@ -31298,6 +31725,7 @@ var VibePermissions = class VibePermissions extends ToolPermissions {
|
|
|
31298
31725
|
}
|
|
31299
31726
|
static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, validate = true, logger, global = false }) {
|
|
31300
31727
|
const paths = this.getSettablePaths({ global });
|
|
31728
|
+
if (global) await warnIfGlobalVibeConfigIsShadowed(logger);
|
|
31301
31729
|
const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
31302
31730
|
const existingContent = await readFileContentOrNull(filePath) ?? "";
|
|
31303
31731
|
const config = parseVibeConfig(existingContent);
|
|
@@ -31305,46 +31733,31 @@ var VibePermissions = class VibePermissions extends ToolPermissions {
|
|
|
31305
31733
|
const tools = toVibeToolsRecord(config.tools);
|
|
31306
31734
|
const enabledTools = new Set(toStringArray(config.enabled_tools));
|
|
31307
31735
|
const disabledTools = new Set(toStringArray(config.disabled_tools));
|
|
31736
|
+
const removedEnabledTools = [];
|
|
31308
31737
|
for (const category of Object.keys(permission)) {
|
|
31738
|
+
if (!hasVibeToolName(category)) continue;
|
|
31309
31739
|
const vibeToolName = toVibeToolName(category);
|
|
31310
|
-
enabledTools.delete(vibeToolName);
|
|
31740
|
+
if (enabledTools.delete(vibeToolName)) removedEnabledTools.push(vibeToolName);
|
|
31311
31741
|
disabledTools.delete(vibeToolName);
|
|
31312
31742
|
}
|
|
31313
31743
|
for (const [category, rules] of Object.entries(permission)) {
|
|
31314
|
-
|
|
31315
|
-
|
|
31316
|
-
|
|
31317
|
-
|
|
31318
|
-
const deny = new Set(toStringArray(existingTool.deny ?? existingTool.denylist));
|
|
31319
|
-
for (const [pattern, action] of Object.entries(rules)) {
|
|
31320
|
-
if (pattern === "*") {
|
|
31321
|
-
applyWildcardPermission({
|
|
31322
|
-
action,
|
|
31323
|
-
toolConfig: nextTool
|
|
31324
|
-
});
|
|
31325
|
-
if (action === "deny") {
|
|
31326
|
-
disabledTools.add(vibeToolName);
|
|
31327
|
-
enabledTools.delete(vibeToolName);
|
|
31328
|
-
} else if (action === "allow") {
|
|
31329
|
-
enabledTools.add(vibeToolName);
|
|
31330
|
-
disabledTools.delete(vibeToolName);
|
|
31331
|
-
}
|
|
31332
|
-
continue;
|
|
31333
|
-
}
|
|
31334
|
-
if (action === "ask") {
|
|
31335
|
-
logger?.warn(`Vibe permissions do not support pattern-level "ask" rules. Skipping ${category}: ${pattern}`);
|
|
31336
|
-
continue;
|
|
31337
|
-
}
|
|
31338
|
-
if (action === "allow") allow.add(pattern);
|
|
31339
|
-
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;
|
|
31340
31748
|
}
|
|
31341
|
-
|
|
31342
|
-
|
|
31343
|
-
|
|
31344
|
-
|
|
31345
|
-
|
|
31749
|
+
applyCategoryRules({
|
|
31750
|
+
category,
|
|
31751
|
+
rules,
|
|
31752
|
+
tools,
|
|
31753
|
+
enabledTools,
|
|
31754
|
+
disabledTools,
|
|
31755
|
+
logger
|
|
31756
|
+
});
|
|
31346
31757
|
}
|
|
31347
|
-
|
|
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.`);
|
|
31348
31761
|
return new VibePermissions({
|
|
31349
31762
|
outputRoot,
|
|
31350
31763
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -31355,7 +31768,10 @@ var VibePermissions = class VibePermissions extends ToolPermissions {
|
|
|
31355
31768
|
existingContent,
|
|
31356
31769
|
patch: {
|
|
31357
31770
|
tools,
|
|
31358
|
-
enabled_tools:
|
|
31771
|
+
enabled_tools: resolveEnabledToolsPatch({
|
|
31772
|
+
vibeOverride,
|
|
31773
|
+
enabledTools
|
|
31774
|
+
}),
|
|
31359
31775
|
disabled_tools: disabledTools.size > 0 ? [...disabledTools].toSorted() : void 0
|
|
31360
31776
|
},
|
|
31361
31777
|
filePath
|
|
@@ -31367,7 +31783,7 @@ var VibePermissions = class VibePermissions extends ToolPermissions {
|
|
|
31367
31783
|
toRulesyncPermissions() {
|
|
31368
31784
|
const permission = {};
|
|
31369
31785
|
const vibeOverridePermission = {};
|
|
31370
|
-
|
|
31786
|
+
const enabledToolsList = toStringArray(this.toml.enabled_tools);
|
|
31371
31787
|
for (const tool of toStringArray(this.toml.disabled_tools)) ensurePermission(permission, toCanonicalToolName$1(tool))["*"] = "deny";
|
|
31372
31788
|
for (const [vibeToolName, toolConfig] of Object.entries(toVibeToolsRecord(this.toml.tools))) {
|
|
31373
31789
|
const category = toCanonicalToolName$1(vibeToolName);
|
|
@@ -31380,9 +31796,12 @@ var VibePermissions = class VibePermissions extends ToolPermissions {
|
|
|
31380
31796
|
if (sensitivePatterns.length > 0) vibeOverridePermission[category] = { sensitive_patterns: sensitivePatterns };
|
|
31381
31797
|
}
|
|
31382
31798
|
for (const [category, rules] of Object.entries(permission)) if (Object.keys(rules).length === 0) delete permission[category];
|
|
31383
|
-
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 ? {
|
|
31384
31803
|
permission,
|
|
31385
|
-
vibe:
|
|
31804
|
+
vibe: vibeOverride
|
|
31386
31805
|
} : { permission };
|
|
31387
31806
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(json, null, 2) });
|
|
31388
31807
|
}
|
|
@@ -31417,8 +31836,12 @@ var VibePermissions = class VibePermissions extends ToolPermissions {
|
|
|
31417
31836
|
* list for any category the override names: a present list is set, an empty
|
|
31418
31837
|
* one clears it. Categories not named keep whatever the existing file had.
|
|
31419
31838
|
*/
|
|
31420
|
-
function applyVibeSensitivePatterns(tools, vibeOverride) {
|
|
31839
|
+
function applyVibeSensitivePatterns(tools, vibeOverride, logger) {
|
|
31421
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
|
+
}
|
|
31422
31845
|
const vibeToolName = toVibeToolName(category);
|
|
31423
31846
|
const nextTool = toVibeToolConfig(tools[vibeToolName]);
|
|
31424
31847
|
const patterns = toStringArray(toolOverride.sensitive_patterns);
|
|
@@ -31427,6 +31850,57 @@ function applyVibeSensitivePatterns(tools, vibeOverride) {
|
|
|
31427
31850
|
tools[vibeToolName] = nextTool;
|
|
31428
31851
|
}
|
|
31429
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
|
+
}
|
|
31430
31904
|
function parseVibeConfig(fileContent) {
|
|
31431
31905
|
const parsed = smolToml.parse(fileContent || smolToml.stringify({}));
|
|
31432
31906
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
@@ -31435,6 +31909,17 @@ function parseVibeConfig(fileContent) {
|
|
|
31435
31909
|
function toVibeToolName(category) {
|
|
31436
31910
|
return CANONICAL_TO_VIBE_TOOL_NAMES[category] ?? category;
|
|
31437
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
|
+
}
|
|
31438
31923
|
function toCanonicalToolName$1(vibeToolName) {
|
|
31439
31924
|
return VIBE_TO_CANONICAL_TOOL_NAMES[vibeToolName] ?? vibeToolName;
|
|
31440
31925
|
}
|
|
@@ -31452,7 +31937,7 @@ function toStringArray(value) {
|
|
|
31452
31937
|
function applyWildcardPermission({ action, toolConfig }) {
|
|
31453
31938
|
if (action === "allow") toolConfig.permission = "always";
|
|
31454
31939
|
else if (action === "ask") toolConfig.permission = "ask";
|
|
31455
|
-
else
|
|
31940
|
+
else toolConfig.permission = "never";
|
|
31456
31941
|
}
|
|
31457
31942
|
function fromVibePermission(value) {
|
|
31458
31943
|
if (value === "always" || value === "allow") return "allow";
|
|
@@ -31480,6 +31965,20 @@ const WARP_OVERRIDE_KEYS = [
|
|
|
31480
31965
|
"agent_mode_coding_file_read_allowlist",
|
|
31481
31966
|
"agent_mode_execute_readonly_commands"
|
|
31482
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
|
+
];
|
|
31483
31982
|
/**
|
|
31484
31983
|
* Warp's `settings.toml` lives in a different directory per platform (Stable
|
|
31485
31984
|
* channel). The home directory is resolved by the processor through
|
|
@@ -31534,7 +32033,14 @@ function warpSettingsDir() {
|
|
|
31534
32033
|
* through the `warp` override namespace (see `WarpPermissionsOverrideSchema`):
|
|
31535
32034
|
* on **import** they are lifted from `settings.toml` into the override, and on
|
|
31536
32035
|
* **export** they are merged back into `[agents.profiles]` (the override wins).
|
|
31537
|
-
*
|
|
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.
|
|
31538
32044
|
*
|
|
31539
32045
|
* The `settings.toml` file holds all of Warp's settings, so the
|
|
31540
32046
|
* `[agents.profiles]` block is merged in place and the file is never deleted.
|
|
@@ -31587,7 +32093,11 @@ var WarpPermissions = class WarpPermissions extends ToolPermissions {
|
|
|
31587
32093
|
const agents = isRecord(settings.agents) ? { ...settings.agents } : {};
|
|
31588
32094
|
const profiles = isRecord(agents.profiles) ? { ...agents.profiles } : {};
|
|
31589
32095
|
const override = config.warp;
|
|
31590
|
-
|
|
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
|
+
}
|
|
31591
32101
|
const mergedAllow = uniq(allow.toSorted());
|
|
31592
32102
|
const mergedDeny = uniq(deny.toSorted());
|
|
31593
32103
|
if (mergedAllow.length > 0) profiles[ALLOWLIST_KEY] = mergedAllow;
|
|
@@ -31595,18 +32105,13 @@ var WarpPermissions = class WarpPermissions extends ToolPermissions {
|
|
|
31595
32105
|
if (mergedDeny.length > 0) profiles[DENYLIST_KEY] = mergedDeny;
|
|
31596
32106
|
else delete profiles[DENYLIST_KEY];
|
|
31597
32107
|
agents.profiles = profiles;
|
|
31598
|
-
|
|
31599
|
-
|
|
31600
|
-
|
|
31601
|
-
|
|
31602
|
-
|
|
31603
|
-
|
|
31604
|
-
|
|
31605
|
-
executionProfiles[DEFAULT_PROFILE_KEY] = defaultProfile;
|
|
31606
|
-
agents[EXECUTION_PROFILES_KEY] = executionProfiles;
|
|
31607
|
-
const otherProfileIds = Object.keys(executionProfiles).filter((id) => id !== DEFAULT_PROFILE_KEY);
|
|
31608
|
-
if (mergedDeny.length > 0 && otherProfileIds.length > 0 && logger) logger.warn(`Warp command deny rules were written to the 'default' execution profile only; they are not enforced while another profile (${otherProfileIds.join(", ")}) is active.`);
|
|
31609
|
-
}
|
|
32108
|
+
mergeIntoDefaultExecutionProfile({
|
|
32109
|
+
agents,
|
|
32110
|
+
mergedAllow,
|
|
32111
|
+
mergedDeny,
|
|
32112
|
+
executionProfileOverride,
|
|
32113
|
+
logger
|
|
32114
|
+
});
|
|
31610
32115
|
settings.agents = agents;
|
|
31611
32116
|
return new WarpPermissions({
|
|
31612
32117
|
outputRoot,
|
|
@@ -31634,6 +32139,11 @@ var WarpPermissions = class WarpPermissions extends ToolPermissions {
|
|
|
31634
32139
|
});
|
|
31635
32140
|
const warpOverride = {};
|
|
31636
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
|
+
}
|
|
31637
32147
|
const result = { ...config };
|
|
31638
32148
|
if (Object.keys(warpOverride).length > 0) result.warp = warpOverride;
|
|
31639
32149
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
|
|
@@ -31656,6 +32166,36 @@ var WarpPermissions = class WarpPermissions extends ToolPermissions {
|
|
|
31656
32166
|
}
|
|
31657
32167
|
};
|
|
31658
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
|
+
/**
|
|
31659
32199
|
* Convert rulesync permissions config to Warp command allow/deny regex lists.
|
|
31660
32200
|
* Only the `bash` category maps; `ask` rules and non-`bash` categories are
|
|
31661
32201
|
* dropped (the latter with a warning when they carry `deny` rules).
|
|
@@ -31672,10 +32212,7 @@ function convertRulesyncToWarpPermissions({ config, logger }) {
|
|
|
31672
32212
|
case "allow":
|
|
31673
32213
|
allow.push(pattern);
|
|
31674
32214
|
break;
|
|
31675
|
-
case "deny":
|
|
31676
|
-
deny.push(pattern);
|
|
31677
|
-
break;
|
|
31678
|
-
case "ask": break;
|
|
32215
|
+
case "deny": deny.push(pattern);
|
|
31679
32216
|
}
|
|
31680
32217
|
}
|
|
31681
32218
|
return {
|
|
@@ -32130,7 +32667,7 @@ const toolPermissionsFactories = /* @__PURE__ */ new Map([
|
|
|
32130
32667
|
["rovodev", {
|
|
32131
32668
|
class: RovodevPermissions,
|
|
32132
32669
|
meta: {
|
|
32133
|
-
supportsProject:
|
|
32670
|
+
supportsProject: true,
|
|
32134
32671
|
supportsGlobal: true,
|
|
32135
32672
|
supportsImport: true
|
|
32136
32673
|
}
|
|
@@ -33327,6 +33864,38 @@ var AugmentcodeSkill = class AugmentcodeSkill extends ToolSkill {
|
|
|
33327
33864
|
}
|
|
33328
33865
|
};
|
|
33329
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
|
|
33330
33899
|
//#region src/features/skills/claudecode-skill.ts
|
|
33331
33900
|
const ClaudecodeSkillFrontmatterSchema = z.looseObject({
|
|
33332
33901
|
name: z.string(),
|
|
@@ -33412,6 +33981,41 @@ var ClaudecodeSkill = class extends ToolSkill {
|
|
|
33412
33981
|
alternativeSkillRoots: [CLAUDECODE_SCHEDULED_TASKS_DIR_PATH]
|
|
33413
33982
|
};
|
|
33414
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
|
+
}
|
|
33415
34019
|
getFrontmatter() {
|
|
33416
34020
|
return ClaudecodeSkillFrontmatterSchema.parse(this.requireMainFileFrontmatter());
|
|
33417
34021
|
}
|
|
@@ -36810,7 +37414,8 @@ var TaktSkill = class TaktSkill extends ToolSkill {
|
|
|
36810
37414
|
getDirPath() {
|
|
36811
37415
|
const fullPath = join(this.outputRoot, this.relativeDirPath);
|
|
36812
37416
|
const resolvedFull = resolve(fullPath);
|
|
36813
|
-
const
|
|
37417
|
+
const resolvedBase = resolve(this.outputRoot);
|
|
37418
|
+
const rel = relative(resolvedBase, resolvedFull);
|
|
36814
37419
|
if (rel.startsWith("..") || path.isAbsolute(rel)) throw new Error(`Path traversal detected: Final path escapes outputRoot. outputRoot="${this.outputRoot}", relativeDirPath="${this.relativeDirPath}"`);
|
|
36815
37420
|
return fullPath;
|
|
36816
37421
|
}
|
|
@@ -37360,6 +37965,23 @@ var ZedSkill = class ZedSkill extends ToolSkill {
|
|
|
37360
37965
|
}
|
|
37361
37966
|
};
|
|
37362
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
|
|
37363
37985
|
//#region src/features/skills/skills-processor.ts
|
|
37364
37986
|
const SkillsProcessorToolTargetSchema = z.enum(skillsProcessorToolTargetTuple);
|
|
37365
37987
|
/**
|
|
@@ -37632,6 +38254,14 @@ const toolSkillFactories = /* @__PURE__ */ new Map([
|
|
|
37632
38254
|
supportsGlobal: true
|
|
37633
38255
|
}
|
|
37634
38256
|
}],
|
|
38257
|
+
["zoocode", {
|
|
38258
|
+
class: ZoocodeSkill,
|
|
38259
|
+
meta: {
|
|
38260
|
+
supportsProject: true,
|
|
38261
|
+
supportsSimulated: false,
|
|
38262
|
+
supportsGlobal: true
|
|
38263
|
+
}
|
|
38264
|
+
}],
|
|
37635
38265
|
["rovodev", {
|
|
37636
38266
|
class: RovodevSkill,
|
|
37637
38267
|
meta: {
|
|
@@ -37794,7 +38424,7 @@ var SkillsProcessor = class extends DirFeatureProcessor {
|
|
|
37794
38424
|
for (const root of roots) {
|
|
37795
38425
|
const rootOutputRoot = typeof root === "string" ? this.outputRoot : root.outputRoot;
|
|
37796
38426
|
const relativeDirPath = typeof root === "string" ? root : root.relativeDirPath;
|
|
37797
|
-
const
|
|
38427
|
+
const isLenientRoot = configuredRootPaths.has(relativeDirPath) || factory.meta.lenientImport === true || isAgentSkillsInteropRoot(relativeDirPath);
|
|
37798
38428
|
const skillsDirPath = join(rootOutputRoot, relativeDirPath);
|
|
37799
38429
|
if (!await directoryExists(skillsDirPath)) continue;
|
|
37800
38430
|
const dirPaths = await findFilesByGlobs(join(skillsDirPath, "*"), { type: "dir" });
|
|
@@ -37818,7 +38448,7 @@ var SkillsProcessor = class extends DirFeatureProcessor {
|
|
|
37818
38448
|
global: this.global
|
|
37819
38449
|
});
|
|
37820
38450
|
} catch (error) {
|
|
37821
|
-
if (!
|
|
38451
|
+
if (!isLenientRoot) throw error;
|
|
37822
38452
|
this.logger.warn(`Skipping ${join(relativeDirPath, dirName)}: ${formatError(error)}`);
|
|
37823
38453
|
return null;
|
|
37824
38454
|
}
|
|
@@ -37833,12 +38463,20 @@ var SkillsProcessor = class extends DirFeatureProcessor {
|
|
|
37833
38463
|
const fromFlatFile = factory.class.fromFlatFile;
|
|
37834
38464
|
const directoryStems = new Set(ownedDirNames);
|
|
37835
38465
|
const flatFilePaths = (await findFilesByGlobs(join(skillsDirPath, "*.md"), { type: "file" })).filter((filePath) => !directoryStems.has(basename(filePath, ".md")));
|
|
37836
|
-
const flatSkills = await Promise.all(flatFilePaths.map((filePath) =>
|
|
37837
|
-
|
|
37838
|
-
|
|
37839
|
-
|
|
37840
|
-
|
|
37841
|
-
|
|
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);
|
|
37842
38480
|
for (const skill of flatSkills) {
|
|
37843
38481
|
const skillName = skill.getImportIdentity();
|
|
37844
38482
|
if (seenSkillNames.has(skillName)) continue;
|
|
@@ -37911,14 +38549,15 @@ var SkillsProcessor = class extends DirFeatureProcessor {
|
|
|
37911
38549
|
*/
|
|
37912
38550
|
static getSimulatedConventionSection({ skillList }) {
|
|
37913
38551
|
if (!skillList || skillList.length === 0) return "";
|
|
38552
|
+
const skillListWithAtPrefix = skillList.map((skill) => ({
|
|
38553
|
+
...skill,
|
|
38554
|
+
path: `@${skill.path}`
|
|
38555
|
+
}));
|
|
37914
38556
|
return `## Simulated Skills
|
|
37915
38557
|
|
|
37916
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.
|
|
37917
38559
|
|
|
37918
|
-
${encode({ skillList:
|
|
37919
|
-
...skill,
|
|
37920
|
-
path: `@${skill.path}`
|
|
37921
|
-
})) })}`;
|
|
38560
|
+
${encode({ skillList: skillListWithAtPrefix })}`;
|
|
37922
38561
|
}
|
|
37923
38562
|
/**
|
|
37924
38563
|
* Return the tool targets that this processor supports in global mode
|
|
@@ -40522,7 +41161,8 @@ var HermesagentSubagent = class HermesagentSubagent extends ToolSubagent {
|
|
|
40522
41161
|
return getHermesagentSharedConfigWritePaths();
|
|
40523
41162
|
}
|
|
40524
41163
|
static getSettablePathsForRulesyncSubagent(rulesyncSubagent) {
|
|
40525
|
-
|
|
41164
|
+
const slug = subagentSlug(rulesyncSubagent.getRelativePathFromCwd());
|
|
41165
|
+
return [join(HERMESAGENT_RULESYNC_SUBAGENTS_DIR_PATH, `${slug}.json`)];
|
|
40526
41166
|
}
|
|
40527
41167
|
toRulesyncSubagent() {
|
|
40528
41168
|
const slug = basename(this.getRelativeFilePath(), ".json");
|
|
@@ -41757,7 +42397,7 @@ function stringifyRooModes(modes) {
|
|
|
41757
42397
|
*
|
|
41758
42398
|
* @see https://roocodeinc.github.io/Roo-Code/features/custom-modes
|
|
41759
42399
|
*/
|
|
41760
|
-
var RooSubagent = class
|
|
42400
|
+
var RooSubagent = class extends ToolSubagent {
|
|
41761
42401
|
modes;
|
|
41762
42402
|
constructor({ modes, ...rest }) {
|
|
41763
42403
|
if (rest.validate !== false) {
|
|
@@ -41813,7 +42453,7 @@ var RooSubagent = class RooSubagent extends ToolSubagent {
|
|
|
41813
42453
|
const mode = this.toRooMode(rulesyncSubagent);
|
|
41814
42454
|
bySlug.set(mode.slug, mode);
|
|
41815
42455
|
}
|
|
41816
|
-
return new
|
|
42456
|
+
return new this({
|
|
41817
42457
|
outputRoot,
|
|
41818
42458
|
relativeDirPath: this.getSettablePaths().relativeDirPath,
|
|
41819
42459
|
relativeFilePath: ROO_MODES_FILE_NAME,
|
|
@@ -41892,7 +42532,7 @@ var RooSubagent = class RooSubagent extends ToolSubagent {
|
|
|
41892
42532
|
}
|
|
41893
42533
|
const result = RooModesFileSchema.safeParse(parsed ?? { customModes: [] });
|
|
41894
42534
|
if (!result.success) throw new Error(`Invalid .roomodes in ${filePath}: ${formatError(result.error)}`);
|
|
41895
|
-
return new
|
|
42535
|
+
return new this({
|
|
41896
42536
|
outputRoot,
|
|
41897
42537
|
relativeDirPath: paths.relativeDirPath,
|
|
41898
42538
|
relativeFilePath: basename(relativeFilePath),
|
|
@@ -41901,7 +42541,7 @@ var RooSubagent = class RooSubagent extends ToolSubagent {
|
|
|
41901
42541
|
});
|
|
41902
42542
|
}
|
|
41903
42543
|
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
41904
|
-
return new
|
|
42544
|
+
return new this({
|
|
41905
42545
|
outputRoot,
|
|
41906
42546
|
relativeDirPath,
|
|
41907
42547
|
relativeFilePath,
|
|
@@ -42147,6 +42787,60 @@ var VibeSubagent = class VibeSubagent extends ToolSubagent {
|
|
|
42147
42787
|
}
|
|
42148
42788
|
};
|
|
42149
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
|
|
42150
42844
|
//#region src/features/subagents/subagents-processor.ts
|
|
42151
42845
|
const SubagentsProcessorToolTargetSchema = z.enum(subagentsProcessorToolTargetTuple);
|
|
42152
42846
|
/**
|
|
@@ -42386,6 +43080,14 @@ const toolSubagentFactories = /* @__PURE__ */ new Map([
|
|
|
42386
43080
|
filePattern: ".roomodes"
|
|
42387
43081
|
}
|
|
42388
43082
|
}],
|
|
43083
|
+
["zoocode", {
|
|
43084
|
+
class: ZoocodeSubagent,
|
|
43085
|
+
meta: {
|
|
43086
|
+
supportsSimulated: false,
|
|
43087
|
+
supportsGlobal: false,
|
|
43088
|
+
filePattern: ".roomodes"
|
|
43089
|
+
}
|
|
43090
|
+
}],
|
|
42389
43091
|
["rovodev", {
|
|
42390
43092
|
class: RovodevSubagent,
|
|
42391
43093
|
meta: {
|
|
@@ -42646,11 +43348,13 @@ For example, if the user instructs \`Call planner subagent to plan the refactori
|
|
|
42646
43348
|
//#region src/features/rules/tool-rule.ts
|
|
42647
43349
|
var ToolRule = class extends ToolFile {
|
|
42648
43350
|
root;
|
|
43351
|
+
localRoot;
|
|
42649
43352
|
description;
|
|
42650
43353
|
globs;
|
|
42651
|
-
constructor({ root = false, description, globs, ...rest }) {
|
|
43354
|
+
constructor({ root = false, localRoot = false, description, globs, ...rest }) {
|
|
42652
43355
|
super(rest);
|
|
42653
43356
|
this.root = root;
|
|
43357
|
+
this.localRoot = localRoot;
|
|
42654
43358
|
this.description = description;
|
|
42655
43359
|
this.globs = globs;
|
|
42656
43360
|
}
|
|
@@ -42733,6 +43437,38 @@ var ToolRule = class extends ToolFile {
|
|
|
42733
43437
|
isRoot() {
|
|
42734
43438
|
return this.root;
|
|
42735
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
|
+
}
|
|
42736
43472
|
/**
|
|
42737
43473
|
* Whether this rule must be left out of the root rule's reference/MCP
|
|
42738
43474
|
* instruction listings even though it is a non-root survivor. Used by files
|
|
@@ -42765,31 +43501,6 @@ function buildToolPath(toolDir, subDir, excludeToolDir) {
|
|
|
42765
43501
|
}
|
|
42766
43502
|
//#endregion
|
|
42767
43503
|
//#region src/features/rules/agentsmd-rule.ts
|
|
42768
|
-
/**
|
|
42769
|
-
* Dependency trees never scanned for nested `AGENTS.md` files, at any depth. An
|
|
42770
|
-
* `AGENTS.md` there describes somebody else's project, and neither name is ever
|
|
42771
|
-
* a package name. Hidden directories are excluded separately, because an
|
|
42772
|
-
* `AGENTS.md` inside one is another tool's generated output (rulesync writes
|
|
42773
|
-
* several itself).
|
|
42774
|
-
*/
|
|
42775
|
-
const NESTED_SCAN_EXCLUDED_DIRS_ANY_DEPTH = ["node_modules", "__pycache__"];
|
|
42776
|
-
/**
|
|
42777
|
-
* Build, vendoring and scratch directories, excluded at the **project root
|
|
42778
|
-
* only**. A top-level `build/` is a build directory; `packages/build/` is a
|
|
42779
|
-
* package, and dropping it silently would lose a real subproject.
|
|
42780
|
-
*/
|
|
42781
|
-
const NESTED_SCAN_EXCLUDED_ROOT_DIRS = [
|
|
42782
|
-
"vendor",
|
|
42783
|
-
"third_party",
|
|
42784
|
-
"dist",
|
|
42785
|
-
"build",
|
|
42786
|
-
"out",
|
|
42787
|
-
"target",
|
|
42788
|
-
"coverage",
|
|
42789
|
-
"tmp",
|
|
42790
|
-
"temp",
|
|
42791
|
-
"venv"
|
|
42792
|
-
];
|
|
42793
43504
|
var AgentsMdRule = class AgentsMdRule extends ToolRule {
|
|
42794
43505
|
constructor({ fileContent, root, ...rest }) {
|
|
42795
43506
|
super({
|
|
@@ -42846,7 +43557,8 @@ var AgentsMdRule = class AgentsMdRule extends ToolRule {
|
|
|
42846
43557
|
const normalizedDirPath = relativeDirPath === void 0 ? "." : toPosixPath(relativeDirPath);
|
|
42847
43558
|
const isNested = relativeFilePath === "AGENTS.md" && normalizedDirPath !== "." && normalizedDirPath !== "" && !normalizedDirPath.startsWith(".");
|
|
42848
43559
|
const isRoot = !isNested && relativeFilePath === "AGENTS.md" && (normalizedDirPath === "." || normalizedDirPath === "");
|
|
42849
|
-
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));
|
|
42850
43562
|
return new AgentsMdRule({
|
|
42851
43563
|
outputRoot,
|
|
42852
43564
|
relativeDirPath: isNested ? join(normalizedDirPath) : isRoot ? this.getSettablePaths().root.relativeDirPath : this.getSettablePaths().nonRoot.relativeDirPath,
|
|
@@ -43153,7 +43865,8 @@ var AntigravityCliRule = class AntigravityCliRule extends ToolRule {
|
|
|
43153
43865
|
});
|
|
43154
43866
|
}
|
|
43155
43867
|
if (!paths.nonRoot) throw new Error(`nonRoot path is not set for ${relativeFilePath}`);
|
|
43156
|
-
const
|
|
43868
|
+
const relativePath = join(paths.nonRoot.relativeDirPath, relativeFilePath);
|
|
43869
|
+
const fileContent = await readFileContent(join(outputRoot, relativePath));
|
|
43157
43870
|
return new AntigravityCliRule({
|
|
43158
43871
|
outputRoot,
|
|
43159
43872
|
relativeDirPath: paths.nonRoot.relativeDirPath,
|
|
@@ -43614,7 +44327,8 @@ var AugmentcodeLegacyRule = class AugmentcodeLegacyRule extends ToolRule {
|
|
|
43614
44327
|
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true }) {
|
|
43615
44328
|
const settablePaths = this.getSettablePaths();
|
|
43616
44329
|
const isRoot = relativeFilePath === settablePaths.root.relativeFilePath;
|
|
43617
|
-
const
|
|
44330
|
+
const relativePath = isRoot ? settablePaths.root.relativeFilePath : join(settablePaths.nonRoot.relativeDirPath, relativeFilePath);
|
|
44331
|
+
const fileContent = await readFileContent(join(outputRoot, relativePath));
|
|
43618
44332
|
return new AugmentcodeLegacyRule({
|
|
43619
44333
|
outputRoot,
|
|
43620
44334
|
relativeDirPath: isRoot ? settablePaths.root.relativeDirPath : settablePaths.nonRoot.relativeDirPath,
|
|
@@ -43838,7 +44552,8 @@ var ClaudecodeLegacyRule = class ClaudecodeLegacyRule extends ToolRule {
|
|
|
43838
44552
|
});
|
|
43839
44553
|
}
|
|
43840
44554
|
if (!paths.nonRoot) throw new Error(`nonRoot path is not set for ${relativeFilePath}`);
|
|
43841
|
-
const
|
|
44555
|
+
const relativePath = join(paths.nonRoot.relativeDirPath, relativeFilePath);
|
|
44556
|
+
const fileContent = await readFileContent(join(outputRoot, relativePath));
|
|
43842
44557
|
return new ClaudecodeLegacyRule({
|
|
43843
44558
|
outputRoot,
|
|
43844
44559
|
relativeDirPath: paths.nonRoot.relativeDirPath,
|
|
@@ -43959,7 +44674,8 @@ var ClaudecodeRule = class ClaudecodeRule extends ToolRule {
|
|
|
43959
44674
|
});
|
|
43960
44675
|
}
|
|
43961
44676
|
if (!paths.nonRoot) throw new Error(`nonRoot path is not set for ${relativeFilePath}`);
|
|
43962
|
-
const
|
|
44677
|
+
const relativePath = join(paths.nonRoot.relativeDirPath, relativeFilePath);
|
|
44678
|
+
const filePath = join(outputRoot, relativePath);
|
|
43963
44679
|
const { frontmatter, body: content } = parseFrontmatter(await readFileContent(filePath), filePath);
|
|
43964
44680
|
const result = ClaudecodeRuleFrontmatterSchema.safeParse(frontmatter);
|
|
43965
44681
|
if (!result.success) throw new Error(`Invalid frontmatter in ${filePath}: ${formatError(result.error)}`);
|
|
@@ -44346,7 +45062,8 @@ var CodexcliRule = class CodexcliRule extends ToolRule {
|
|
|
44346
45062
|
}
|
|
44347
45063
|
static async fromFile({ outputRoot = process.cwd(), relativeFilePath: _relativeFilePath, validate = true, global = false }) {
|
|
44348
45064
|
const { root } = this.getSettablePaths({ global });
|
|
44349
|
-
const
|
|
45065
|
+
const relativePath = join(root.relativeDirPath, root.relativeFilePath);
|
|
45066
|
+
const fileContent = await readFileContent(join(outputRoot, relativePath));
|
|
44350
45067
|
return new CodexcliRule({
|
|
44351
45068
|
outputRoot,
|
|
44352
45069
|
relativeDirPath: root.relativeDirPath,
|
|
@@ -44515,7 +45232,8 @@ var CopilotRule = class CopilotRule extends ToolRule {
|
|
|
44515
45232
|
}) : relativeFilePath === paths.root.relativeFilePath;
|
|
44516
45233
|
const resolvedRelativeDirPath = relativeDirPath ?? (isRoot ? paths.root.relativeDirPath : paths.nonRoot?.relativeDirPath ?? paths.root.relativeDirPath);
|
|
44517
45234
|
if (isRoot) {
|
|
44518
|
-
const
|
|
45235
|
+
const relativePath = join(paths.root.relativeDirPath, paths.root.relativeFilePath);
|
|
45236
|
+
const fileContent = await readFileContent(join(outputRoot, relativePath));
|
|
44519
45237
|
return new CopilotRule({
|
|
44520
45238
|
outputRoot,
|
|
44521
45239
|
relativeDirPath: paths.root.relativeDirPath,
|
|
@@ -44527,7 +45245,8 @@ var CopilotRule = class CopilotRule extends ToolRule {
|
|
|
44527
45245
|
});
|
|
44528
45246
|
}
|
|
44529
45247
|
if (!paths.nonRoot) throw new Error(`nonRoot path is not set for ${relativeFilePath}`);
|
|
44530
|
-
const
|
|
45248
|
+
const relativePath = join(resolvedRelativeDirPath, relativeFilePath);
|
|
45249
|
+
const filePath = join(outputRoot, relativePath);
|
|
44531
45250
|
const { frontmatter, body: content } = parseFrontmatter(await readFileContent(filePath), filePath);
|
|
44532
45251
|
const result = CopilotRuleFrontmatterSchema.safeParse(frontmatter);
|
|
44533
45252
|
if (!result.success) throw new Error(`Invalid frontmatter in ${filePath}: ${formatError(result.error)}`);
|
|
@@ -44799,7 +45518,8 @@ var DeepagentsRule = class DeepagentsRule extends ToolRule {
|
|
|
44799
45518
|
}
|
|
44800
45519
|
static async fromFile({ outputRoot = process.cwd(), relativeFilePath: _relativeFilePath, validate = true, global = false }) {
|
|
44801
45520
|
const settablePaths = this.getSettablePaths({ global });
|
|
44802
|
-
const
|
|
45521
|
+
const relativePath = join(settablePaths.root.relativeDirPath, settablePaths.root.relativeFilePath);
|
|
45522
|
+
const fileContent = await readFileContent(join(outputRoot, relativePath));
|
|
44803
45523
|
return new DeepagentsRule({
|
|
44804
45524
|
outputRoot,
|
|
44805
45525
|
relativeDirPath: settablePaths.root.relativeDirPath,
|
|
@@ -45226,7 +45946,8 @@ var FactorydroidRule = class FactorydroidRule extends ToolRule {
|
|
|
45226
45946
|
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
|
|
45227
45947
|
const paths = this.getSettablePaths({ global });
|
|
45228
45948
|
if (relativeFilePath === paths.root.relativeFilePath) {
|
|
45229
|
-
const
|
|
45949
|
+
const relativePath = join(paths.root.relativeDirPath, paths.root.relativeFilePath);
|
|
45950
|
+
const fileContent = await readFileContent(join(outputRoot, relativePath));
|
|
45230
45951
|
return new FactorydroidRule({
|
|
45231
45952
|
outputRoot,
|
|
45232
45953
|
relativeDirPath: paths.root.relativeDirPath,
|
|
@@ -45237,7 +45958,8 @@ var FactorydroidRule = class FactorydroidRule extends ToolRule {
|
|
|
45237
45958
|
});
|
|
45238
45959
|
}
|
|
45239
45960
|
if (!paths.nonRoot) throw new Error(`nonRoot path is not set for ${relativeFilePath}`);
|
|
45240
|
-
const
|
|
45961
|
+
const relativePath = join(paths.nonRoot.relativeDirPath, relativeFilePath);
|
|
45962
|
+
const fileContent = await readFileContent(join(outputRoot, relativePath));
|
|
45241
45963
|
return new FactorydroidRule({
|
|
45242
45964
|
outputRoot,
|
|
45243
45965
|
relativeDirPath: paths.nonRoot.relativeDirPath,
|
|
@@ -45467,7 +46189,8 @@ var HermesagentRule = class HermesagentRule extends ToolRule {
|
|
|
45467
46189
|
}
|
|
45468
46190
|
static async fromFile({ outputRoot = process.cwd(), relativeFilePath: _relativeFilePath, validate = true }) {
|
|
45469
46191
|
const { root } = this.getSettablePaths();
|
|
45470
|
-
const
|
|
46192
|
+
const relativePath = join(root.relativeDirPath, root.relativeFilePath);
|
|
46193
|
+
const fileContent = await readFileContent(join(outputRoot, relativePath));
|
|
45471
46194
|
return new HermesagentRule({
|
|
45472
46195
|
outputRoot,
|
|
45473
46196
|
relativeDirPath: root.relativeDirPath,
|
|
@@ -45578,7 +46301,8 @@ var JunieRule = class JunieRule extends ToolRule {
|
|
|
45578
46301
|
});
|
|
45579
46302
|
}
|
|
45580
46303
|
const relativeDirPath = this.getSettablePaths().root.relativeDirPath;
|
|
45581
|
-
const
|
|
46304
|
+
const relativePath = join(relativeDirPath, relativeFilePath);
|
|
46305
|
+
const fileContent = await readFileContent(join(outputRoot, relativePath));
|
|
45582
46306
|
return new JunieRule({
|
|
45583
46307
|
outputRoot,
|
|
45584
46308
|
relativeDirPath,
|
|
@@ -45678,7 +46402,8 @@ var KiloRule = class KiloRule extends ToolRule {
|
|
|
45678
46402
|
});
|
|
45679
46403
|
}
|
|
45680
46404
|
if (!paths.nonRoot) throw new Error(`nonRoot path is not set for ${relativeFilePath}`);
|
|
45681
|
-
const
|
|
46405
|
+
const relativePath = join(paths.nonRoot.relativeDirPath, relativeFilePath);
|
|
46406
|
+
const fileContent = await readFileContent(join(outputRoot, relativePath));
|
|
45682
46407
|
return new KiloRule({
|
|
45683
46408
|
outputRoot,
|
|
45684
46409
|
relativeDirPath: paths.nonRoot.relativeDirPath,
|
|
@@ -46015,10 +46740,13 @@ var KiroIdeRule = class extends KiroRule {
|
|
|
46015
46740
|
//#region src/features/rules/opencode-rule.ts
|
|
46016
46741
|
var OpenCodeRule = class OpenCodeRule extends ToolRule {
|
|
46017
46742
|
static getSettablePaths({ global, excludeToolDir } = {}) {
|
|
46018
|
-
if (global) return {
|
|
46019
|
-
|
|
46020
|
-
|
|
46021
|
-
|
|
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
|
+
};
|
|
46022
46750
|
return {
|
|
46023
46751
|
root: {
|
|
46024
46752
|
relativeDirPath: ".",
|
|
@@ -46028,7 +46756,7 @@ var OpenCodeRule = class OpenCodeRule extends ToolRule {
|
|
|
46028
46756
|
};
|
|
46029
46757
|
}
|
|
46030
46758
|
static getExtraSharedWritePaths({ global = false } = {}) {
|
|
46031
|
-
return
|
|
46759
|
+
return [OpencodeMcp.getSettablePaths({ global })];
|
|
46032
46760
|
}
|
|
46033
46761
|
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
|
|
46034
46762
|
const paths = this.getSettablePaths({ global });
|
|
@@ -46045,7 +46773,8 @@ var OpenCodeRule = class OpenCodeRule extends ToolRule {
|
|
|
46045
46773
|
});
|
|
46046
46774
|
}
|
|
46047
46775
|
if (!paths.nonRoot) throw new Error(`nonRoot path is not set for ${relativeFilePath}`);
|
|
46048
|
-
const
|
|
46776
|
+
const relativePath = join(paths.nonRoot.relativeDirPath, relativeFilePath);
|
|
46777
|
+
const fileContent = await readFileContent(join(outputRoot, relativePath));
|
|
46049
46778
|
return new OpenCodeRule({
|
|
46050
46779
|
outputRoot,
|
|
46051
46780
|
relativeDirPath: paths.nonRoot.relativeDirPath,
|
|
@@ -46160,7 +46889,8 @@ var PiRule = class PiRule extends ToolRule {
|
|
|
46160
46889
|
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
|
|
46161
46890
|
const { root, appendSystemPrompt } = this.getSettablePaths({ global });
|
|
46162
46891
|
if (relativeFilePath === "APPEND_SYSTEM.md") {
|
|
46163
|
-
const
|
|
46892
|
+
const relativePath = join(appendSystemPrompt.relativeDirPath, appendSystemPrompt.relativeFilePath);
|
|
46893
|
+
const fileContent = await readFileContent(join(outputRoot, relativePath));
|
|
46164
46894
|
return new PiRule({
|
|
46165
46895
|
outputRoot,
|
|
46166
46896
|
relativeDirPath: appendSystemPrompt.relativeDirPath,
|
|
@@ -46171,7 +46901,8 @@ var PiRule = class PiRule extends ToolRule {
|
|
|
46171
46901
|
appendSystemPrompt: true
|
|
46172
46902
|
});
|
|
46173
46903
|
}
|
|
46174
|
-
const
|
|
46904
|
+
const relativePath = join(root.relativeDirPath, root.relativeFilePath);
|
|
46905
|
+
const fileContent = await readFileContent(join(outputRoot, relativePath));
|
|
46175
46906
|
return new PiRule({
|
|
46176
46907
|
outputRoot,
|
|
46177
46908
|
relativeDirPath: root.relativeDirPath,
|
|
@@ -46471,7 +47202,7 @@ var QwencodeRule = class QwencodeRule extends ToolRule {
|
|
|
46471
47202
|
* The personal local context file lives under `.qwen/`, not at the project
|
|
46472
47203
|
* root where the settable root path points, so override the deletion glob.
|
|
46473
47204
|
*/
|
|
46474
|
-
static
|
|
47205
|
+
static getLocalRootFileGlob({ outputRoot, fileName }) {
|
|
46475
47206
|
return join(outputRoot, QWENCODE_DIR, fileName);
|
|
46476
47207
|
}
|
|
46477
47208
|
};
|
|
@@ -46491,9 +47222,51 @@ var ReasonixRule = class ReasonixRule extends ToolRule {
|
|
|
46491
47222
|
relativeFilePath: REASONIX_RULE_FILE_NAME
|
|
46492
47223
|
} };
|
|
46493
47224
|
}
|
|
46494
|
-
|
|
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 }) {
|
|
46495
47256
|
const { root } = this.getSettablePaths({ global });
|
|
46496
|
-
|
|
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));
|
|
46497
47270
|
return new ReasonixRule({
|
|
46498
47271
|
outputRoot,
|
|
46499
47272
|
relativeDirPath: root.relativeDirPath,
|
|
@@ -46505,7 +47278,17 @@ var ReasonixRule = class ReasonixRule extends ToolRule {
|
|
|
46505
47278
|
}
|
|
46506
47279
|
static fromRulesyncRule({ outputRoot = process.cwd(), rulesyncRule, validate = true, global = false }) {
|
|
46507
47280
|
const { root } = this.getSettablePaths({ global });
|
|
46508
|
-
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
|
+
});
|
|
46509
47292
|
return new ReasonixRule({
|
|
46510
47293
|
outputRoot,
|
|
46511
47294
|
relativeDirPath: root.relativeDirPath,
|
|
@@ -46516,7 +47299,22 @@ var ReasonixRule = class ReasonixRule extends ToolRule {
|
|
|
46516
47299
|
});
|
|
46517
47300
|
}
|
|
46518
47301
|
toRulesyncRule() {
|
|
46519
|
-
|
|
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
|
+
});
|
|
46520
47318
|
}
|
|
46521
47319
|
validate() {
|
|
46522
47320
|
return {
|
|
@@ -46686,7 +47484,7 @@ var RooRule = class RooRule extends ToolRule {
|
|
|
46686
47484
|
* Glob for the `separate-local-file` deletion; Roo reads `AGENTS.local.md`
|
|
46687
47485
|
* at the project root, not under `.roo/` (mirrors rovodev).
|
|
46688
47486
|
*/
|
|
46689
|
-
static
|
|
47487
|
+
static getLocalRootFileGlob({ outputRoot, fileName }) {
|
|
46690
47488
|
return join(outputRoot, fileName);
|
|
46691
47489
|
}
|
|
46692
47490
|
};
|
|
@@ -46818,19 +47616,6 @@ var RovodevRule = class RovodevRule extends ToolRule {
|
|
|
46818
47616
|
}));
|
|
46819
47617
|
}
|
|
46820
47618
|
toRulesyncRule() {
|
|
46821
|
-
if (this.getRelativeFilePath() === "AGENTS.local.md") return new RulesyncRule({
|
|
46822
|
-
outputRoot: this.getOutputRoot(),
|
|
46823
|
-
relativeDirPath: RULESYNC_RULES_RELATIVE_DIR_PATH,
|
|
46824
|
-
relativeFilePath: ROVODEV_LEGACY_RULE_FILE_NAME,
|
|
46825
|
-
frontmatter: {
|
|
46826
|
-
targets: ["rovodev"],
|
|
46827
|
-
root: false,
|
|
46828
|
-
localRoot: true,
|
|
46829
|
-
globs: []
|
|
46830
|
-
},
|
|
46831
|
-
body: this.getFileContent(),
|
|
46832
|
-
validate: true
|
|
46833
|
-
});
|
|
46834
47619
|
if (!this.isRoot()) return new RulesyncRule({
|
|
46835
47620
|
outputRoot: this.getOutputRoot(),
|
|
46836
47621
|
relativeDirPath: RULESYNC_RULES_RELATIVE_DIR_PATH,
|
|
@@ -46887,7 +47672,7 @@ var RovodevRule = class RovodevRule extends ToolRule {
|
|
|
46887
47672
|
};
|
|
46888
47673
|
}
|
|
46889
47674
|
/** Glob for the `separate-local-file` deletion; rovodev writes it at project root, not under `.rovodev/`. */
|
|
46890
|
-
static
|
|
47675
|
+
static getLocalRootFileGlob({ outputRoot, fileName }) {
|
|
46891
47676
|
return join(outputRoot, fileName);
|
|
46892
47677
|
}
|
|
46893
47678
|
};
|
|
@@ -47089,7 +47874,8 @@ var WarpRule = class WarpRule extends ToolRule {
|
|
|
47089
47874
|
}
|
|
47090
47875
|
static async fromFile({ outputRoot = process.cwd(), relativeFilePath: _relativeFilePath, validate = true, global = false }) {
|
|
47091
47876
|
const { root } = this.getSettablePaths({ global });
|
|
47092
|
-
const
|
|
47877
|
+
const relativePath = join(root.relativeDirPath, root.relativeFilePath);
|
|
47878
|
+
const fileContent = await readFileContent(join(outputRoot, relativePath));
|
|
47093
47879
|
return new WarpRule({
|
|
47094
47880
|
outputRoot,
|
|
47095
47881
|
relativeDirPath: root.relativeDirPath,
|
|
@@ -47214,6 +48000,27 @@ var ZedRule = class ZedRule extends ToolRule {
|
|
|
47214
48000
|
}
|
|
47215
48001
|
};
|
|
47216
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
|
|
47217
48024
|
//#region src/features/rules/rules-processor.ts
|
|
47218
48025
|
const RulesProcessorToolTargetSchema = z.enum(rulesProcessorToolTargetTuple);
|
|
47219
48026
|
const formatRulePaths = (rules) => rules.map((r) => join(r.getRelativeDirPath(), r.getRelativeFilePath())).join(", ");
|
|
@@ -47476,6 +48283,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
47476
48283
|
supportsGlobal: true,
|
|
47477
48284
|
ruleDiscoveryMode: "toon",
|
|
47478
48285
|
mcpInstructionsRegistrar: OpencodeMcp,
|
|
48286
|
+
mcpInstructionsRegistrarGlobal: true,
|
|
47479
48287
|
collisionPolicy: "compose"
|
|
47480
48288
|
}
|
|
47481
48289
|
}],
|
|
@@ -47540,6 +48348,16 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
47540
48348
|
localRootFileName: "AGENTS.local.md"
|
|
47541
48349
|
}
|
|
47542
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
|
+
}],
|
|
47543
48361
|
["takt", {
|
|
47544
48362
|
class: TaktRule,
|
|
47545
48363
|
meta: {
|
|
@@ -47712,18 +48530,19 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
47712
48530
|
/**
|
|
47713
48531
|
* Non-root rules of some tools are not auto-loaded; the tool's MCP feature
|
|
47714
48532
|
* registers them in its shared config's `instructions` key. The root rule is
|
|
47715
|
-
* auto-loaded and never registered.
|
|
48533
|
+
* auto-loaded and never registered. Global scope opt-in via
|
|
48534
|
+
* `mcpInstructionsRegistrarGlobal`.
|
|
47716
48535
|
*/
|
|
47717
48536
|
async buildMcpInstructionFiles({ toolRules, meta }) {
|
|
47718
|
-
if (!meta.mcpInstructionsRegistrar || this.global) return [];
|
|
48537
|
+
if (!meta.mcpInstructionsRegistrar || this.global && !meta.mcpInstructionsRegistrarGlobal) return [];
|
|
47719
48538
|
const instructionPaths = toolRules.filter((rule) => !rule.isRoot() && !rule.isExcludedFromRootReferences()).map((rule) => toPosixPath(join(rule.getRelativeDirPath(), rule.getRelativeFilePath())));
|
|
47720
|
-
|
|
47721
|
-
return [await meta.mcpInstructionsRegistrar.fromInstructions({
|
|
48539
|
+
const registered = await meta.mcpInstructionsRegistrar.fromInstructions({
|
|
47722
48540
|
outputRoot: this.outputRoot,
|
|
47723
48541
|
instructions: instructionPaths,
|
|
47724
48542
|
validate: true,
|
|
47725
48543
|
global: this.global
|
|
47726
|
-
})
|
|
48544
|
+
});
|
|
48545
|
+
return registered ? [registered] : [];
|
|
47727
48546
|
}
|
|
47728
48547
|
/**
|
|
47729
48548
|
* For tools that don't create a separate conventions rule, prepend the
|
|
@@ -47854,53 +48673,58 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
47854
48673
|
const rootRule = toolRules.find((rule) => rule.isRoot());
|
|
47855
48674
|
if (rootRule) rootRule.setFileContent(rootRule.getFileContent() + "\n\n" + localRootBody);
|
|
47856
48675
|
}
|
|
47857
|
-
buildLocalRootFile({ factory, fileName, body }) {
|
|
48676
|
+
buildLocalRootFile({ factory, fileName, body, relativeDirPath, localRoot = false }) {
|
|
47858
48677
|
if (factory.class === ClaudecodeRule) {
|
|
47859
48678
|
const paths = ClaudecodeRule.getSettablePaths({ global: this.global });
|
|
47860
48679
|
return new ClaudecodeRule({
|
|
47861
48680
|
outputRoot: this.outputRoot,
|
|
47862
|
-
relativeDirPath: paths.root.relativeDirPath,
|
|
48681
|
+
relativeDirPath: relativeDirPath ?? paths.root.relativeDirPath,
|
|
47863
48682
|
relativeFilePath: fileName,
|
|
47864
48683
|
frontmatter: {},
|
|
47865
48684
|
body,
|
|
47866
48685
|
validate: true,
|
|
47867
|
-
root: true
|
|
48686
|
+
root: true,
|
|
48687
|
+
localRoot
|
|
47868
48688
|
});
|
|
47869
48689
|
}
|
|
47870
48690
|
if (factory.class === ClaudecodeLegacyRule) {
|
|
47871
48691
|
const paths = ClaudecodeLegacyRule.getSettablePaths({ global: this.global });
|
|
47872
48692
|
return new ClaudecodeLegacyRule({
|
|
47873
48693
|
outputRoot: this.outputRoot,
|
|
47874
|
-
relativeDirPath: paths.root.relativeDirPath,
|
|
48694
|
+
relativeDirPath: relativeDirPath ?? paths.root.relativeDirPath,
|
|
47875
48695
|
relativeFilePath: fileName,
|
|
47876
48696
|
fileContent: body,
|
|
47877
48697
|
validate: true,
|
|
47878
|
-
root: true
|
|
48698
|
+
root: true,
|
|
48699
|
+
localRoot
|
|
47879
48700
|
});
|
|
47880
48701
|
}
|
|
47881
48702
|
if (factory.class === RovodevRule) return new RovodevRule({
|
|
47882
48703
|
outputRoot: this.outputRoot,
|
|
47883
|
-
relativeDirPath: ".",
|
|
48704
|
+
relativeDirPath: relativeDirPath ?? ".",
|
|
47884
48705
|
relativeFilePath: fileName,
|
|
47885
48706
|
fileContent: body,
|
|
47886
48707
|
validate: true,
|
|
47887
|
-
root: true
|
|
48708
|
+
root: true,
|
|
48709
|
+
localRoot
|
|
47888
48710
|
});
|
|
47889
48711
|
if (factory.class === RooRule) return new RooRule({
|
|
47890
48712
|
outputRoot: this.outputRoot,
|
|
47891
|
-
relativeDirPath: ".",
|
|
48713
|
+
relativeDirPath: relativeDirPath ?? ".",
|
|
47892
48714
|
relativeFilePath: fileName,
|
|
47893
48715
|
fileContent: body,
|
|
47894
48716
|
validate: true,
|
|
47895
|
-
root: true
|
|
48717
|
+
root: true,
|
|
48718
|
+
localRoot
|
|
47896
48719
|
});
|
|
47897
48720
|
if (factory.class === QwencodeRule) return new QwencodeRule({
|
|
47898
48721
|
outputRoot: this.outputRoot,
|
|
47899
|
-
relativeDirPath:
|
|
48722
|
+
relativeDirPath: relativeDirPath ?? ".qwen",
|
|
47900
48723
|
relativeFilePath: fileName,
|
|
47901
48724
|
fileContent: body,
|
|
47902
48725
|
validate: true,
|
|
47903
|
-
root: true
|
|
48726
|
+
root: true,
|
|
48727
|
+
localRoot
|
|
47904
48728
|
});
|
|
47905
48729
|
return null;
|
|
47906
48730
|
}
|
|
@@ -47938,6 +48762,7 @@ As this project's AI coding tool, you must follow the additional conventions bel
|
|
|
47938
48762
|
async convertToolFilesToRulesyncFiles(toolFiles) {
|
|
47939
48763
|
const toolRules = toolFiles.filter((file) => file instanceof ToolRule);
|
|
47940
48764
|
const rulesyncRules = toolRules.map((toolRule) => {
|
|
48765
|
+
if (toolRule.isLocalRoot()) return toolRule.toLocalRootRulesyncRule({ targets: [this.toolTarget] });
|
|
47941
48766
|
return toolRule.toRulesyncRule();
|
|
47942
48767
|
});
|
|
47943
48768
|
const claimedBy = /* @__PURE__ */ new Map();
|
|
@@ -48071,20 +48896,34 @@ As this project's AI coding tool, you must follow the additional conventions bel
|
|
|
48071
48896
|
})();
|
|
48072
48897
|
this.logger.debug(`Found ${rootToolRules.length} root tool rule files`);
|
|
48073
48898
|
const localRootToolRules = await (async () => {
|
|
48074
|
-
if (
|
|
48899
|
+
if (this.global || factory.meta.localRootMode !== "separate-local-file" || !factory.meta.localRootFileName) return [];
|
|
48075
48900
|
const fileName = factory.meta.localRootFileName;
|
|
48076
|
-
|
|
48077
|
-
|
|
48901
|
+
const filePaths = await (async () => {
|
|
48902
|
+
if (factory.class.getLocalRootFileGlob) return await findFilesByGlobs(factory.class.getLocalRootFileGlob({
|
|
48078
48903
|
outputRoot: this.outputRoot,
|
|
48079
48904
|
fileName
|
|
48080
48905
|
}));
|
|
48081
|
-
|
|
48082
|
-
|
|
48083
|
-
|
|
48084
|
-
|
|
48085
|
-
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);
|
|
48086
48925
|
})();
|
|
48087
|
-
this.logger.debug(`Found ${localRootToolRules.length} local root tool rule files
|
|
48926
|
+
this.logger.debug(`Found ${localRootToolRules.length} local root tool rule files`);
|
|
48088
48927
|
const rootMirrorDeletionRules = await (async () => {
|
|
48089
48928
|
const rootMirror = factory.class.getRootMirror?.();
|
|
48090
48929
|
if (!forDeletion || this.global || !rootMirror) return [];
|
|
@@ -48210,13 +49049,14 @@ As this project's AI coding tool, you must follow the additional conventions bel
|
|
|
48210
49049
|
const lines = [];
|
|
48211
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.");
|
|
48212
49051
|
lines.push("");
|
|
48213
|
-
const
|
|
49052
|
+
const rules = toolRulesWithoutRoot.map((toolRule) => {
|
|
48214
49053
|
const frontmatter = toolRule.toRulesyncRule().getFrontmatter();
|
|
48215
49054
|
const rule = { path: `@${toolRule.getRelativePathFromCwd()}` };
|
|
48216
49055
|
if (frontmatter.description) rule.description = frontmatter.description;
|
|
48217
49056
|
if (frontmatter.globs && frontmatter.globs.length > 0) rule.applyTo = frontmatter.globs;
|
|
48218
49057
|
return rule;
|
|
48219
|
-
})
|
|
49058
|
+
});
|
|
49059
|
+
const toonContent = encode({ rules });
|
|
48220
49060
|
lines.push(toonContent);
|
|
48221
49061
|
return lines.join("\n") + "\n\n";
|
|
48222
49062
|
}
|
|
@@ -49378,8 +50218,8 @@ async function generateIgnoreCore(params) {
|
|
|
49378
50218
|
allPaths.push(...result.paths);
|
|
49379
50219
|
if (result.hasDiff) hasDiff = true;
|
|
49380
50220
|
} catch (error) {
|
|
49381
|
-
logger.
|
|
49382
|
-
|
|
50221
|
+
logger.error(`Failed to generate ${toolTarget} ignore files for ${outputRoot}: ${formatError(error)}`);
|
|
50222
|
+
throw error;
|
|
49383
50223
|
}
|
|
49384
50224
|
}
|
|
49385
50225
|
return {
|
|
@@ -49651,8 +50491,8 @@ async function generatePermissionsCore(params) {
|
|
|
49651
50491
|
allPaths.push(...result.paths);
|
|
49652
50492
|
if (result.hasDiff) hasDiff = true;
|
|
49653
50493
|
} catch (error) {
|
|
49654
|
-
logger.
|
|
49655
|
-
|
|
50494
|
+
logger.error(`Failed to generate ${toolTarget} permissions files for ${outputRoot}: ${formatError(error)}`);
|
|
50495
|
+
throw error;
|
|
49656
50496
|
}
|
|
49657
50497
|
}
|
|
49658
50498
|
return {
|
|
@@ -50056,4 +50896,4 @@ async function importChecksCore(params) {
|
|
|
50056
50896
|
//#endregion
|
|
50057
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 };
|
|
50058
50898
|
|
|
50059
|
-
//# sourceMappingURL=import-
|
|
50899
|
+
//# sourceMappingURL=import-BlwypG9v.js.map
|