rulesync 9.6.3 → 9.8.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 +12 -2
- package/dist/cli/index.cjs +10 -21
- package/dist/cli/index.js +10 -21
- package/dist/cli/index.js.map +1 -1
- package/dist/{import-A1yGuzwv.js → import-Dioh9ca8.js} +1135 -348
- package/dist/import-Dioh9ca8.js.map +1 -0
- package/dist/{import-DnpMhZOY.cjs → import-l0o1jVdc.cjs} +1143 -357
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/import-A1yGuzwv.js.map +0 -1
|
@@ -114,22 +114,22 @@ function formatError(error) {
|
|
|
114
114
|
}
|
|
115
115
|
//#endregion
|
|
116
116
|
//#region src/constants/rulesync-paths.ts
|
|
117
|
-
const { join: join$
|
|
117
|
+
const { join: join$250 } = node_path.posix;
|
|
118
118
|
const RULESYNC_CONFIG_RELATIVE_FILE_PATH = "rulesync.jsonc";
|
|
119
119
|
const RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH = "rulesync.local.jsonc";
|
|
120
120
|
const RULESYNC_RELATIVE_DIR_PATH = ".rulesync";
|
|
121
|
-
const RULESYNC_RULES_RELATIVE_DIR_PATH = join$
|
|
122
|
-
const RULESYNC_COMMANDS_RELATIVE_DIR_PATH = join$
|
|
123
|
-
const RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH = join$
|
|
124
|
-
const RULESYNC_MCP_RELATIVE_FILE_PATH = join$
|
|
125
|
-
const RULESYNC_HOOKS_RELATIVE_FILE_PATH = join$
|
|
126
|
-
const RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH = join$
|
|
121
|
+
const RULESYNC_RULES_RELATIVE_DIR_PATH = join$250(RULESYNC_RELATIVE_DIR_PATH, "rules");
|
|
122
|
+
const RULESYNC_COMMANDS_RELATIVE_DIR_PATH = join$250(RULESYNC_RELATIVE_DIR_PATH, "commands");
|
|
123
|
+
const RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH = join$250(RULESYNC_RELATIVE_DIR_PATH, "subagents");
|
|
124
|
+
const RULESYNC_MCP_RELATIVE_FILE_PATH = join$250(RULESYNC_RELATIVE_DIR_PATH, "mcp.json");
|
|
125
|
+
const RULESYNC_HOOKS_RELATIVE_FILE_PATH = join$250(RULESYNC_RELATIVE_DIR_PATH, "hooks.json");
|
|
126
|
+
const RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH = join$250(RULESYNC_RELATIVE_DIR_PATH, "permissions.json");
|
|
127
127
|
const RULESYNC_AIIGNORE_FILE_NAME = ".aiignore";
|
|
128
|
-
const RULESYNC_AIIGNORE_RELATIVE_FILE_PATH = join$
|
|
128
|
+
const RULESYNC_AIIGNORE_RELATIVE_FILE_PATH = join$250(RULESYNC_RELATIVE_DIR_PATH, ".aiignore");
|
|
129
129
|
const RULESYNC_IGNORE_RELATIVE_FILE_PATH = ".rulesyncignore";
|
|
130
130
|
const RULESYNC_OVERVIEW_FILE_NAME = "overview.md";
|
|
131
|
-
const RULESYNC_SKILLS_RELATIVE_DIR_PATH = join$
|
|
132
|
-
const RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH = join$
|
|
131
|
+
const RULESYNC_SKILLS_RELATIVE_DIR_PATH = join$250(RULESYNC_RELATIVE_DIR_PATH, "skills");
|
|
132
|
+
const RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH = join$250(RULESYNC_SKILLS_RELATIVE_DIR_PATH, ".curated");
|
|
133
133
|
const RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH = "rulesync.lock";
|
|
134
134
|
const RULESYNC_MCP_FILE_NAME = "mcp.json";
|
|
135
135
|
const RULESYNC_HOOKS_FILE_NAME = "hooks.json";
|
|
@@ -347,7 +347,8 @@ const hooksProcessorToolTargetTuple = [
|
|
|
347
347
|
"junie",
|
|
348
348
|
"vibe",
|
|
349
349
|
"qwencode",
|
|
350
|
-
"reasonix"
|
|
350
|
+
"reasonix",
|
|
351
|
+
"grokcli"
|
|
351
352
|
];
|
|
352
353
|
const permissionsProcessorToolTargetTuple = [
|
|
353
354
|
"amp",
|
|
@@ -2703,6 +2704,13 @@ const CODEXCLI_MCP_FILE_NAME = "config.toml";
|
|
|
2703
2704
|
const CODEXCLI_RULE_FILE_NAME = "AGENTS.md";
|
|
2704
2705
|
const CODEXCLI_BASH_RULES_FILE_NAME = "rulesync.rules";
|
|
2705
2706
|
const CODEXCLI_OPENAI_YAML_RELATIVE_PATH = (0, node_path.join)("agents", "openai.yaml");
|
|
2707
|
+
const CODEXCLI_OVERRIDE_KEYS = [
|
|
2708
|
+
"approval_policy",
|
|
2709
|
+
"sandbox_mode",
|
|
2710
|
+
"sandbox_workspace_write",
|
|
2711
|
+
"apps",
|
|
2712
|
+
"approvals_reviewer"
|
|
2713
|
+
];
|
|
2706
2714
|
//#endregion
|
|
2707
2715
|
//#region src/features/commands/codexcli-command.ts
|
|
2708
2716
|
const CodexcliCommandFrontmatterSchema = zod_mini.z.looseObject({
|
|
@@ -4082,14 +4090,22 @@ function sanitizeSharedConfigValue(value) {
|
|
|
4082
4090
|
* root follows `invalidRootPolicy`. Syntax errors are wrapped with the file
|
|
4083
4091
|
* path when one is given.
|
|
4084
4092
|
*/
|
|
4085
|
-
function parseSharedConfig({ format, fileContent, filePath, invalidRootPolicy = "coerce-empty" }) {
|
|
4093
|
+
function parseSharedConfig({ format, fileContent, filePath, invalidRootPolicy = "coerce-empty", jsoncParseErrors = "tolerate" }) {
|
|
4086
4094
|
if (fileContent.trim() === "") return {};
|
|
4087
4095
|
const at = filePath === void 0 ? "" : ` at ${filePath}`;
|
|
4088
4096
|
let parsed;
|
|
4089
4097
|
try {
|
|
4090
4098
|
if (format === "yaml") parsed = loadYaml(fileContent);
|
|
4099
|
+
else if (format === "toml") parsed = (0, smol_toml.parse)(fileContent);
|
|
4091
4100
|
else if (format === "json") parsed = JSON.parse(fileContent);
|
|
4092
|
-
else
|
|
4101
|
+
else if (jsoncParseErrors === "error") {
|
|
4102
|
+
const errors = [];
|
|
4103
|
+
parsed = (0, jsonc_parser.parse)(fileContent, errors, { allowTrailingComma: true });
|
|
4104
|
+
if (errors.length > 0) {
|
|
4105
|
+
const details = errors.map((error) => `${(0, jsonc_parser.printParseErrorCode)(error.error)} at offset ${error.offset}`).join(", ");
|
|
4106
|
+
throw new Error(details);
|
|
4107
|
+
}
|
|
4108
|
+
} else parsed = (0, jsonc_parser.parse)(fileContent);
|
|
4093
4109
|
} catch (error) {
|
|
4094
4110
|
throw new Error(`Failed to parse shared config${at}: ${formatError(error)}`, { cause: error });
|
|
4095
4111
|
}
|
|
@@ -4103,13 +4119,15 @@ function parseSharedConfig({ format, fileContent, filePath, invalidRootPolicy =
|
|
|
4103
4119
|
/**
|
|
4104
4120
|
* Serialize a shared config document. YAML output always ends with exactly one
|
|
4105
4121
|
* newline; JSON output matches the 2-space `JSON.stringify` shape the JSON
|
|
4106
|
-
* writers have always emitted (no trailing newline)
|
|
4122
|
+
* writers have always emitted (no trailing newline); TOML output matches the
|
|
4123
|
+
* `smol-toml` `stringify` shape the TOML writers have always emitted.
|
|
4107
4124
|
*/
|
|
4108
4125
|
function stringifySharedConfig({ format, document }) {
|
|
4109
4126
|
if (format === "yaml") return (0, js_yaml.dump)(document, {
|
|
4110
4127
|
noRefs: true,
|
|
4111
4128
|
sortKeys: false
|
|
4112
4129
|
}).trimEnd() + "\n";
|
|
4130
|
+
if (format === "toml") return (0, smol_toml.stringify)(document);
|
|
4113
4131
|
return JSON.stringify(document, null, 2);
|
|
4114
4132
|
}
|
|
4115
4133
|
/**
|
|
@@ -4148,6 +4166,25 @@ function mergeSharedConfigDeep({ base, patch }) {
|
|
|
4148
4166
|
const CLAUDE_SETTINGS_SHARED_FILE_KEY = ".claude/settings.json";
|
|
4149
4167
|
const HERMES_CONFIG_SHARED_FILE_KEY = ".hermes/config.yaml";
|
|
4150
4168
|
const TAKT_CONFIG_SHARED_FILE_KEY = ".takt/config.yaml";
|
|
4169
|
+
const CODEXCLI_CONFIG_SHARED_FILE_KEY = ".codex/config.toml";
|
|
4170
|
+
const GROKCLI_CONFIG_SHARED_FILE_KEY = ".grok/config.toml";
|
|
4171
|
+
const VIBE_CONFIG_SHARED_FILE_KEY = ".vibe/config.toml";
|
|
4172
|
+
const REASONIX_PROJECT_CONFIG_SHARED_FILE_KEY = "reasonix.toml";
|
|
4173
|
+
const REASONIX_GLOBAL_CONFIG_SHARED_FILE_KEY = ".reasonix/config.toml";
|
|
4174
|
+
/**
|
|
4175
|
+
* Build the `SHARED_CONFIG_OWNERSHIP` lookup key from a tool's settable paths.
|
|
4176
|
+
* Mirrors `sharedFileKey` in `src/lib/shared-file-derive.ts` (kept separate so
|
|
4177
|
+
* feature classes don't pull the processor registry through this module and
|
|
4178
|
+
* create an import cycle); the ownership lock-step test keeps the two aligned.
|
|
4179
|
+
* Lets a tool whose file lives at a scope-dependent path (`.zed/settings.json`
|
|
4180
|
+
* vs `.config/zed/settings.json`) resolve its declaration from the settable
|
|
4181
|
+
* paths it already holds.
|
|
4182
|
+
*/
|
|
4183
|
+
const sharedConfigFileKey = ({ relativeDirPath, relativeFilePath }) => {
|
|
4184
|
+
const dir = relativeDirPath.replace(/\\/g, "/").replace(/\/$/, "");
|
|
4185
|
+
const file = relativeFilePath.replace(/\\/g, "/");
|
|
4186
|
+
return dir === "" || dir === "." ? file : `${dir}/${file}`;
|
|
4187
|
+
};
|
|
4151
4188
|
/**
|
|
4152
4189
|
* Who owns what in each gateway-managed shared config file, and which policy
|
|
4153
4190
|
* resolves conflicts. Keys are `dir/file` tokens matching
|
|
@@ -4204,6 +4241,298 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
4204
4241
|
},
|
|
4205
4242
|
permissions: { kind: "deep-merge" }
|
|
4206
4243
|
}
|
|
4244
|
+
},
|
|
4245
|
+
".zed/settings.json": {
|
|
4246
|
+
format: "json",
|
|
4247
|
+
features: {
|
|
4248
|
+
ignore: {
|
|
4249
|
+
kind: "replace-owned-keys",
|
|
4250
|
+
ownedKeys: ["private_files"]
|
|
4251
|
+
},
|
|
4252
|
+
mcp: {
|
|
4253
|
+
kind: "replace-owned-keys",
|
|
4254
|
+
ownedKeys: ["context_servers"]
|
|
4255
|
+
},
|
|
4256
|
+
permissions: {
|
|
4257
|
+
kind: "replace-owned-keys",
|
|
4258
|
+
ownedKeys: ["agent"]
|
|
4259
|
+
}
|
|
4260
|
+
}
|
|
4261
|
+
},
|
|
4262
|
+
".config/zed/settings.json": {
|
|
4263
|
+
format: "json",
|
|
4264
|
+
features: {
|
|
4265
|
+
mcp: {
|
|
4266
|
+
kind: "replace-owned-keys",
|
|
4267
|
+
ownedKeys: ["context_servers"]
|
|
4268
|
+
},
|
|
4269
|
+
permissions: {
|
|
4270
|
+
kind: "replace-owned-keys",
|
|
4271
|
+
ownedKeys: ["agent"]
|
|
4272
|
+
}
|
|
4273
|
+
}
|
|
4274
|
+
},
|
|
4275
|
+
".qwen/settings.json": {
|
|
4276
|
+
format: "json",
|
|
4277
|
+
features: {
|
|
4278
|
+
mcp: {
|
|
4279
|
+
kind: "replace-owned-keys",
|
|
4280
|
+
ownedKeys: ["mcpServers"]
|
|
4281
|
+
},
|
|
4282
|
+
hooks: {
|
|
4283
|
+
kind: "replace-owned-keys",
|
|
4284
|
+
ownedKeys: ["hooks", "disableAllHooks"]
|
|
4285
|
+
},
|
|
4286
|
+
permissions: {
|
|
4287
|
+
kind: "replace-owned-keys",
|
|
4288
|
+
ownedKeys: [
|
|
4289
|
+
"permissions",
|
|
4290
|
+
"tools",
|
|
4291
|
+
"security"
|
|
4292
|
+
]
|
|
4293
|
+
}
|
|
4294
|
+
}
|
|
4295
|
+
},
|
|
4296
|
+
".augment/settings.json": {
|
|
4297
|
+
format: "json",
|
|
4298
|
+
features: {
|
|
4299
|
+
mcp: {
|
|
4300
|
+
kind: "replace-owned-keys",
|
|
4301
|
+
ownedKeys: ["mcpServers"]
|
|
4302
|
+
},
|
|
4303
|
+
hooks: {
|
|
4304
|
+
kind: "replace-owned-keys",
|
|
4305
|
+
ownedKeys: ["hooks"]
|
|
4306
|
+
},
|
|
4307
|
+
permissions: {
|
|
4308
|
+
kind: "replace-owned-keys",
|
|
4309
|
+
ownedKeys: ["toolPermissions"]
|
|
4310
|
+
}
|
|
4311
|
+
}
|
|
4312
|
+
},
|
|
4313
|
+
".devin/config.json": {
|
|
4314
|
+
format: "json",
|
|
4315
|
+
features: {
|
|
4316
|
+
mcp: {
|
|
4317
|
+
kind: "replace-owned-keys",
|
|
4318
|
+
ownedKeys: ["mcpServers"]
|
|
4319
|
+
},
|
|
4320
|
+
permissions: {
|
|
4321
|
+
kind: "replace-owned-keys",
|
|
4322
|
+
ownedKeys: ["permissions"]
|
|
4323
|
+
}
|
|
4324
|
+
}
|
|
4325
|
+
},
|
|
4326
|
+
".config/devin/config.json": {
|
|
4327
|
+
format: "json",
|
|
4328
|
+
features: {
|
|
4329
|
+
mcp: {
|
|
4330
|
+
kind: "replace-owned-keys",
|
|
4331
|
+
ownedKeys: ["mcpServers"]
|
|
4332
|
+
},
|
|
4333
|
+
hooks: {
|
|
4334
|
+
kind: "replace-owned-keys",
|
|
4335
|
+
ownedKeys: ["hooks"]
|
|
4336
|
+
},
|
|
4337
|
+
permissions: {
|
|
4338
|
+
kind: "replace-owned-keys",
|
|
4339
|
+
ownedKeys: ["permissions"]
|
|
4340
|
+
}
|
|
4341
|
+
}
|
|
4342
|
+
},
|
|
4343
|
+
".kiro/agents/default.json": {
|
|
4344
|
+
format: "json",
|
|
4345
|
+
features: {
|
|
4346
|
+
hooks: {
|
|
4347
|
+
kind: "replace-owned-keys",
|
|
4348
|
+
ownedKeys: ["hooks"]
|
|
4349
|
+
},
|
|
4350
|
+
permissions: {
|
|
4351
|
+
kind: "replace-owned-keys",
|
|
4352
|
+
ownedKeys: ["allowedTools", "toolsSettings"]
|
|
4353
|
+
}
|
|
4354
|
+
}
|
|
4355
|
+
},
|
|
4356
|
+
".amp/settings.json": {
|
|
4357
|
+
format: "jsonc",
|
|
4358
|
+
invalidRootPolicy: "error",
|
|
4359
|
+
jsoncParseErrors: "error",
|
|
4360
|
+
features: {
|
|
4361
|
+
mcp: {
|
|
4362
|
+
kind: "replace-owned-keys",
|
|
4363
|
+
ownedKeys: ["amp.mcpServers"]
|
|
4364
|
+
},
|
|
4365
|
+
permissions: {
|
|
4366
|
+
kind: "replace-owned-keys",
|
|
4367
|
+
ownedKeys: [
|
|
4368
|
+
"amp.tools.disable",
|
|
4369
|
+
"amp.permissions",
|
|
4370
|
+
"amp.guardedFiles.allowlist",
|
|
4371
|
+
"amp.dangerouslyAllowAll",
|
|
4372
|
+
"amp.mcpPermissions"
|
|
4373
|
+
]
|
|
4374
|
+
}
|
|
4375
|
+
}
|
|
4376
|
+
},
|
|
4377
|
+
".config/amp/settings.json": {
|
|
4378
|
+
format: "jsonc",
|
|
4379
|
+
invalidRootPolicy: "error",
|
|
4380
|
+
jsoncParseErrors: "error",
|
|
4381
|
+
features: {
|
|
4382
|
+
mcp: {
|
|
4383
|
+
kind: "replace-owned-keys",
|
|
4384
|
+
ownedKeys: ["amp.mcpServers"]
|
|
4385
|
+
},
|
|
4386
|
+
permissions: {
|
|
4387
|
+
kind: "replace-owned-keys",
|
|
4388
|
+
ownedKeys: [
|
|
4389
|
+
"amp.tools.disable",
|
|
4390
|
+
"amp.permissions",
|
|
4391
|
+
"amp.guardedFiles.allowlist",
|
|
4392
|
+
"amp.dangerouslyAllowAll",
|
|
4393
|
+
"amp.mcpPermissions"
|
|
4394
|
+
]
|
|
4395
|
+
}
|
|
4396
|
+
}
|
|
4397
|
+
},
|
|
4398
|
+
"opencode.json": {
|
|
4399
|
+
format: "jsonc",
|
|
4400
|
+
features: {
|
|
4401
|
+
mcp: {
|
|
4402
|
+
kind: "replace-owned-keys",
|
|
4403
|
+
ownedKeys: ["mcp", "tools"]
|
|
4404
|
+
},
|
|
4405
|
+
permissions: {
|
|
4406
|
+
kind: "replace-owned-keys",
|
|
4407
|
+
ownedKeys: ["permission"]
|
|
4408
|
+
},
|
|
4409
|
+
rules: {
|
|
4410
|
+
kind: "replace-owned-keys",
|
|
4411
|
+
ownedKeys: ["instructions"]
|
|
4412
|
+
}
|
|
4413
|
+
}
|
|
4414
|
+
},
|
|
4415
|
+
".config/opencode/opencode.json": {
|
|
4416
|
+
format: "jsonc",
|
|
4417
|
+
features: {
|
|
4418
|
+
mcp: {
|
|
4419
|
+
kind: "replace-owned-keys",
|
|
4420
|
+
ownedKeys: ["mcp", "tools"]
|
|
4421
|
+
},
|
|
4422
|
+
permissions: {
|
|
4423
|
+
kind: "replace-owned-keys",
|
|
4424
|
+
ownedKeys: ["permission"]
|
|
4425
|
+
}
|
|
4426
|
+
}
|
|
4427
|
+
},
|
|
4428
|
+
"kilo.json": {
|
|
4429
|
+
format: "jsonc",
|
|
4430
|
+
features: {
|
|
4431
|
+
mcp: {
|
|
4432
|
+
kind: "replace-owned-keys",
|
|
4433
|
+
ownedKeys: ["mcp", "tools"]
|
|
4434
|
+
},
|
|
4435
|
+
rules: {
|
|
4436
|
+
kind: "replace-owned-keys",
|
|
4437
|
+
ownedKeys: ["instructions"]
|
|
4438
|
+
}
|
|
4439
|
+
}
|
|
4440
|
+
},
|
|
4441
|
+
".config/kilo/kilo.json": {
|
|
4442
|
+
format: "jsonc",
|
|
4443
|
+
features: { mcp: {
|
|
4444
|
+
kind: "replace-owned-keys",
|
|
4445
|
+
ownedKeys: ["mcp", "tools"]
|
|
4446
|
+
} }
|
|
4447
|
+
},
|
|
4448
|
+
[CODEXCLI_CONFIG_SHARED_FILE_KEY]: {
|
|
4449
|
+
format: "toml",
|
|
4450
|
+
features: {
|
|
4451
|
+
hooks: {
|
|
4452
|
+
kind: "replace-owned-keys",
|
|
4453
|
+
ownedKeys: ["features"]
|
|
4454
|
+
},
|
|
4455
|
+
mcp: {
|
|
4456
|
+
kind: "replace-owned-keys",
|
|
4457
|
+
ownedKeys: ["mcp_servers"]
|
|
4458
|
+
},
|
|
4459
|
+
permissions: {
|
|
4460
|
+
kind: "replace-owned-keys",
|
|
4461
|
+
ownedKeys: [
|
|
4462
|
+
"permissions",
|
|
4463
|
+
"default_permissions",
|
|
4464
|
+
...CODEXCLI_OVERRIDE_KEYS
|
|
4465
|
+
]
|
|
4466
|
+
}
|
|
4467
|
+
}
|
|
4468
|
+
},
|
|
4469
|
+
[GROKCLI_CONFIG_SHARED_FILE_KEY]: {
|
|
4470
|
+
format: "toml",
|
|
4471
|
+
features: {
|
|
4472
|
+
mcp: {
|
|
4473
|
+
kind: "replace-owned-keys",
|
|
4474
|
+
ownedKeys: ["mcp_servers"]
|
|
4475
|
+
},
|
|
4476
|
+
permissions: {
|
|
4477
|
+
kind: "replace-owned-keys",
|
|
4478
|
+
ownedKeys: ["permission", "ui"]
|
|
4479
|
+
}
|
|
4480
|
+
}
|
|
4481
|
+
},
|
|
4482
|
+
[VIBE_CONFIG_SHARED_FILE_KEY]: {
|
|
4483
|
+
format: "toml",
|
|
4484
|
+
features: {
|
|
4485
|
+
hooks: {
|
|
4486
|
+
kind: "replace-owned-keys",
|
|
4487
|
+
ownedKeys: ["enable_experimental_hooks"]
|
|
4488
|
+
},
|
|
4489
|
+
mcp: {
|
|
4490
|
+
kind: "replace-owned-keys",
|
|
4491
|
+
ownedKeys: ["mcp_servers"]
|
|
4492
|
+
},
|
|
4493
|
+
permissions: {
|
|
4494
|
+
kind: "replace-owned-keys",
|
|
4495
|
+
ownedKeys: [
|
|
4496
|
+
"tools",
|
|
4497
|
+
"enabled_tools",
|
|
4498
|
+
"disabled_tools"
|
|
4499
|
+
]
|
|
4500
|
+
}
|
|
4501
|
+
}
|
|
4502
|
+
},
|
|
4503
|
+
[REASONIX_PROJECT_CONFIG_SHARED_FILE_KEY]: {
|
|
4504
|
+
format: "toml",
|
|
4505
|
+
features: {
|
|
4506
|
+
mcp: {
|
|
4507
|
+
kind: "replace-owned-keys",
|
|
4508
|
+
ownedKeys: ["plugins"]
|
|
4509
|
+
},
|
|
4510
|
+
permissions: {
|
|
4511
|
+
kind: "replace-owned-keys",
|
|
4512
|
+
ownedKeys: [
|
|
4513
|
+
"permissions",
|
|
4514
|
+
"sandbox",
|
|
4515
|
+
"agent"
|
|
4516
|
+
]
|
|
4517
|
+
}
|
|
4518
|
+
}
|
|
4519
|
+
},
|
|
4520
|
+
[REASONIX_GLOBAL_CONFIG_SHARED_FILE_KEY]: {
|
|
4521
|
+
format: "toml",
|
|
4522
|
+
features: {
|
|
4523
|
+
mcp: {
|
|
4524
|
+
kind: "replace-owned-keys",
|
|
4525
|
+
ownedKeys: ["plugins"]
|
|
4526
|
+
},
|
|
4527
|
+
permissions: {
|
|
4528
|
+
kind: "replace-owned-keys",
|
|
4529
|
+
ownedKeys: [
|
|
4530
|
+
"permissions",
|
|
4531
|
+
"sandbox",
|
|
4532
|
+
"agent"
|
|
4533
|
+
]
|
|
4534
|
+
}
|
|
4535
|
+
}
|
|
4207
4536
|
}
|
|
4208
4537
|
};
|
|
4209
4538
|
/**
|
|
@@ -4224,17 +4553,20 @@ function applySharedConfigPatch({ fileKey, feature, existingContent, patch, file
|
|
|
4224
4553
|
format: declaration.format,
|
|
4225
4554
|
fileContent: existingContent,
|
|
4226
4555
|
filePath,
|
|
4227
|
-
...declaration.invalidRootPolicy !== void 0 && { invalidRootPolicy: declaration.invalidRootPolicy }
|
|
4556
|
+
...declaration.invalidRootPolicy !== void 0 && { invalidRootPolicy: declaration.invalidRootPolicy },
|
|
4557
|
+
...declaration.jsoncParseErrors !== void 0 && { jsoncParseErrors: declaration.jsoncParseErrors }
|
|
4228
4558
|
});
|
|
4229
4559
|
if (policy.kind === "replace-owned-keys") {
|
|
4230
4560
|
const unowned = Object.keys(patch).filter((key) => !policy.ownedKeys.includes(key));
|
|
4231
4561
|
if (unowned.length > 0) throw new Error(`Feature '${feature}' tried to write undeclared keys [${unowned.join(", ")}] to '${fileKey}'; extend its ownedKeys declaration if that ownership is intended.`);
|
|
4562
|
+
const document = mergeSharedConfigShallow({
|
|
4563
|
+
base,
|
|
4564
|
+
patch
|
|
4565
|
+
});
|
|
4566
|
+
for (const [key, value] of Object.entries(patch)) if (value === void 0) delete document[key];
|
|
4232
4567
|
return stringifySharedConfig({
|
|
4233
4568
|
format: declaration.format,
|
|
4234
|
-
document
|
|
4235
|
-
base,
|
|
4236
|
-
patch
|
|
4237
|
-
})
|
|
4569
|
+
document
|
|
4238
4570
|
});
|
|
4239
4571
|
}
|
|
4240
4572
|
const merged = mergeSharedConfigDeep({
|
|
@@ -5964,10 +6296,13 @@ const CLAUDE_HOOK_EVENTS = [
|
|
|
5964
6296
|
* Hook events supported by Devin Local (native `.devin/` hooks).
|
|
5965
6297
|
*
|
|
5966
6298
|
* Devin Local adopts a Claude-Code-style lifecycle hooks surface. It documents
|
|
5967
|
-
*
|
|
5968
|
-
* `UserPromptSubmit`, `Stop`, `SessionStart`,
|
|
6299
|
+
* eight events: `PreToolUse`, `PostToolUse`, `PermissionRequest`,
|
|
6300
|
+
* `UserPromptSubmit`, `Stop`, `SessionStart`, `SessionEnd`, and
|
|
6301
|
+
* `PostCompaction` (fires after context compaction; added in the Devin CLI
|
|
6302
|
+
* stable changelog — https://docs.devin.ai/cli/changelog/stable). The
|
|
5969
6303
|
* tool/permission events (`PreToolUse`/`PostToolUse`/`PermissionRequest`) carry
|
|
5970
|
-
* a `matcher` (regex against `tool_name`); the session/turn events
|
|
6304
|
+
* a `matcher` (regex against `tool_name`); the session/turn/compaction events
|
|
6305
|
+
* do not.
|
|
5971
6306
|
*
|
|
5972
6307
|
* Hooks live in `.devin/hooks.v1.json` (project, standalone — the hooks object
|
|
5973
6308
|
* is the entire file) or under the `"hooks"` key of `.devin/config.json` /
|
|
@@ -5982,7 +6317,8 @@ const DEVIN_HOOK_EVENTS = [
|
|
|
5982
6317
|
"postToolUse",
|
|
5983
6318
|
"beforeSubmitPrompt",
|
|
5984
6319
|
"stop",
|
|
5985
|
-
"permissionRequest"
|
|
6320
|
+
"permissionRequest",
|
|
6321
|
+
"postCompact"
|
|
5986
6322
|
];
|
|
5987
6323
|
/** Hook events supported by OpenCode. */
|
|
5988
6324
|
const OPENCODE_HOOK_EVENTS = [
|
|
@@ -6176,7 +6512,7 @@ const ANTIGRAVITY_HOOK_EVENTS = [
|
|
|
6176
6512
|
* Hook events supported by AugmentCode (Auggie CLI).
|
|
6177
6513
|
* Auggie mirrors Claude Code's lifecycle hooks but exposes a smaller set:
|
|
6178
6514
|
* PreToolUse / PostToolUse (tool events, matcher-aware) plus the
|
|
6179
|
-
* SessionStart / SessionEnd / Stop session events (no matcher).
|
|
6515
|
+
* SessionStart / SessionEnd / Stop / Notification session events (no matcher).
|
|
6180
6516
|
* @see https://docs.augmentcode.com/cli/hooks
|
|
6181
6517
|
*/
|
|
6182
6518
|
const AUGMENTCODE_HOOK_EVENTS = [
|
|
@@ -6184,7 +6520,8 @@ const AUGMENTCODE_HOOK_EVENTS = [
|
|
|
6184
6520
|
"postToolUse",
|
|
6185
6521
|
"sessionStart",
|
|
6186
6522
|
"sessionEnd",
|
|
6187
|
-
"stop"
|
|
6523
|
+
"stop",
|
|
6524
|
+
"notification"
|
|
6188
6525
|
];
|
|
6189
6526
|
/**
|
|
6190
6527
|
* Hook events supported by Mistral Vibe (mistral-vibe).
|
|
@@ -6236,6 +6573,10 @@ const JUNIE_HOOK_EVENTS = [
|
|
|
6236
6573
|
* The Qwen-specific events
|
|
6237
6574
|
* `TodoCreated`, `TodoCompleted`, and `StopFailure` map to the canonical
|
|
6238
6575
|
* `todoCreated`, `todoCompleted`, and `stopFailure` events respectively.
|
|
6576
|
+
* Qwen's `HookEventName` enum (`packages/core/src/hooks/types.ts`) also documents
|
|
6577
|
+
* `PostToolBatch`, `UserPromptExpansion`, `PermissionDenied`, and
|
|
6578
|
+
* `InstructionsLoaded`, which map to the canonical `postToolBatch`,
|
|
6579
|
+
* `userPromptExpansion`, `permissionDenied`, and `instructionsLoaded` events.
|
|
6239
6580
|
* @see https://github.com/QwenLM/qwen-code/blob/main/docs/users/features/hooks.md
|
|
6240
6581
|
*/
|
|
6241
6582
|
const QWENCODE_HOOK_EVENTS = [
|
|
@@ -6244,7 +6585,9 @@ const QWENCODE_HOOK_EVENTS = [
|
|
|
6244
6585
|
"preToolUse",
|
|
6245
6586
|
"postToolUse",
|
|
6246
6587
|
"postToolUseFailure",
|
|
6588
|
+
"postToolBatch",
|
|
6247
6589
|
"beforeSubmitPrompt",
|
|
6590
|
+
"userPromptExpansion",
|
|
6248
6591
|
"stop",
|
|
6249
6592
|
"stopFailure",
|
|
6250
6593
|
"subagentStart",
|
|
@@ -6252,7 +6595,9 @@ const QWENCODE_HOOK_EVENTS = [
|
|
|
6252
6595
|
"preCompact",
|
|
6253
6596
|
"postCompact",
|
|
6254
6597
|
"permissionRequest",
|
|
6598
|
+
"permissionDenied",
|
|
6255
6599
|
"notification",
|
|
6600
|
+
"instructionsLoaded",
|
|
6256
6601
|
"todoCreated",
|
|
6257
6602
|
"todoCompleted"
|
|
6258
6603
|
];
|
|
@@ -6262,11 +6607,11 @@ const QWENCODE_HOOK_EVENTS = [
|
|
|
6262
6607
|
* Reasonix's `.reasonix/settings.json` (project) / `~/.reasonix/settings.json`
|
|
6263
6608
|
* (global) documents a ten-event surface (`PreToolUse`, `PostToolUse`,
|
|
6264
6609
|
* `UserPromptSubmit`, `Stop`, `PostLLMCall`, `SessionStart`, `SessionEnd`,
|
|
6265
|
-
* `SubagentStop`, `Notification`, `PreCompact`).
|
|
6266
|
-
*
|
|
6610
|
+
* `SubagentStop`, `Notification`, `PreCompact`). All ten have a clean canonical
|
|
6611
|
+
* equivalent and are mapped: `PreToolUse`, `PostToolUse`,
|
|
6267
6612
|
* `UserPromptSubmit` ← `beforeSubmitPrompt`, `Stop`, `SessionStart`,
|
|
6268
|
-
* `SessionEnd`, `SubagentStop`,
|
|
6269
|
-
*
|
|
6613
|
+
* `SessionEnd`, `SubagentStop`, `PostLLMCall` ← `postModelInvocation`,
|
|
6614
|
+
* `Notification` ← `notification`, and `PreCompact` ← `preCompact`.
|
|
6270
6615
|
* `match` (Reasonix's matcher field name) is honored only on
|
|
6271
6616
|
* `PreToolUse`/`PostToolUse`, matching the canonical `matcher` field's
|
|
6272
6617
|
* tool-event scoping used by other adapters.
|
|
@@ -6280,7 +6625,39 @@ const REASONIX_HOOK_EVENTS = [
|
|
|
6280
6625
|
"sessionStart",
|
|
6281
6626
|
"sessionEnd",
|
|
6282
6627
|
"subagentStop",
|
|
6283
|
-
"postModelInvocation"
|
|
6628
|
+
"postModelInvocation",
|
|
6629
|
+
"notification",
|
|
6630
|
+
"preCompact"
|
|
6631
|
+
];
|
|
6632
|
+
/**
|
|
6633
|
+
* Hook events supported by Grok CLI (xAI Grok Build).
|
|
6634
|
+
*
|
|
6635
|
+
* Grok Build documents a Claude-Code-compatible hooks surface with fourteen
|
|
6636
|
+
* PascalCase events, all of which map 1:1 onto an existing canonical arm:
|
|
6637
|
+
* `SessionStart`, `SessionEnd`, `UserPromptSubmit` ← `beforeSubmitPrompt`,
|
|
6638
|
+
* `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PermissionDenied`,
|
|
6639
|
+
* `Stop`, `StopFailure`, `Notification`, `SubagentStart`, `SubagentStop`,
|
|
6640
|
+
* `PreCompact`, `PostCompact`. A `matcher` (a regex tested against the tool
|
|
6641
|
+
* name) is meaningful only on the tool-name events (`PreToolUse`, `PostToolUse`,
|
|
6642
|
+
* `PostToolUseFailure`, `PermissionDenied`), matching Claude Code's semantics;
|
|
6643
|
+
* the remaining lifecycle events are matcher-less.
|
|
6644
|
+
* @see https://docs.x.ai/build/features/hooks
|
|
6645
|
+
*/
|
|
6646
|
+
const GROKCLI_HOOK_EVENTS = [
|
|
6647
|
+
"sessionStart",
|
|
6648
|
+
"sessionEnd",
|
|
6649
|
+
"beforeSubmitPrompt",
|
|
6650
|
+
"preToolUse",
|
|
6651
|
+
"postToolUse",
|
|
6652
|
+
"postToolUseFailure",
|
|
6653
|
+
"permissionDenied",
|
|
6654
|
+
"stop",
|
|
6655
|
+
"stopFailure",
|
|
6656
|
+
"notification",
|
|
6657
|
+
"subagentStart",
|
|
6658
|
+
"subagentStop",
|
|
6659
|
+
"preCompact",
|
|
6660
|
+
"postCompact"
|
|
6284
6661
|
];
|
|
6285
6662
|
/**
|
|
6286
6663
|
* Hook events supported by Hermes Agent's native Shell Hooks system.
|
|
@@ -6354,6 +6731,7 @@ const HooksConfigSchema = zod_mini.z.looseObject({
|
|
|
6354
6731
|
junie: zod_mini.z.optional(zod_mini.z.looseObject({ hooks: zod_mini.z.optional(hooksRecordSchema) })),
|
|
6355
6732
|
vibe: zod_mini.z.optional(zod_mini.z.looseObject({ hooks: zod_mini.z.optional(hooksRecordSchema) })),
|
|
6356
6733
|
reasonix: zod_mini.z.optional(zod_mini.z.looseObject({ hooks: zod_mini.z.optional(hooksRecordSchema) })),
|
|
6734
|
+
grokcli: zod_mini.z.optional(zod_mini.z.looseObject({ hooks: zod_mini.z.optional(hooksRecordSchema) })),
|
|
6357
6735
|
qwencode: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
6358
6736
|
hooks: zod_mini.z.optional(hooksRecordSchema),
|
|
6359
6737
|
disableAllHooks: zod_mini.z.optional(zod_mini.z.boolean())
|
|
@@ -6412,7 +6790,8 @@ const CANONICAL_TO_DEVIN_EVENT_NAMES = {
|
|
|
6412
6790
|
postToolUse: "PostToolUse",
|
|
6413
6791
|
beforeSubmitPrompt: "UserPromptSubmit",
|
|
6414
6792
|
stop: "Stop",
|
|
6415
|
-
permissionRequest: "PermissionRequest"
|
|
6793
|
+
permissionRequest: "PermissionRequest",
|
|
6794
|
+
postCompact: "PostCompaction"
|
|
6416
6795
|
};
|
|
6417
6796
|
/**
|
|
6418
6797
|
* Map Devin Local PascalCase event names to canonical camelCase.
|
|
@@ -6427,7 +6806,8 @@ const CANONICAL_TO_AUGMENTCODE_EVENT_NAMES = {
|
|
|
6427
6806
|
postToolUse: "PostToolUse",
|
|
6428
6807
|
sessionStart: "SessionStart",
|
|
6429
6808
|
sessionEnd: "SessionEnd",
|
|
6430
|
-
stop: "Stop"
|
|
6809
|
+
stop: "Stop",
|
|
6810
|
+
notification: "Notification"
|
|
6431
6811
|
};
|
|
6432
6812
|
/**
|
|
6433
6813
|
* Map AugmentCode PascalCase event names to canonical camelCase.
|
|
@@ -6697,7 +7077,9 @@ const CANONICAL_TO_QWENCODE_EVENT_NAMES = {
|
|
|
6697
7077
|
preToolUse: "PreToolUse",
|
|
6698
7078
|
postToolUse: "PostToolUse",
|
|
6699
7079
|
postToolUseFailure: "PostToolUseFailure",
|
|
7080
|
+
postToolBatch: "PostToolBatch",
|
|
6700
7081
|
beforeSubmitPrompt: "UserPromptSubmit",
|
|
7082
|
+
userPromptExpansion: "UserPromptExpansion",
|
|
6701
7083
|
stop: "Stop",
|
|
6702
7084
|
subagentStart: "SubagentStart",
|
|
6703
7085
|
subagentStop: "SubagentStop",
|
|
@@ -6705,7 +7087,9 @@ const CANONICAL_TO_QWENCODE_EVENT_NAMES = {
|
|
|
6705
7087
|
preCompact: "PreCompact",
|
|
6706
7088
|
postCompact: "PostCompact",
|
|
6707
7089
|
permissionRequest: "PermissionRequest",
|
|
7090
|
+
permissionDenied: "PermissionDenied",
|
|
6708
7091
|
notification: "Notification",
|
|
7092
|
+
instructionsLoaded: "InstructionsLoaded",
|
|
6709
7093
|
todoCreated: "TodoCreated",
|
|
6710
7094
|
todoCompleted: "TodoCompleted"
|
|
6711
7095
|
};
|
|
@@ -6727,12 +7111,40 @@ const CANONICAL_TO_REASONIX_EVENT_NAMES = {
|
|
|
6727
7111
|
sessionStart: "SessionStart",
|
|
6728
7112
|
sessionEnd: "SessionEnd",
|
|
6729
7113
|
subagentStop: "SubagentStop",
|
|
6730
|
-
postModelInvocation: "PostLLMCall"
|
|
7114
|
+
postModelInvocation: "PostLLMCall",
|
|
7115
|
+
notification: "Notification",
|
|
7116
|
+
preCompact: "PreCompact"
|
|
6731
7117
|
};
|
|
6732
7118
|
/**
|
|
6733
7119
|
* Map Reasonix PascalCase event names to canonical camelCase.
|
|
6734
7120
|
*/
|
|
6735
7121
|
const REASONIX_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_REASONIX_EVENT_NAMES).map(([k, v]) => [v, k]));
|
|
7122
|
+
/**
|
|
7123
|
+
* Map canonical camelCase event names to Grok CLI PascalCase.
|
|
7124
|
+
* Grok Build reuses the same Claude-style PascalCase event names, so each
|
|
7125
|
+
* canonical arm maps to its PascalCase equivalent.
|
|
7126
|
+
* @see https://docs.x.ai/build/features/hooks
|
|
7127
|
+
*/
|
|
7128
|
+
const CANONICAL_TO_GROKCLI_EVENT_NAMES = {
|
|
7129
|
+
sessionStart: "SessionStart",
|
|
7130
|
+
sessionEnd: "SessionEnd",
|
|
7131
|
+
beforeSubmitPrompt: "UserPromptSubmit",
|
|
7132
|
+
preToolUse: "PreToolUse",
|
|
7133
|
+
postToolUse: "PostToolUse",
|
|
7134
|
+
postToolUseFailure: "PostToolUseFailure",
|
|
7135
|
+
permissionDenied: "PermissionDenied",
|
|
7136
|
+
stop: "Stop",
|
|
7137
|
+
stopFailure: "StopFailure",
|
|
7138
|
+
notification: "Notification",
|
|
7139
|
+
subagentStart: "SubagentStart",
|
|
7140
|
+
subagentStop: "SubagentStop",
|
|
7141
|
+
preCompact: "PreCompact",
|
|
7142
|
+
postCompact: "PostCompact"
|
|
7143
|
+
};
|
|
7144
|
+
/**
|
|
7145
|
+
* Map Grok CLI PascalCase event names to canonical camelCase.
|
|
7146
|
+
*/
|
|
7147
|
+
const GROKCLI_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_GROKCLI_EVENT_NAMES).map(([k, v]) => [v, k]));
|
|
6736
7148
|
//#endregion
|
|
6737
7149
|
//#region src/features/hooks/tool-hooks-converter.ts
|
|
6738
7150
|
function isToolMatcherEntry(x) {
|
|
@@ -7257,7 +7669,8 @@ const AUGMENTCODE_CONVERTER_CONFIG = {
|
|
|
7257
7669
|
noMatcherEvents: /* @__PURE__ */ new Set([
|
|
7258
7670
|
"sessionStart",
|
|
7259
7671
|
"sessionEnd",
|
|
7260
|
-
"stop"
|
|
7672
|
+
"stop",
|
|
7673
|
+
"notification"
|
|
7261
7674
|
])
|
|
7262
7675
|
};
|
|
7263
7676
|
/**
|
|
@@ -7307,12 +7720,6 @@ var AugmentcodeHooks = class AugmentcodeHooks extends ToolHooks {
|
|
|
7307
7720
|
const paths = AugmentcodeHooks.getSettablePaths({ global });
|
|
7308
7721
|
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
7309
7722
|
const existingContent = await readOrInitializeFileContent(filePath, JSON.stringify({}, null, 2));
|
|
7310
|
-
let settings;
|
|
7311
|
-
try {
|
|
7312
|
-
settings = JSON.parse(existingContent);
|
|
7313
|
-
} catch (error) {
|
|
7314
|
-
throw new Error(`Failed to parse existing AugmentCode settings at ${filePath}: ${formatError(error)}`, { cause: error });
|
|
7315
|
-
}
|
|
7316
7723
|
const config = rulesyncHooks.getJson();
|
|
7317
7724
|
const augmentHooks = canonicalToToolHooks({
|
|
7318
7725
|
config,
|
|
@@ -7320,11 +7727,13 @@ var AugmentcodeHooks = class AugmentcodeHooks extends ToolHooks {
|
|
|
7320
7727
|
converterConfig: AUGMENTCODE_CONVERTER_CONFIG,
|
|
7321
7728
|
logger
|
|
7322
7729
|
});
|
|
7323
|
-
const
|
|
7324
|
-
|
|
7325
|
-
|
|
7326
|
-
|
|
7327
|
-
|
|
7730
|
+
const fileContent = applySharedConfigPatch({
|
|
7731
|
+
fileKey: sharedConfigFileKey(paths),
|
|
7732
|
+
feature: "hooks",
|
|
7733
|
+
existingContent,
|
|
7734
|
+
patch: { hooks: augmentHooks },
|
|
7735
|
+
filePath
|
|
7736
|
+
});
|
|
7328
7737
|
return new AugmentcodeHooks({
|
|
7329
7738
|
outputRoot,
|
|
7330
7739
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -7491,15 +7900,28 @@ const CODEXCLI_CONVERTER_CONFIG = {
|
|
|
7491
7900
|
*/
|
|
7492
7901
|
async function buildCodexConfigTomlContent({ outputRoot }) {
|
|
7493
7902
|
const configPath = (0, node_path.join)(outputRoot, CODEXCLI_DIR, CODEXCLI_MCP_FILE_NAME);
|
|
7494
|
-
const existingContent = await readFileContentOrNull(configPath) ??
|
|
7903
|
+
const existingContent = await readFileContentOrNull(configPath) ?? "";
|
|
7495
7904
|
let configToml;
|
|
7496
7905
|
try {
|
|
7497
|
-
configToml = smol_toml.parse(existingContent);
|
|
7906
|
+
configToml = smol_toml.parse(existingContent || smol_toml.stringify({}));
|
|
7498
7907
|
} catch (error) {
|
|
7499
7908
|
throw new Error(`Failed to parse existing Codex CLI config at ${configPath}: ${formatError(error)}`, { cause: error });
|
|
7500
7909
|
}
|
|
7501
|
-
|
|
7502
|
-
|
|
7910
|
+
let features;
|
|
7911
|
+
if (typeof configToml.features === "object" && configToml.features !== null) {
|
|
7912
|
+
features = { ...configToml.features };
|
|
7913
|
+
delete features.codex_hooks;
|
|
7914
|
+
}
|
|
7915
|
+
return applySharedConfigPatch({
|
|
7916
|
+
fileKey: sharedConfigFileKey({
|
|
7917
|
+
relativeDirPath: CODEXCLI_DIR,
|
|
7918
|
+
relativeFilePath: CODEXCLI_MCP_FILE_NAME
|
|
7919
|
+
}),
|
|
7920
|
+
feature: "hooks",
|
|
7921
|
+
existingContent,
|
|
7922
|
+
patch: { features },
|
|
7923
|
+
filePath: configPath
|
|
7924
|
+
});
|
|
7503
7925
|
}
|
|
7504
7926
|
/**
|
|
7505
7927
|
* Represents the `.codex/config.toml` file as a generated ToolFile,
|
|
@@ -8347,7 +8769,8 @@ const DEVIN_CONVERTER_CONFIG = {
|
|
|
8347
8769
|
"sessionStart",
|
|
8348
8770
|
"sessionEnd",
|
|
8349
8771
|
"stop",
|
|
8350
|
-
"beforeSubmitPrompt"
|
|
8772
|
+
"beforeSubmitPrompt",
|
|
8773
|
+
"postCompact"
|
|
8351
8774
|
])
|
|
8352
8775
|
};
|
|
8353
8776
|
/**
|
|
@@ -8415,17 +8838,13 @@ var DevinHooks = class DevinHooks extends ToolHooks {
|
|
|
8415
8838
|
let fileContent;
|
|
8416
8839
|
if (global) {
|
|
8417
8840
|
const existingContent = await readOrInitializeFileContent(filePath, JSON.stringify({}, null, 2));
|
|
8418
|
-
|
|
8419
|
-
|
|
8420
|
-
|
|
8421
|
-
|
|
8422
|
-
|
|
8423
|
-
|
|
8424
|
-
|
|
8425
|
-
...isRecord(parsedSettings) ? parsedSettings : {},
|
|
8426
|
-
hooks: devinHooks
|
|
8427
|
-
};
|
|
8428
|
-
fileContent = JSON.stringify(merged, null, 2);
|
|
8841
|
+
fileContent = applySharedConfigPatch({
|
|
8842
|
+
fileKey: sharedConfigFileKey(paths),
|
|
8843
|
+
feature: "hooks",
|
|
8844
|
+
existingContent,
|
|
8845
|
+
patch: { hooks: devinHooks },
|
|
8846
|
+
filePath
|
|
8847
|
+
});
|
|
8429
8848
|
} else {
|
|
8430
8849
|
await readOrInitializeFileContent(filePath, JSON.stringify({}, null, 2));
|
|
8431
8850
|
fileContent = JSON.stringify(devinHooks, null, 2);
|
|
@@ -8660,6 +9079,167 @@ var GooseHooks = class GooseHooks extends ToolHooks {
|
|
|
8660
9079
|
}
|
|
8661
9080
|
};
|
|
8662
9081
|
//#endregion
|
|
9082
|
+
//#region src/constants/grokcli-paths.ts
|
|
9083
|
+
/**
|
|
9084
|
+
* Grok Build CLI (xAI) configuration-layout conventions.
|
|
9085
|
+
*
|
|
9086
|
+
* Single source of truth for where Grok Build expects its files. Grok Build
|
|
9087
|
+
* stores MCP servers (and other settings) in a `config.toml` under `.grok/`,
|
|
9088
|
+
* with project/global scopes resolved by the directory the CLI runs in
|
|
9089
|
+
* (`./.grok/config.toml` vs `~/.grok/config.toml`).
|
|
9090
|
+
*
|
|
9091
|
+
* Verified against `grok` 0.2.54 (`grok mcp add --help`, `grok mcp add`):
|
|
9092
|
+
* `-s project` writes `./.grok/config.toml`, `-s user` writes
|
|
9093
|
+
* `~/.grok/config.toml`, both as a TOML `[mcp_servers.<name>]` table.
|
|
9094
|
+
* @see https://docs.x.ai/build/overview
|
|
9095
|
+
*/
|
|
9096
|
+
/** Root directory for Grok Build configuration, relative to the scope root. */
|
|
9097
|
+
const GROKCLI_DIR = ".grok";
|
|
9098
|
+
/** MCP servers and other settings live in `config.toml` under `.grok/`. */
|
|
9099
|
+
const GROKCLI_MCP_FILE_NAME = "config.toml";
|
|
9100
|
+
/**
|
|
9101
|
+
* Shared Grok CLI config file (`config.toml`). MCP servers, the `[ui]`
|
|
9102
|
+
* permission mode, and other settings all live here; permissions reuse the same
|
|
9103
|
+
* file name as MCP since Grok consolidates everything into one config.
|
|
9104
|
+
*/
|
|
9105
|
+
const GROKCLI_CONFIG_FILE_NAME = "config.toml";
|
|
9106
|
+
/** Skills directory under `.grok/` (project: `./.grok/skills`, global: `~/.grok/skills`). */
|
|
9107
|
+
const GROKCLI_SKILLS_DIR_PATH = (0, node_path.join)(GROKCLI_DIR, "skills");
|
|
9108
|
+
/**
|
|
9109
|
+
* Hooks directory under `.grok/`. Grok Build discovers hook config files from
|
|
9110
|
+
* `.grok/hooks/*.json` (project) and `~/.grok/hooks/*.json` (global), each a
|
|
9111
|
+
* standalone JSON file using the Claude-Code-compatible nested `{ hooks: { … } }`
|
|
9112
|
+
* shape. rulesync writes all its hooks into a single `rulesync.json`.
|
|
9113
|
+
* @see https://docs.x.ai/build/features/hooks
|
|
9114
|
+
*/
|
|
9115
|
+
const GROKCLI_HOOKS_DIR_PATH = (0, node_path.join)(GROKCLI_DIR, "hooks");
|
|
9116
|
+
/** rulesync-managed Grok hooks file under `.grok/hooks/`. */
|
|
9117
|
+
const GROKCLI_HOOKS_FILE_NAME = "rulesync.json";
|
|
9118
|
+
/**
|
|
9119
|
+
* Subagents (agent profiles) directory under `.grok/`. Grok Build discovers
|
|
9120
|
+
* agent definitions from `.grok/agents/*.md` (project) and `~/.grok/agents/*.md`
|
|
9121
|
+
* (global), each a Markdown file with YAML frontmatter (verified via
|
|
9122
|
+
* `grok inspect`; format matches the bundled `~/.grok/bundled/agents/*.md`).
|
|
9123
|
+
*/
|
|
9124
|
+
const GROKCLI_AGENTS_DIR_PATH = (0, node_path.join)(GROKCLI_DIR, "agents");
|
|
9125
|
+
/**
|
|
9126
|
+
* Instruction file. Grok reads the AGENTS.md instruction-file family natively,
|
|
9127
|
+
* including the user-level `~/.grok/AGENTS.md` for global rules (verified via
|
|
9128
|
+
* `grok inspect`, consistent with the `.grok/` global discovery used by the
|
|
9129
|
+
* MCP/skills/subagents adapters).
|
|
9130
|
+
*/
|
|
9131
|
+
const GROKCLI_RULE_FILE_NAME = "AGENTS.md";
|
|
9132
|
+
//#endregion
|
|
9133
|
+
//#region src/features/hooks/grokcli-hooks.ts
|
|
9134
|
+
const GROKCLI_CONVERTER_CONFIG = {
|
|
9135
|
+
supportedEvents: GROKCLI_HOOK_EVENTS,
|
|
9136
|
+
canonicalToToolEventNames: CANONICAL_TO_GROKCLI_EVENT_NAMES,
|
|
9137
|
+
toolToCanonicalEventNames: GROKCLI_TO_CANONICAL_EVENT_NAMES,
|
|
9138
|
+
projectDirVar: "",
|
|
9139
|
+
supportedHookTypes: /* @__PURE__ */ new Set(["command"]),
|
|
9140
|
+
noMatcherEvents: /* @__PURE__ */ new Set([
|
|
9141
|
+
"sessionStart",
|
|
9142
|
+
"sessionEnd",
|
|
9143
|
+
"beforeSubmitPrompt",
|
|
9144
|
+
"stop",
|
|
9145
|
+
"stopFailure",
|
|
9146
|
+
"notification",
|
|
9147
|
+
"subagentStart",
|
|
9148
|
+
"subagentStop",
|
|
9149
|
+
"preCompact",
|
|
9150
|
+
"postCompact"
|
|
9151
|
+
])
|
|
9152
|
+
};
|
|
9153
|
+
/**
|
|
9154
|
+
* Hooks generator for Grok CLI (xAI Grok Build).
|
|
9155
|
+
*
|
|
9156
|
+
* Grok Build adopts a Claude-Code-compatible lifecycle hooks model: each event
|
|
9157
|
+
* maps to an array of `{ matcher?, hooks: [{ type, command, timeout? }] }`
|
|
9158
|
+
* matcher groups under a top-level `hooks` key. rulesync writes all its hooks
|
|
9159
|
+
* into a single standalone `rulesync.json` file discovered from
|
|
9160
|
+
* `.grok/hooks/*.json` (project) and `~/.grok/hooks/*.json` (global). The file
|
|
9161
|
+
* is dedicated to hooks and owned wholesale by rulesync, so it may be deleted
|
|
9162
|
+
* as an orphan.
|
|
9163
|
+
*
|
|
9164
|
+
* @see https://docs.x.ai/build/features/hooks
|
|
9165
|
+
*/
|
|
9166
|
+
var GrokcliHooks = class GrokcliHooks extends ToolHooks {
|
|
9167
|
+
constructor(params) {
|
|
9168
|
+
super({
|
|
9169
|
+
...params,
|
|
9170
|
+
fileContent: params.fileContent ?? "{}"
|
|
9171
|
+
});
|
|
9172
|
+
}
|
|
9173
|
+
static getSettablePaths(_options = {}) {
|
|
9174
|
+
return {
|
|
9175
|
+
relativeDirPath: GROKCLI_HOOKS_DIR_PATH,
|
|
9176
|
+
relativeFilePath: GROKCLI_HOOKS_FILE_NAME
|
|
9177
|
+
};
|
|
9178
|
+
}
|
|
9179
|
+
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
9180
|
+
const paths = GrokcliHooks.getSettablePaths({ global });
|
|
9181
|
+
const fileContent = await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "{\"hooks\":{}}";
|
|
9182
|
+
return new GrokcliHooks({
|
|
9183
|
+
outputRoot,
|
|
9184
|
+
relativeDirPath: paths.relativeDirPath,
|
|
9185
|
+
relativeFilePath: paths.relativeFilePath,
|
|
9186
|
+
fileContent,
|
|
9187
|
+
validate
|
|
9188
|
+
});
|
|
9189
|
+
}
|
|
9190
|
+
static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false, logger }) {
|
|
9191
|
+
const paths = GrokcliHooks.getSettablePaths({ global });
|
|
9192
|
+
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
9193
|
+
const config = rulesyncHooks.getJson();
|
|
9194
|
+
const grokHooks = canonicalToToolHooks({
|
|
9195
|
+
config,
|
|
9196
|
+
toolOverrideHooks: config.grokcli?.hooks,
|
|
9197
|
+
converterConfig: GROKCLI_CONVERTER_CONFIG,
|
|
9198
|
+
logger
|
|
9199
|
+
});
|
|
9200
|
+
await readOrInitializeFileContent(filePath, JSON.stringify({ hooks: {} }, null, 2));
|
|
9201
|
+
const fileContent = JSON.stringify({ hooks: grokHooks }, null, 2);
|
|
9202
|
+
return new GrokcliHooks({
|
|
9203
|
+
outputRoot,
|
|
9204
|
+
relativeDirPath: paths.relativeDirPath,
|
|
9205
|
+
relativeFilePath: paths.relativeFilePath,
|
|
9206
|
+
fileContent,
|
|
9207
|
+
validate
|
|
9208
|
+
});
|
|
9209
|
+
}
|
|
9210
|
+
toRulesyncHooks() {
|
|
9211
|
+
let parsed;
|
|
9212
|
+
try {
|
|
9213
|
+
parsed = JSON.parse(this.getFileContent());
|
|
9214
|
+
} catch (error) {
|
|
9215
|
+
throw new Error(`Failed to parse Grok hooks content in ${(0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
9216
|
+
}
|
|
9217
|
+
const hooks = toolHooksToCanonical({
|
|
9218
|
+
hooks: isRecord(parsed) && isRecord(parsed.hooks) ? parsed.hooks : {},
|
|
9219
|
+
converterConfig: GROKCLI_CONVERTER_CONFIG
|
|
9220
|
+
});
|
|
9221
|
+
return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
|
|
9222
|
+
version: 1,
|
|
9223
|
+
hooks
|
|
9224
|
+
}, null, 2) });
|
|
9225
|
+
}
|
|
9226
|
+
validate() {
|
|
9227
|
+
return {
|
|
9228
|
+
success: true,
|
|
9229
|
+
error: null
|
|
9230
|
+
};
|
|
9231
|
+
}
|
|
9232
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
9233
|
+
return new GrokcliHooks({
|
|
9234
|
+
outputRoot,
|
|
9235
|
+
relativeDirPath,
|
|
9236
|
+
relativeFilePath,
|
|
9237
|
+
fileContent: JSON.stringify({ hooks: {} }, null, 2),
|
|
9238
|
+
validate: false
|
|
9239
|
+
});
|
|
9240
|
+
}
|
|
9241
|
+
};
|
|
9242
|
+
//#endregion
|
|
8663
9243
|
//#region src/features/hooks/hermesagent-hooks.ts
|
|
8664
9244
|
/**
|
|
8665
9245
|
* Canonical events that map to a Hermes tool-call event (`pre_tool_call` /
|
|
@@ -9237,18 +9817,14 @@ var KiroHooks = class KiroHooks extends ToolHooks {
|
|
|
9237
9817
|
const paths = KiroHooks.getSettablePaths({ global });
|
|
9238
9818
|
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
9239
9819
|
const existingContent = await readOrInitializeFileContent(filePath, JSON.stringify({}, null, 2));
|
|
9240
|
-
let agentConfig;
|
|
9241
|
-
try {
|
|
9242
|
-
agentConfig = JSON.parse(existingContent);
|
|
9243
|
-
} catch (error) {
|
|
9244
|
-
throw new Error(`Failed to parse existing Kiro agent config at ${filePath}: ${formatError(error)}`, { cause: error });
|
|
9245
|
-
}
|
|
9246
9820
|
const kiroHooks = canonicalToKiroHooks(rulesyncHooks.getJson(), this.getOverrideKey());
|
|
9247
|
-
const
|
|
9248
|
-
|
|
9249
|
-
|
|
9250
|
-
|
|
9251
|
-
|
|
9821
|
+
const fileContent = applySharedConfigPatch({
|
|
9822
|
+
fileKey: sharedConfigFileKey(paths),
|
|
9823
|
+
feature: "hooks",
|
|
9824
|
+
existingContent,
|
|
9825
|
+
patch: { hooks: kiroHooks },
|
|
9826
|
+
filePath
|
|
9827
|
+
});
|
|
9252
9828
|
return new KiroHooks({
|
|
9253
9829
|
outputRoot,
|
|
9254
9830
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -9743,21 +10319,17 @@ var QwencodeHooks = class QwencodeHooks extends ToolHooks {
|
|
|
9743
10319
|
const paths = QwencodeHooks.getSettablePaths({ global });
|
|
9744
10320
|
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
9745
10321
|
const existingContent = await readOrInitializeFileContent(filePath, JSON.stringify({}, null, 2));
|
|
9746
|
-
let settings;
|
|
9747
|
-
try {
|
|
9748
|
-
settings = JSON.parse(existingContent);
|
|
9749
|
-
} catch (error) {
|
|
9750
|
-
throw new Error(`Failed to parse existing Qwen Code settings at ${filePath}: ${formatError(error)}`, { cause: error });
|
|
9751
|
-
}
|
|
9752
10322
|
const config = rulesyncHooks.getJson();
|
|
9753
|
-
const
|
|
9754
|
-
const merged = {
|
|
9755
|
-
...settings,
|
|
9756
|
-
hooks: qwencodeHooks
|
|
9757
|
-
};
|
|
10323
|
+
const patch = { hooks: canonicalToQwencodeHooks(config) };
|
|
9758
10324
|
const disableAllHooks = config.qwencode?.disableAllHooks;
|
|
9759
|
-
if (typeof disableAllHooks === "boolean")
|
|
9760
|
-
const fileContent =
|
|
10325
|
+
if (typeof disableAllHooks === "boolean") patch.disableAllHooks = disableAllHooks;
|
|
10326
|
+
const fileContent = applySharedConfigPatch({
|
|
10327
|
+
fileKey: sharedConfigFileKey(paths),
|
|
10328
|
+
feature: "hooks",
|
|
10329
|
+
existingContent,
|
|
10330
|
+
patch,
|
|
10331
|
+
filePath
|
|
10332
|
+
});
|
|
9761
10333
|
return new QwencodeHooks({
|
|
9762
10334
|
outputRoot,
|
|
9763
10335
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -9874,9 +10446,9 @@ function reasonixHooksToCanonical(hooks) {
|
|
|
9874
10446
|
* Reasonix hooks live in a Claude-Code-style but standalone JSON file —
|
|
9875
10447
|
* `.reasonix/settings.json` (project) or `~/.reasonix/settings.json`
|
|
9876
10448
|
* (global) — separate from the `[permissions]`/`[[plugins]]` TOML config.
|
|
9877
|
-
*
|
|
10449
|
+
* All ten upstream events have a clean canonical equivalent and are mapped:
|
|
9878
10450
|
* PreToolUse/PostToolUse/UserPromptSubmit/Stop plus SessionStart/SessionEnd/
|
|
9879
|
-
* SubagentStop/PostLLMCall (see REASONIX_HOOK_EVENTS).
|
|
10451
|
+
* SubagentStop/PostLLMCall/Notification/PreCompact (see REASONIX_HOOK_EVENTS).
|
|
9880
10452
|
* @see https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/docs/DESKTOP_HOOKS.zh-CN.md
|
|
9881
10453
|
*/
|
|
9882
10454
|
var ReasonixHooks = class ReasonixHooks extends ToolHooks {
|
|
@@ -10016,10 +10588,6 @@ function canonicalToVibeHooks(config, toolOverride) {
|
|
|
10016
10588
|
}
|
|
10017
10589
|
return { hooks };
|
|
10018
10590
|
}
|
|
10019
|
-
/**
|
|
10020
|
-
* Reverse {@link canonicalToVibeHooks}: parse the flat `[[hooks]]` array back
|
|
10021
|
-
* into a canonical event → definition[] record.
|
|
10022
|
-
*/
|
|
10023
10591
|
/** Convert one raw `[[hooks]]` entry to a canonical definition, or null to skip. */
|
|
10024
10592
|
function vibeEntryToCanonicalDef(raw) {
|
|
10025
10593
|
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
@@ -10039,6 +10607,10 @@ function vibeEntryToCanonicalDef(raw) {
|
|
|
10039
10607
|
def
|
|
10040
10608
|
};
|
|
10041
10609
|
}
|
|
10610
|
+
/**
|
|
10611
|
+
* Reverse {@link canonicalToVibeHooks}: parse the flat `[[hooks]]` array back
|
|
10612
|
+
* into a canonical event → definition[] record.
|
|
10613
|
+
*/
|
|
10042
10614
|
function vibeHooksToCanonical(parsed) {
|
|
10043
10615
|
const canonical = {};
|
|
10044
10616
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return canonical;
|
|
@@ -10067,15 +10639,22 @@ function parseVibeToml(fileContent) {
|
|
|
10067
10639
|
*/
|
|
10068
10640
|
async function buildVibeConfigTomlContent({ outputRoot }) {
|
|
10069
10641
|
const configPath = (0, node_path.join)(outputRoot, VIBE_DIR, VIBE_CONFIG_FILE_NAME);
|
|
10070
|
-
const existingContent = await readFileContentOrNull(configPath) ??
|
|
10071
|
-
let config;
|
|
10642
|
+
const existingContent = await readFileContentOrNull(configPath) ?? "";
|
|
10072
10643
|
try {
|
|
10073
|
-
|
|
10644
|
+
parseVibeToml(existingContent);
|
|
10074
10645
|
} catch (error) {
|
|
10075
10646
|
throw new Error(`Failed to parse existing Vibe config at ${configPath}: ${formatError(error)}`, { cause: error });
|
|
10076
10647
|
}
|
|
10077
|
-
|
|
10078
|
-
|
|
10648
|
+
return applySharedConfigPatch({
|
|
10649
|
+
fileKey: sharedConfigFileKey({
|
|
10650
|
+
relativeDirPath: VIBE_DIR,
|
|
10651
|
+
relativeFilePath: VIBE_CONFIG_FILE_NAME
|
|
10652
|
+
}),
|
|
10653
|
+
feature: "hooks",
|
|
10654
|
+
existingContent,
|
|
10655
|
+
patch: { enable_experimental_hooks: true },
|
|
10656
|
+
filePath: configPath
|
|
10657
|
+
});
|
|
10079
10658
|
}
|
|
10080
10659
|
/**
|
|
10081
10660
|
* Represents the `.vibe/config.toml` file as a generated ToolFile so it goes
|
|
@@ -10457,6 +11036,17 @@ const toolHooksFactories = /* @__PURE__ */ new Map([
|
|
|
10457
11036
|
supportedEvents: REASONIX_HOOK_EVENTS,
|
|
10458
11037
|
supportedHookTypes: ["command"],
|
|
10459
11038
|
supportsMatcher: true
|
|
11039
|
+
}],
|
|
11040
|
+
["grokcli", {
|
|
11041
|
+
class: GrokcliHooks,
|
|
11042
|
+
meta: {
|
|
11043
|
+
supportsProject: true,
|
|
11044
|
+
supportsGlobal: true,
|
|
11045
|
+
supportsImport: true
|
|
11046
|
+
},
|
|
11047
|
+
supportedEvents: GROKCLI_HOOK_EVENTS,
|
|
11048
|
+
supportedHookTypes: ["command"],
|
|
11049
|
+
supportsMatcher: true
|
|
10460
11050
|
}]
|
|
10461
11051
|
]);
|
|
10462
11052
|
const hooksProcessorToolTargets = [...toolHooksFactories.entries()].filter(([, f]) => f.meta.supportsProject).map(([t]) => t);
|
|
@@ -11575,17 +12165,18 @@ var ZedIgnore = class ZedIgnore extends ToolIgnore {
|
|
|
11575
12165
|
const patterns = rulesyncIgnore.getFileContent().split(/\r?\n|\r/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
|
|
11576
12166
|
const filePath = (0, node_path.join)(outputRoot, this.getSettablePaths().relativeDirPath, this.getSettablePaths().relativeFilePath);
|
|
11577
12167
|
const existingFileContent = await fileExists(filePath) ? await readFileContent(filePath) : "{}";
|
|
11578
|
-
const
|
|
11579
|
-
const mergedPatterns = (0, es_toolkit.uniq)([...existingJsonValue.private_files ?? [], ...patterns].toSorted());
|
|
11580
|
-
const jsonValue = {
|
|
11581
|
-
...existingJsonValue,
|
|
11582
|
-
private_files: mergedPatterns
|
|
11583
|
-
};
|
|
12168
|
+
const mergedPatterns = (0, es_toolkit.uniq)([...JSON.parse(existingFileContent).private_files ?? [], ...patterns].toSorted());
|
|
11584
12169
|
return new ZedIgnore({
|
|
11585
12170
|
outputRoot,
|
|
11586
12171
|
relativeDirPath: this.getSettablePaths().relativeDirPath,
|
|
11587
12172
|
relativeFilePath: this.getSettablePaths().relativeFilePath,
|
|
11588
|
-
fileContent:
|
|
12173
|
+
fileContent: applySharedConfigPatch({
|
|
12174
|
+
fileKey: sharedConfigFileKey(this.getSettablePaths()),
|
|
12175
|
+
feature: "ignore",
|
|
12176
|
+
existingContent: existingFileContent,
|
|
12177
|
+
patch: { private_files: mergedPatterns },
|
|
12178
|
+
filePath
|
|
12179
|
+
}),
|
|
11589
12180
|
validate: true
|
|
11590
12181
|
});
|
|
11591
12182
|
}
|
|
@@ -12042,17 +12633,17 @@ var AmpMcp = class AmpMcp extends ToolMcp {
|
|
|
12042
12633
|
const basePaths = this.getSettablePaths({ global });
|
|
12043
12634
|
const jsonDir = (0, node_path.join)(outputRoot, basePaths.relativeDirPath);
|
|
12044
12635
|
const { fileContent, relativeFilePath } = await this.resolveSettingsFile(jsonDir);
|
|
12045
|
-
const json = fileContent ? parseAmpSettingsJsonc(fileContent) : { [AMP_MCP_SERVERS_KEY]: {} };
|
|
12046
|
-
const filteredMcpServers = filterMcpServers(rulesyncMcp.getMcpServers());
|
|
12047
|
-
const newJson = {
|
|
12048
|
-
...json,
|
|
12049
|
-
[AMP_MCP_SERVERS_KEY]: filteredMcpServers
|
|
12050
|
-
};
|
|
12051
12636
|
return new AmpMcp({
|
|
12052
12637
|
outputRoot,
|
|
12053
12638
|
relativeDirPath: basePaths.relativeDirPath,
|
|
12054
12639
|
relativeFilePath,
|
|
12055
|
-
fileContent:
|
|
12640
|
+
fileContent: applySharedConfigPatch({
|
|
12641
|
+
fileKey: sharedConfigFileKey(basePaths),
|
|
12642
|
+
feature: "mcp",
|
|
12643
|
+
existingContent: fileContent ?? "",
|
|
12644
|
+
patch: { [AMP_MCP_SERVERS_KEY]: filterMcpServers(rulesyncMcp.getMcpServers()) },
|
|
12645
|
+
filePath: (0, node_path.join)(jsonDir, relativeFilePath)
|
|
12646
|
+
}),
|
|
12056
12647
|
validate,
|
|
12057
12648
|
global
|
|
12058
12649
|
});
|
|
@@ -12339,15 +12930,19 @@ var AugmentcodeMcp = class AugmentcodeMcp extends ToolMcp {
|
|
|
12339
12930
|
}
|
|
12340
12931
|
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false }) {
|
|
12341
12932
|
const paths = this.getSettablePaths({ global });
|
|
12342
|
-
const
|
|
12343
|
-
|
|
12344
|
-
mcpServers: rulesyncMcp.getMcpServers()
|
|
12345
|
-
};
|
|
12933
|
+
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
12934
|
+
const existingContent = await readOrInitializeFileContent(filePath, JSON.stringify({}, null, 2));
|
|
12346
12935
|
return new AugmentcodeMcp({
|
|
12347
12936
|
outputRoot,
|
|
12348
12937
|
relativeDirPath: paths.relativeDirPath,
|
|
12349
12938
|
relativeFilePath: paths.relativeFilePath,
|
|
12350
|
-
fileContent:
|
|
12939
|
+
fileContent: applySharedConfigPatch({
|
|
12940
|
+
fileKey: sharedConfigFileKey(paths),
|
|
12941
|
+
feature: "mcp",
|
|
12942
|
+
existingContent,
|
|
12943
|
+
patch: { mcpServers: rulesyncMcp.getMcpServers() },
|
|
12944
|
+
filePath
|
|
12945
|
+
}),
|
|
12351
12946
|
validate,
|
|
12352
12947
|
global
|
|
12353
12948
|
});
|
|
@@ -12707,7 +13302,13 @@ var CodexcliMcp = class CodexcliMcp extends ToolMcp {
|
|
|
12707
13302
|
...rest,
|
|
12708
13303
|
validate: false
|
|
12709
13304
|
});
|
|
12710
|
-
|
|
13305
|
+
let toml;
|
|
13306
|
+
try {
|
|
13307
|
+
toml = smol_toml.parse(this.fileContent);
|
|
13308
|
+
} catch (error) {
|
|
13309
|
+
throw new Error(`Failed to parse Codex CLI config at ${this.getFilePath()}: ${formatError(error)}`, { cause: error });
|
|
13310
|
+
}
|
|
13311
|
+
this.toml = toml;
|
|
12711
13312
|
if (rest.validate) {
|
|
12712
13313
|
const result = this.validate();
|
|
12713
13314
|
if (!result.success) throw result.error;
|
|
@@ -12741,8 +13342,14 @@ var CodexcliMcp = class CodexcliMcp extends ToolMcp {
|
|
|
12741
13342
|
}
|
|
12742
13343
|
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false }) {
|
|
12743
13344
|
const paths = this.getSettablePaths({ global });
|
|
12744
|
-
const
|
|
12745
|
-
const
|
|
13345
|
+
const configTomlFilePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
13346
|
+
const configTomlFileContent = await readFileContentOrNull(configTomlFilePath) ?? "";
|
|
13347
|
+
let configToml;
|
|
13348
|
+
try {
|
|
13349
|
+
configToml = smol_toml.parse(configTomlFileContent || smol_toml.stringify({}));
|
|
13350
|
+
} catch (error) {
|
|
13351
|
+
throw new Error(`Failed to parse existing Codex CLI config at ${configTomlFilePath}: ${formatError(error)}`, { cause: error });
|
|
13352
|
+
}
|
|
12746
13353
|
const strippedMcpServers = rulesyncMcp.getMcpServers();
|
|
12747
13354
|
const rawMcpServers = rulesyncMcp.getJson().mcpServers;
|
|
12748
13355
|
const converted = convertToCodexFormat(Object.fromEntries(Object.entries(strippedMcpServers).map(([serverName, serverConfig]) => {
|
|
@@ -12755,7 +13362,7 @@ var CodexcliMcp = class CodexcliMcp extends ToolMcp {
|
|
|
12755
13362
|
const filteredMcpServers = this.removeEmptyEntries(converted);
|
|
12756
13363
|
for (const name of Object.keys(converted)) if (!Object.hasOwn(filteredMcpServers, name)) warnWithFallback(void 0, `MCP server "${name}" had no non-empty configuration and was dropped from the codex CLI config`);
|
|
12757
13364
|
const existingMcpServers = isRecord(configToml["mcp_servers"]) ? configToml["mcp_servers"] : {};
|
|
12758
|
-
|
|
13365
|
+
const mergedMcpServers = Object.fromEntries(Object.entries(filteredMcpServers).map(([name, serverConfig]) => {
|
|
12759
13366
|
const existingServer = isRecord(existingMcpServers[name]) ? existingMcpServers[name] : void 0;
|
|
12760
13367
|
const serverRecord = serverConfig;
|
|
12761
13368
|
if (existingServer && isRecord(existingServer["tools"]) && !("tools" in serverRecord)) return [name, {
|
|
@@ -12768,7 +13375,13 @@ var CodexcliMcp = class CodexcliMcp extends ToolMcp {
|
|
|
12768
13375
|
outputRoot,
|
|
12769
13376
|
relativeDirPath: paths.relativeDirPath,
|
|
12770
13377
|
relativeFilePath: paths.relativeFilePath,
|
|
12771
|
-
fileContent:
|
|
13378
|
+
fileContent: applySharedConfigPatch({
|
|
13379
|
+
fileKey: sharedConfigFileKey(paths),
|
|
13380
|
+
feature: "mcp",
|
|
13381
|
+
existingContent: configTomlFileContent,
|
|
13382
|
+
patch: { mcp_servers: mergedMcpServers },
|
|
13383
|
+
filePath: configTomlFilePath
|
|
13384
|
+
}),
|
|
12772
13385
|
validate
|
|
12773
13386
|
});
|
|
12774
13387
|
}
|
|
@@ -13335,16 +13948,19 @@ var DevinMcp = class DevinMcp extends ToolMcp {
|
|
|
13335
13948
|
}
|
|
13336
13949
|
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false }) {
|
|
13337
13950
|
const paths = this.getSettablePaths({ global });
|
|
13338
|
-
const
|
|
13339
|
-
const
|
|
13340
|
-
...this.parseJsonOrThrow(fileContent, paths.relativeDirPath, paths.relativeFilePath),
|
|
13341
|
-
mcpServers: rulesyncMcp.getMcpServers()
|
|
13342
|
-
};
|
|
13951
|
+
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
13952
|
+
const existingContent = await readOrInitializeFileContent(filePath, JSON.stringify({ mcpServers: {} }, null, 2));
|
|
13343
13953
|
return new DevinMcp({
|
|
13344
13954
|
outputRoot,
|
|
13345
13955
|
relativeDirPath: paths.relativeDirPath,
|
|
13346
13956
|
relativeFilePath: paths.relativeFilePath,
|
|
13347
|
-
fileContent:
|
|
13957
|
+
fileContent: applySharedConfigPatch({
|
|
13958
|
+
fileKey: sharedConfigFileKey(paths),
|
|
13959
|
+
feature: "mcp",
|
|
13960
|
+
existingContent,
|
|
13961
|
+
patch: { mcpServers: rulesyncMcp.getMcpServers() },
|
|
13962
|
+
filePath
|
|
13963
|
+
}),
|
|
13348
13964
|
validate,
|
|
13349
13965
|
global
|
|
13350
13966
|
});
|
|
@@ -13707,47 +14323,6 @@ var GooseMcp = class GooseMcp extends ToolMcp {
|
|
|
13707
14323
|
}
|
|
13708
14324
|
};
|
|
13709
14325
|
//#endregion
|
|
13710
|
-
//#region src/constants/grokcli-paths.ts
|
|
13711
|
-
/**
|
|
13712
|
-
* Grok Build CLI (xAI) configuration-layout conventions.
|
|
13713
|
-
*
|
|
13714
|
-
* Single source of truth for where Grok Build expects its files. Grok Build
|
|
13715
|
-
* stores MCP servers (and other settings) in a `config.toml` under `.grok/`,
|
|
13716
|
-
* with project/global scopes resolved by the directory the CLI runs in
|
|
13717
|
-
* (`./.grok/config.toml` vs `~/.grok/config.toml`).
|
|
13718
|
-
*
|
|
13719
|
-
* Verified against `grok` 0.2.54 (`grok mcp add --help`, `grok mcp add`):
|
|
13720
|
-
* `-s project` writes `./.grok/config.toml`, `-s user` writes
|
|
13721
|
-
* `~/.grok/config.toml`, both as a TOML `[mcp_servers.<name>]` table.
|
|
13722
|
-
* @see https://docs.x.ai/build/overview
|
|
13723
|
-
*/
|
|
13724
|
-
/** Root directory for Grok Build configuration, relative to the scope root. */
|
|
13725
|
-
const GROKCLI_DIR = ".grok";
|
|
13726
|
-
/** MCP servers and other settings live in `config.toml` under `.grok/`. */
|
|
13727
|
-
const GROKCLI_MCP_FILE_NAME = "config.toml";
|
|
13728
|
-
/**
|
|
13729
|
-
* Shared Grok CLI config file (`config.toml`). MCP servers, the `[ui]`
|
|
13730
|
-
* permission mode, and other settings all live here; permissions reuse the same
|
|
13731
|
-
* file name as MCP since Grok consolidates everything into one config.
|
|
13732
|
-
*/
|
|
13733
|
-
const GROKCLI_CONFIG_FILE_NAME = "config.toml";
|
|
13734
|
-
/** Skills directory under `.grok/` (project: `./.grok/skills`, global: `~/.grok/skills`). */
|
|
13735
|
-
const GROKCLI_SKILLS_DIR_PATH = (0, node_path.join)(GROKCLI_DIR, "skills");
|
|
13736
|
-
/**
|
|
13737
|
-
* Subagents (agent profiles) directory under `.grok/`. Grok Build discovers
|
|
13738
|
-
* agent definitions from `.grok/agents/*.md` (project) and `~/.grok/agents/*.md`
|
|
13739
|
-
* (global), each a Markdown file with YAML frontmatter (verified via
|
|
13740
|
-
* `grok inspect`; format matches the bundled `~/.grok/bundled/agents/*.md`).
|
|
13741
|
-
*/
|
|
13742
|
-
const GROKCLI_AGENTS_DIR_PATH = (0, node_path.join)(GROKCLI_DIR, "agents");
|
|
13743
|
-
/**
|
|
13744
|
-
* Instruction file. Grok reads the AGENTS.md instruction-file family natively,
|
|
13745
|
-
* including the user-level `~/.grok/AGENTS.md` for global rules (verified via
|
|
13746
|
-
* `grok inspect`, consistent with the `.grok/` global discovery used by the
|
|
13747
|
-
* MCP/skills/subagents adapters).
|
|
13748
|
-
*/
|
|
13749
|
-
const GROKCLI_RULE_FILE_NAME = "AGENTS.md";
|
|
13750
|
-
//#endregion
|
|
13751
14326
|
//#region src/features/mcp/grokcli-mcp.ts
|
|
13752
14327
|
const MAX_REMOVE_EMPTY_ENTRIES_DEPTH = 32;
|
|
13753
14328
|
/**
|
|
@@ -13831,17 +14406,22 @@ var GrokcliMcp = class GrokcliMcp extends ToolMcp {
|
|
|
13831
14406
|
}
|
|
13832
14407
|
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false }) {
|
|
13833
14408
|
const paths = this.getSettablePaths({ global });
|
|
13834
|
-
const
|
|
13835
|
-
const
|
|
14409
|
+
const configTomlFilePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
14410
|
+
const configTomlFileContent = await readFileContentOrNull(configTomlFilePath) ?? "";
|
|
13836
14411
|
const converted = convertToGrokFormat(rulesyncMcp.getMcpServers());
|
|
13837
14412
|
const filteredMcpServers = this.removeEmptyEntries(converted);
|
|
13838
14413
|
for (const name of Object.keys(converted)) if (!Object.hasOwn(filteredMcpServers, name)) warnWithFallback(void 0, `MCP server "${name}" had no non-empty configuration and was dropped from the grok CLI config`);
|
|
13839
|
-
configToml["mcp_servers"] = filteredMcpServers;
|
|
13840
14414
|
return new GrokcliMcp({
|
|
13841
14415
|
outputRoot,
|
|
13842
14416
|
relativeDirPath: paths.relativeDirPath,
|
|
13843
14417
|
relativeFilePath: paths.relativeFilePath,
|
|
13844
|
-
fileContent:
|
|
14418
|
+
fileContent: applySharedConfigPatch({
|
|
14419
|
+
fileKey: sharedConfigFileKey(paths),
|
|
14420
|
+
feature: "mcp",
|
|
14421
|
+
existingContent: configTomlFileContent,
|
|
14422
|
+
patch: { mcp_servers: filteredMcpServers },
|
|
14423
|
+
filePath: configTomlFilePath
|
|
14424
|
+
}),
|
|
13845
14425
|
validate
|
|
13846
14426
|
});
|
|
13847
14427
|
}
|
|
@@ -13902,6 +14482,54 @@ function resolveHermesTimeout(config) {
|
|
|
13902
14482
|
if (typeof config.networkTimeout === "number") return config.networkTimeout;
|
|
13903
14483
|
}
|
|
13904
14484
|
/**
|
|
14485
|
+
* Copies the advanced Hermes-recognized per-server fields that have no canonical
|
|
14486
|
+
* alias — `auth` (`oauth` for OAuth 2.1/PKCE), mTLS `client_cert` (string PEM
|
|
14487
|
+
* path, or `[cert, key]`/`[cert, key, password]` list) and `client_key`,
|
|
14488
|
+
* `connect_timeout` (seconds), and `supports_parallel_tool_calls` — verbatim
|
|
14489
|
+
* from `source` to `target`. Field names are identical on both sides (the
|
|
14490
|
+
* canonical `McpServerSchema` is a `looseObject`), so this serves export and
|
|
14491
|
+
* import alike. See the Hermes mcp-config-reference.
|
|
14492
|
+
*/
|
|
14493
|
+
function copyHermesAdvancedFields(source, target) {
|
|
14494
|
+
if (typeof source.auth === "string") target.auth = source.auth;
|
|
14495
|
+
if (typeof source.client_cert === "string" || isStringArray(source.client_cert)) target.client_cert = source.client_cert;
|
|
14496
|
+
if (typeof source.client_key === "string") target.client_key = source.client_key;
|
|
14497
|
+
if (typeof source.connect_timeout === "number") target.connect_timeout = source.connect_timeout;
|
|
14498
|
+
if (typeof source.supports_parallel_tool_calls === "boolean") target.supports_parallel_tool_calls = source.supports_parallel_tool_calls;
|
|
14499
|
+
}
|
|
14500
|
+
/**
|
|
14501
|
+
* Builds Hermes's per-server `tools` block from a canonical server config. The
|
|
14502
|
+
* canonical `enabledTools`/`disabledTools` arrays become `include`/`exclude`,
|
|
14503
|
+
* and the boolean `promptsEnabled`/`resourcesEnabled` toggles become Hermes's
|
|
14504
|
+
* `prompts`/`resources` capability flags. Returns an empty object when the
|
|
14505
|
+
* server has no tool scoping (the caller omits the block in that case).
|
|
14506
|
+
*
|
|
14507
|
+
* Note: `promptsEnabled`/`resourcesEnabled` are canonical top-level keys rather
|
|
14508
|
+
* than a nested canonical `tools` object, because canonical `McpServerSchema.tools`
|
|
14509
|
+
* is reserved as a `string[]` (used by other tools) — reusing it for an object
|
|
14510
|
+
* would fail validation on the next `generate`.
|
|
14511
|
+
*/
|
|
14512
|
+
function buildHermesToolsBlock(config) {
|
|
14513
|
+
const tools = {};
|
|
14514
|
+
if (isStringArray(config.enabledTools)) tools.include = config.enabledTools;
|
|
14515
|
+
if (isStringArray(config.disabledTools)) tools.exclude = config.disabledTools;
|
|
14516
|
+
if (typeof config.promptsEnabled === "boolean") tools.prompts = config.promptsEnabled;
|
|
14517
|
+
if (typeof config.resourcesEnabled === "boolean") tools.resources = config.resourcesEnabled;
|
|
14518
|
+
return tools;
|
|
14519
|
+
}
|
|
14520
|
+
/**
|
|
14521
|
+
* Applies a Hermes per-server `tools` block back onto a canonical server config
|
|
14522
|
+
* (inverse of {@link buildHermesToolsBlock}): `include`/`exclude` become
|
|
14523
|
+
* `enabledTools`/`disabledTools`, and `prompts`/`resources` become the boolean
|
|
14524
|
+
* `promptsEnabled`/`resourcesEnabled` top-level toggles.
|
|
14525
|
+
*/
|
|
14526
|
+
function applyHermesToolsBlock(hermesTools, server) {
|
|
14527
|
+
if (isStringArray(hermesTools.include)) server.enabledTools = hermesTools.include;
|
|
14528
|
+
if (isStringArray(hermesTools.exclude)) server.disabledTools = hermesTools.exclude;
|
|
14529
|
+
if (typeof hermesTools.prompts === "boolean") server.promptsEnabled = hermesTools.prompts;
|
|
14530
|
+
if (typeof hermesTools.resources === "boolean") server.resourcesEnabled = hermesTools.resources;
|
|
14531
|
+
}
|
|
14532
|
+
/**
|
|
13905
14533
|
* Converts a single rulesync canonical MCP server into a Hermes `mcp_servers:` entry.
|
|
13906
14534
|
*
|
|
13907
14535
|
* Hermes is close to the MCP spec but not identical: `command` must be a single
|
|
@@ -13935,9 +14563,8 @@ function convertServerToHermes(config) {
|
|
|
13935
14563
|
if (config.disabled === true) out.enabled = false;
|
|
13936
14564
|
const timeout = resolveHermesTimeout(config);
|
|
13937
14565
|
if (timeout !== void 0) out.timeout = timeout;
|
|
13938
|
-
|
|
13939
|
-
|
|
13940
|
-
if (isStringArray(config.disabledTools)) tools.exclude = config.disabledTools;
|
|
14566
|
+
copyHermesAdvancedFields(config, out);
|
|
14567
|
+
const tools = buildHermesToolsBlock(config);
|
|
13941
14568
|
if (Object.keys(tools).length > 0) out.tools = tools;
|
|
13942
14569
|
return out;
|
|
13943
14570
|
}
|
|
@@ -13980,10 +14607,8 @@ function convertFromHermesFormat(mcpServers) {
|
|
|
13980
14607
|
if (isPlainObject$1(config.headers)) server.headers = omitPrototypePollutionKeys(config.headers);
|
|
13981
14608
|
if (config.enabled === false) server.disabled = true;
|
|
13982
14609
|
if (typeof config.timeout === "number") server.networkTimeout = config.timeout;
|
|
13983
|
-
|
|
13984
|
-
|
|
13985
|
-
if (isStringArray(config.tools.exclude)) server.disabledTools = config.tools.exclude;
|
|
13986
|
-
}
|
|
14610
|
+
copyHermesAdvancedFields(config, server);
|
|
14611
|
+
if (isRecord(config.tools)) applyHermesToolsBlock(config.tools, server);
|
|
13987
14612
|
result[name] = server;
|
|
13988
14613
|
}
|
|
13989
14614
|
return result;
|
|
@@ -14374,20 +14999,21 @@ var KiloMcp = class KiloMcp extends ToolMcp {
|
|
|
14374
14999
|
fileContent = await readFileContentOrNull(jsonPath);
|
|
14375
15000
|
if (fileContent) relativeFilePath = KILO_JSON_FILE_NAME;
|
|
14376
15001
|
}
|
|
14377
|
-
if (!fileContent) fileContent = JSON.stringify({ mcp: {} }, null, 2);
|
|
14378
|
-
const json = (0, jsonc_parser.parse)(fileContent);
|
|
14379
15002
|
const { mcp: convertedMcp, tools: mcpTools } = convertToKiloFormat(rulesyncMcp.getMcpServers());
|
|
14380
|
-
const { tools: _existingTools, ...jsonWithoutTools } = json;
|
|
14381
|
-
const newJson = {
|
|
14382
|
-
...jsonWithoutTools,
|
|
14383
|
-
mcp: convertedMcp,
|
|
14384
|
-
...Object.keys(mcpTools).length > 0 && { tools: mcpTools }
|
|
14385
|
-
};
|
|
14386
15003
|
return new KiloMcp({
|
|
14387
15004
|
outputRoot,
|
|
14388
15005
|
relativeDirPath: basePaths.relativeDirPath,
|
|
14389
15006
|
relativeFilePath,
|
|
14390
|
-
fileContent:
|
|
15007
|
+
fileContent: applySharedConfigPatch({
|
|
15008
|
+
fileKey: sharedConfigFileKey(basePaths),
|
|
15009
|
+
feature: "mcp",
|
|
15010
|
+
existingContent: fileContent ?? "",
|
|
15011
|
+
patch: {
|
|
15012
|
+
mcp: convertedMcp,
|
|
15013
|
+
tools: Object.keys(mcpTools).length > 0 ? mcpTools : void 0
|
|
15014
|
+
},
|
|
15015
|
+
filePath: (0, node_path.join)(jsonDir, relativeFilePath)
|
|
15016
|
+
}),
|
|
14391
15017
|
validate
|
|
14392
15018
|
});
|
|
14393
15019
|
}
|
|
@@ -14416,15 +15042,17 @@ var KiloMcp = class KiloMcp extends ToolMcp {
|
|
|
14416
15042
|
const json = fileContent ? (0, jsonc_parser.parse)(fileContent) : {};
|
|
14417
15043
|
const existingInstructions = Array.isArray(json.instructions) ? json.instructions.filter((entry) => typeof entry === "string") : [];
|
|
14418
15044
|
const mergedInstructions = Array.from(/* @__PURE__ */ new Set([...existingInstructions, ...instructions])).toSorted();
|
|
14419
|
-
const newJson = {
|
|
14420
|
-
...json,
|
|
14421
|
-
instructions: mergedInstructions
|
|
14422
|
-
};
|
|
14423
15045
|
return new KiloMcp({
|
|
14424
15046
|
outputRoot,
|
|
14425
15047
|
relativeDirPath: basePaths.relativeDirPath,
|
|
14426
15048
|
relativeFilePath,
|
|
14427
|
-
fileContent:
|
|
15049
|
+
fileContent: applySharedConfigPatch({
|
|
15050
|
+
fileKey: sharedConfigFileKey(basePaths),
|
|
15051
|
+
feature: "rules",
|
|
15052
|
+
existingContent: fileContent ?? "",
|
|
15053
|
+
patch: { instructions: mergedInstructions },
|
|
15054
|
+
filePath: (0, node_path.join)(jsonDir, relativeFilePath)
|
|
15055
|
+
}),
|
|
14428
15056
|
validate
|
|
14429
15057
|
});
|
|
14430
15058
|
}
|
|
@@ -14696,23 +15324,24 @@ var OpencodeMcp = class OpencodeMcp extends ToolMcp {
|
|
|
14696
15324
|
fileContent = await readFileContentOrNull(jsonPath);
|
|
14697
15325
|
if (fileContent) relativeFilePath = OPENCODE_JSON_FILE_NAME;
|
|
14698
15326
|
}
|
|
14699
|
-
if (!fileContent) fileContent = JSON.stringify({ mcp: {} }, null, 2);
|
|
14700
|
-
const json = (0, jsonc_parser.parse)(fileContent);
|
|
14701
15327
|
const { mcp: convertedMcp, tools: mcpTools } = convertToOpencodeFormat(convertEnvVarRefsToToolFormat({
|
|
14702
15328
|
mcpServers: rulesyncMcp.getMcpServers(),
|
|
14703
15329
|
replacement: "{env:$1}"
|
|
14704
15330
|
}));
|
|
14705
|
-
const { tools: _existingTools, ...jsonWithoutTools } = json;
|
|
14706
|
-
const newJson = {
|
|
14707
|
-
...jsonWithoutTools,
|
|
14708
|
-
mcp: convertedMcp,
|
|
14709
|
-
...Object.keys(mcpTools).length > 0 && { tools: mcpTools }
|
|
14710
|
-
};
|
|
14711
15331
|
return new OpencodeMcp({
|
|
14712
15332
|
outputRoot,
|
|
14713
15333
|
relativeDirPath: basePaths.relativeDirPath,
|
|
14714
15334
|
relativeFilePath,
|
|
14715
|
-
fileContent:
|
|
15335
|
+
fileContent: applySharedConfigPatch({
|
|
15336
|
+
fileKey: sharedConfigFileKey(basePaths),
|
|
15337
|
+
feature: "mcp",
|
|
15338
|
+
existingContent: fileContent ?? "",
|
|
15339
|
+
patch: {
|
|
15340
|
+
mcp: convertedMcp,
|
|
15341
|
+
tools: Object.keys(mcpTools).length > 0 ? mcpTools : void 0
|
|
15342
|
+
},
|
|
15343
|
+
filePath: (0, node_path.join)(jsonDir, relativeFilePath)
|
|
15344
|
+
}),
|
|
14716
15345
|
validate
|
|
14717
15346
|
});
|
|
14718
15347
|
}
|
|
@@ -14747,15 +15376,17 @@ var OpencodeMcp = class OpencodeMcp extends ToolMcp {
|
|
|
14747
15376
|
const json = fileContent ? (0, jsonc_parser.parse)(fileContent) : {};
|
|
14748
15377
|
const existingInstructions = Array.isArray(json.instructions) ? json.instructions.filter((entry) => typeof entry === "string") : [];
|
|
14749
15378
|
const mergedInstructions = Array.from(/* @__PURE__ */ new Set([...existingInstructions, ...instructions])).toSorted();
|
|
14750
|
-
const newJson = {
|
|
14751
|
-
...json,
|
|
14752
|
-
instructions: mergedInstructions
|
|
14753
|
-
};
|
|
14754
15379
|
return new OpencodeMcp({
|
|
14755
15380
|
outputRoot,
|
|
14756
15381
|
relativeDirPath: basePaths.relativeDirPath,
|
|
14757
15382
|
relativeFilePath,
|
|
14758
|
-
fileContent:
|
|
15383
|
+
fileContent: applySharedConfigPatch({
|
|
15384
|
+
fileKey: sharedConfigFileKey(basePaths),
|
|
15385
|
+
feature: "rules",
|
|
15386
|
+
existingContent: fileContent ?? "",
|
|
15387
|
+
patch: { instructions: mergedInstructions },
|
|
15388
|
+
filePath: (0, node_path.join)(jsonDir, relativeFilePath)
|
|
15389
|
+
}),
|
|
14759
15390
|
validate
|
|
14760
15391
|
});
|
|
14761
15392
|
}
|
|
@@ -14865,16 +15496,19 @@ var QwencodeMcp = class QwencodeMcp extends ToolMcp {
|
|
|
14865
15496
|
}
|
|
14866
15497
|
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false }) {
|
|
14867
15498
|
const paths = this.getSettablePaths({ global });
|
|
14868
|
-
const
|
|
14869
|
-
const
|
|
14870
|
-
...JSON.parse(fileContent),
|
|
14871
|
-
mcpServers: convertToQwencodeFormat(rulesyncMcp.getMcpServers())
|
|
14872
|
-
};
|
|
15499
|
+
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
15500
|
+
const existingContent = await readOrInitializeFileContent(filePath, JSON.stringify({ mcpServers: {} }, null, 2));
|
|
14873
15501
|
return new QwencodeMcp({
|
|
14874
15502
|
outputRoot,
|
|
14875
15503
|
relativeDirPath: paths.relativeDirPath,
|
|
14876
15504
|
relativeFilePath: paths.relativeFilePath,
|
|
14877
|
-
fileContent:
|
|
15505
|
+
fileContent: applySharedConfigPatch({
|
|
15506
|
+
fileKey: sharedConfigFileKey(paths),
|
|
15507
|
+
feature: "mcp",
|
|
15508
|
+
existingContent,
|
|
15509
|
+
patch: { mcpServers: convertToQwencodeFormat(rulesyncMcp.getMcpServers()) },
|
|
15510
|
+
filePath
|
|
15511
|
+
}),
|
|
14878
15512
|
validate
|
|
14879
15513
|
});
|
|
14880
15514
|
}
|
|
@@ -14959,13 +15593,20 @@ var ReasonixMcp = class ReasonixMcp extends ToolMcp {
|
|
|
14959
15593
|
}
|
|
14960
15594
|
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false }) {
|
|
14961
15595
|
const paths = this.getSettablePaths({ global });
|
|
14962
|
-
const
|
|
14963
|
-
|
|
15596
|
+
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
15597
|
+
const existingContent = await readFileContentOrNull(filePath) ?? "";
|
|
15598
|
+
const plugins = Object.entries(rulesyncMcp.getMcpServers()).map(([name, server]) => rulesyncMcpServerToReasonix(name, server));
|
|
14964
15599
|
return new ReasonixMcp({
|
|
14965
15600
|
outputRoot,
|
|
14966
15601
|
relativeDirPath: paths.relativeDirPath,
|
|
14967
15602
|
relativeFilePath: paths.relativeFilePath,
|
|
14968
|
-
fileContent:
|
|
15603
|
+
fileContent: applySharedConfigPatch({
|
|
15604
|
+
fileKey: sharedConfigFileKey(paths),
|
|
15605
|
+
feature: "mcp",
|
|
15606
|
+
existingContent,
|
|
15607
|
+
patch: { plugins },
|
|
15608
|
+
filePath
|
|
15609
|
+
}),
|
|
14969
15610
|
validate,
|
|
14970
15611
|
global
|
|
14971
15612
|
});
|
|
@@ -15440,13 +16081,20 @@ var VibeMcp = class VibeMcp extends ToolMcp {
|
|
|
15440
16081
|
}
|
|
15441
16082
|
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false }) {
|
|
15442
16083
|
const paths = this.getSettablePaths({ global });
|
|
15443
|
-
const
|
|
15444
|
-
|
|
16084
|
+
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
16085
|
+
const existingContent = await readFileContentOrNull(filePath) ?? "";
|
|
16086
|
+
const mcpServers = Object.entries(rulesyncMcp.getMcpServers()).map(([name, server]) => rulesyncMcpServerToVibe(name, server));
|
|
15445
16087
|
return new VibeMcp({
|
|
15446
16088
|
outputRoot,
|
|
15447
16089
|
relativeDirPath: paths.relativeDirPath,
|
|
15448
16090
|
relativeFilePath: paths.relativeFilePath,
|
|
15449
|
-
fileContent:
|
|
16091
|
+
fileContent: applySharedConfigPatch({
|
|
16092
|
+
fileKey: sharedConfigFileKey(paths),
|
|
16093
|
+
feature: "mcp",
|
|
16094
|
+
existingContent,
|
|
16095
|
+
patch: { mcp_servers: mcpServers },
|
|
16096
|
+
filePath
|
|
16097
|
+
}),
|
|
15450
16098
|
validate,
|
|
15451
16099
|
global
|
|
15452
16100
|
});
|
|
@@ -15677,16 +16325,19 @@ var ZedMcp = class ZedMcp extends ToolMcp {
|
|
|
15677
16325
|
}
|
|
15678
16326
|
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false }) {
|
|
15679
16327
|
const paths = this.getSettablePaths({ global });
|
|
15680
|
-
const
|
|
15681
|
-
const
|
|
15682
|
-
...JSON.parse(fileContent),
|
|
15683
|
-
context_servers: rulesyncMcp.getMcpServers()
|
|
15684
|
-
};
|
|
16328
|
+
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
16329
|
+
const existingContent = await readOrInitializeFileContent(filePath, "{}");
|
|
15685
16330
|
return new ZedMcp({
|
|
15686
16331
|
outputRoot,
|
|
15687
16332
|
relativeDirPath: paths.relativeDirPath,
|
|
15688
16333
|
relativeFilePath: paths.relativeFilePath,
|
|
15689
|
-
fileContent:
|
|
16334
|
+
fileContent: applySharedConfigPatch({
|
|
16335
|
+
fileKey: sharedConfigFileKey(paths),
|
|
16336
|
+
feature: "mcp",
|
|
16337
|
+
existingContent,
|
|
16338
|
+
patch: { context_servers: rulesyncMcp.getMcpServers() },
|
|
16339
|
+
filePath
|
|
16340
|
+
}),
|
|
15690
16341
|
validate
|
|
15691
16342
|
});
|
|
15692
16343
|
}
|
|
@@ -16252,18 +16903,27 @@ const CursorPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
16252
16903
|
* Tool-scoped override block for Qwen Code. Qwen's `settings.json` exposes
|
|
16253
16904
|
* autonomy/sandbox controls with no canonical permission category — under
|
|
16254
16905
|
* `tools` (`approvalMode` = plan/default/auto-edit/auto/yolo, `autoAccept`,
|
|
16255
|
-
* `sandbox`, `sandboxImage`, `disabled`) and `security` (`folderTrust`).
|
|
16256
|
-
*
|
|
16257
|
-
*
|
|
16258
|
-
*
|
|
16259
|
-
*
|
|
16906
|
+
* `sandbox`, `sandboxImage`, `disabled`) and `security` (`folderTrust`). It also
|
|
16907
|
+
* exposes `permissions.autoMode` (the Auto Mode classifier config:
|
|
16908
|
+
* `hints.{allow,softDeny,hardDeny}`, `environment`, `classifyAllShell` — see
|
|
16909
|
+
* https://qwenlm.github.io/qwen-code-docs/en/users/features/auto-mode/), which
|
|
16910
|
+
* likewise has no canonical category. Fields placed here are merged into the
|
|
16911
|
+
* matching `settings.json` group and emitted only for Qwen, while the shared
|
|
16912
|
+
* `permission` block continues to drive the `permissions.allow`/`ask`/`deny`
|
|
16913
|
+
* arrays. Kept `looseObject` (verbatim passthrough) so any current or future
|
|
16914
|
+
* `tools`/`security`/`autoMode` key can be authored.
|
|
16260
16915
|
*
|
|
16261
16916
|
* @example
|
|
16262
|
-
* {
|
|
16917
|
+
* {
|
|
16918
|
+
* "tools": { "approvalMode": "auto-edit" },
|
|
16919
|
+
* "security": { "folderTrust": { "enabled": true } },
|
|
16920
|
+
* "autoMode": { "hints": { "allow": ["Running tests"] }, "classifyAllShell": true }
|
|
16921
|
+
* }
|
|
16263
16922
|
*/
|
|
16264
16923
|
const QwencodePermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
16265
16924
|
tools: zod_mini.z.optional(zod_mini.z.looseObject({})),
|
|
16266
|
-
security: zod_mini.z.optional(zod_mini.z.looseObject({}))
|
|
16925
|
+
security: zod_mini.z.optional(zod_mini.z.looseObject({})),
|
|
16926
|
+
autoMode: zod_mini.z.optional(zod_mini.z.looseObject({}))
|
|
16267
16927
|
});
|
|
16268
16928
|
/**
|
|
16269
16929
|
* Tool-scoped override block for Reasonix. Reasonix has security axes orthogonal
|
|
@@ -16290,9 +16950,11 @@ const ReasonixPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
16290
16950
|
* exposes security controls with no canonical per-command allow/ask/deny slot —
|
|
16291
16951
|
* `commandBlocklist` (a hard-block tier that can never be approved, distinct from
|
|
16292
16952
|
* an approvable `deny`), `networkPolicy` (`allowedIps`), `sandbox`
|
|
16293
|
-
* (`enabled`/`mode`/`filesystem`/`network`), `mcpPolicy`,
|
|
16953
|
+
* (`enabled`/`mode`/`filesystem`/`network`), `mcpPolicy`,
|
|
16954
|
+
* `mcpAutonomyOverrides` (per-MCP-tool autonomy levels), `enableDroidShield`,
|
|
16294
16955
|
* and autonomy settings (`sessionDefaultSettings`, `maxAutonomyLevel`,
|
|
16295
|
-
* `interactionMode`). Fields placed here are merged
|
|
16956
|
+
* `subagentAutonomyLevel`, `interactionMode`). Fields placed here are merged
|
|
16957
|
+
* into `settings.json` and
|
|
16296
16958
|
* emitted only for Factory Droid, while the shared `permission` block continues
|
|
16297
16959
|
* to drive `commandAllowlist`/`commandDenylist`. Kept `looseObject` passthrough.
|
|
16298
16960
|
*
|
|
@@ -16485,6 +17147,41 @@ const KiroPermissionsOverrideSchema = zod_mini.z.looseObject({ toolsSettings: zo
|
|
|
16485
17147
|
}))
|
|
16486
17148
|
})) });
|
|
16487
17149
|
/**
|
|
17150
|
+
* Codex CLI's approval-workflow policy. Serialized as a kebab-case string in
|
|
17151
|
+
* `.codex/config.toml`. `on-failure` is a legacy alias for `on-request` that
|
|
17152
|
+
* Codex still accepts, so it is included so existing configs round-trip. The
|
|
17153
|
+
* granular table form (`{ granular = { … } }`) is modeled separately in the
|
|
17154
|
+
* override union.
|
|
17155
|
+
* @see https://learn.chatgpt.com/docs/config-file/config-reference
|
|
17156
|
+
*/
|
|
17157
|
+
const CodexApprovalPolicySchema = zod_mini.z.enum([
|
|
17158
|
+
"untrusted",
|
|
17159
|
+
"on-request",
|
|
17160
|
+
"on-failure",
|
|
17161
|
+
"never"
|
|
17162
|
+
]);
|
|
17163
|
+
/**
|
|
17164
|
+
* Codex CLI's classic sandbox mode. Serialized as a kebab-case string in
|
|
17165
|
+
* `.codex/config.toml`.
|
|
17166
|
+
* @see https://learn.chatgpt.com/docs/config-file/config-reference
|
|
17167
|
+
*/
|
|
17168
|
+
const CodexSandboxModeSchema = zod_mini.z.enum([
|
|
17169
|
+
"read-only",
|
|
17170
|
+
"workspace-write",
|
|
17171
|
+
"danger-full-access"
|
|
17172
|
+
]);
|
|
17173
|
+
/**
|
|
17174
|
+
* Codex CLI's reviewer for approval requests. `guardian_subagent` is a legacy
|
|
17175
|
+
* value Codex still accepts for backward compatibility, so it is included so
|
|
17176
|
+
* existing configs round-trip through the rulesync model.
|
|
17177
|
+
* @see https://learn.chatgpt.com/docs/config-file/config-reference
|
|
17178
|
+
*/
|
|
17179
|
+
const CodexApprovalsReviewerSchema = zod_mini.z.enum([
|
|
17180
|
+
"user",
|
|
17181
|
+
"auto_review",
|
|
17182
|
+
"guardian_subagent"
|
|
17183
|
+
]);
|
|
17184
|
+
/**
|
|
16488
17185
|
* Codex CLI-scoped permission override.
|
|
16489
17186
|
*
|
|
16490
17187
|
* Codex CLI's permission surface is richer than the canonical allow/ask/deny
|
|
@@ -16493,15 +17190,16 @@ const KiroPermissionsOverrideSchema = zod_mini.z.looseObject({ toolsSettings: zo
|
|
|
16493
17190
|
* override whose fields are written verbatim as top-level `.codex/config.toml`
|
|
16494
17191
|
* keys (the override wins per key; existing sibling keys the user set directly
|
|
16495
17192
|
* are preserved):
|
|
16496
|
-
* - `approval_policy` — `untrusted` | `on-request`
|
|
16497
|
-
* `{ granular = { … } }` table (kept verbatim; the granular
|
|
16498
|
-
* required fields that are brittle to model as typed keys).
|
|
17193
|
+
* - `approval_policy` — `untrusted` | `on-request` (legacy alias `on-failure`) |
|
|
17194
|
+
* `never`, or a `{ granular = { … } }` table (kept verbatim; the granular
|
|
17195
|
+
* schema has required fields that are brittle to model as typed keys).
|
|
16499
17196
|
* - `sandbox_mode` — `read-only` | `workspace-write` | `danger-full-access`,
|
|
16500
17197
|
* with the sibling `sandbox_workspace_write` table (`network_access`,
|
|
16501
17198
|
* `writable_roots`, …).
|
|
16502
17199
|
* - `apps` — per-app tool gating (`apps.<id>.tools.<tool>.approval_mode` /
|
|
16503
17200
|
* `.enabled`, `apps.<id>.default_tools_approval_mode`).
|
|
16504
|
-
* - `approvals_reviewer` — the reviewer-approval surface
|
|
17201
|
+
* - `approvals_reviewer` — the reviewer-approval surface (`user` | `auto_review`
|
|
17202
|
+
* | `guardian_subagent`), or a table for the richer reviewer config.
|
|
16505
17203
|
*
|
|
16506
17204
|
* Two surfaces are deliberately NOT authorable here so the override can never
|
|
16507
17205
|
* clobber a feature-owned key: `mcp_servers.*` per-MCP gating is owned by the
|
|
@@ -16519,11 +17217,11 @@ const KiroPermissionsOverrideSchema = zod_mini.z.looseObject({ toolsSettings: zo
|
|
|
16519
17217
|
* "sandbox_workspace_write": { "network_access": true } }
|
|
16520
17218
|
*/
|
|
16521
17219
|
const CodexcliPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
16522
|
-
approval_policy: zod_mini.z.optional(zod_mini.z.union([
|
|
16523
|
-
sandbox_mode: zod_mini.z.optional(
|
|
17220
|
+
approval_policy: zod_mini.z.optional(zod_mini.z.union([CodexApprovalPolicySchema, zod_mini.z.looseObject({})])),
|
|
17221
|
+
sandbox_mode: zod_mini.z.optional(CodexSandboxModeSchema),
|
|
16524
17222
|
sandbox_workspace_write: zod_mini.z.optional(zod_mini.z.looseObject({})),
|
|
16525
17223
|
apps: zod_mini.z.optional(zod_mini.z.looseObject({})),
|
|
16526
|
-
approvals_reviewer: zod_mini.z.optional(zod_mini.z.union([
|
|
17224
|
+
approvals_reviewer: zod_mini.z.optional(zod_mini.z.union([CodexApprovalsReviewerSchema, zod_mini.z.looseObject({})]))
|
|
16527
17225
|
});
|
|
16528
17226
|
/**
|
|
16529
17227
|
* Permissions configuration.
|
|
@@ -16855,25 +17553,29 @@ var AmpPermissions = class AmpPermissions extends ToolPermissions {
|
|
|
16855
17553
|
const override = config.amp;
|
|
16856
17554
|
const authoredExtras = override?.permissions ? toPermissionsList(override.permissions) : [];
|
|
16857
17555
|
const preservedDelegates = override?.permissions ? [] : toPermissionsList(json[AMP_PERMISSIONS_KEY]).filter((entry) => entry.action === "delegate");
|
|
16858
|
-
const newJson = {
|
|
16859
|
-
...json,
|
|
16860
|
-
[AMP_TOOLS_DISABLE_KEY]: disable
|
|
16861
|
-
};
|
|
16862
17556
|
const mergedPermissions = mergeAmpPermissions([
|
|
16863
17557
|
...permissions,
|
|
16864
17558
|
...authoredExtras,
|
|
16865
17559
|
...preservedDelegates
|
|
16866
17560
|
]);
|
|
16867
|
-
|
|
16868
|
-
|
|
16869
|
-
|
|
16870
|
-
|
|
16871
|
-
if (override?.
|
|
17561
|
+
const patch = {
|
|
17562
|
+
[AMP_TOOLS_DISABLE_KEY]: disable,
|
|
17563
|
+
[AMP_PERMISSIONS_KEY]: mergedPermissions.length > 0 ? mergedPermissions : void 0
|
|
17564
|
+
};
|
|
17565
|
+
if (override?.guardedFiles?.allowlist !== void 0) patch[AMP_GUARDED_FILES_ALLOWLIST_KEY] = override.guardedFiles.allowlist;
|
|
17566
|
+
if (override?.dangerouslyAllowAll !== void 0) patch[AMP_DANGEROUSLY_ALLOW_ALL_KEY] = override.dangerouslyAllowAll;
|
|
17567
|
+
if (override?.mcpPermissions !== void 0) patch[AMP_MCP_PERMISSIONS_KEY] = override.mcpPermissions;
|
|
16872
17568
|
return new AmpPermissions({
|
|
16873
17569
|
outputRoot,
|
|
16874
17570
|
relativeDirPath: basePaths.relativeDirPath,
|
|
16875
17571
|
relativeFilePath,
|
|
16876
|
-
fileContent:
|
|
17572
|
+
fileContent: applySharedConfigPatch({
|
|
17573
|
+
fileKey: sharedConfigFileKey(basePaths),
|
|
17574
|
+
feature: "permissions",
|
|
17575
|
+
existingContent: fileContent ?? "",
|
|
17576
|
+
patch,
|
|
17577
|
+
filePath: (0, node_path.join)(jsonDir, relativeFilePath)
|
|
17578
|
+
}),
|
|
16877
17579
|
validate: true
|
|
16878
17580
|
});
|
|
16879
17581
|
}
|
|
@@ -17815,11 +18517,13 @@ var AugmentcodePermissions = class AugmentcodePermissions extends ToolPermission
|
|
|
17815
18517
|
...preservedBasicEntries,
|
|
17816
18518
|
...authoredBasics
|
|
17817
18519
|
]);
|
|
17818
|
-
const
|
|
17819
|
-
|
|
17820
|
-
|
|
17821
|
-
|
|
17822
|
-
|
|
18520
|
+
const fileContent = applySharedConfigPatch({
|
|
18521
|
+
fileKey: sharedConfigFileKey(paths),
|
|
18522
|
+
feature: "permissions",
|
|
18523
|
+
existingContent,
|
|
18524
|
+
patch: { toolPermissions: [...specialEntries, ...sortedBasic] },
|
|
18525
|
+
filePath
|
|
18526
|
+
});
|
|
17823
18527
|
return new AugmentcodePermissions({
|
|
17824
18528
|
outputRoot,
|
|
17825
18529
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -18411,13 +19115,6 @@ const WORKSPACE_WIDE_WRITE_PATTERNS = /* @__PURE__ */ new Set([
|
|
|
18411
19115
|
]);
|
|
18412
19116
|
const CODEX_MINIMAL_KEY = ":minimal";
|
|
18413
19117
|
const GLOBAL_WILDCARD_DOMAIN = "*";
|
|
18414
|
-
const CODEXCLI_OVERRIDE_KEYS = [
|
|
18415
|
-
"approval_policy",
|
|
18416
|
-
"sandbox_mode",
|
|
18417
|
-
"sandbox_workspace_write",
|
|
18418
|
-
"apps",
|
|
18419
|
-
"approvals_reviewer"
|
|
18420
|
-
];
|
|
18421
19118
|
var CodexcliPermissions = class CodexcliPermissions extends ToolPermissions {
|
|
18422
19119
|
static getSettablePaths(_options = {}) {
|
|
18423
19120
|
return {
|
|
@@ -18441,26 +19138,27 @@ var CodexcliPermissions = class CodexcliPermissions extends ToolPermissions {
|
|
|
18441
19138
|
}
|
|
18442
19139
|
static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, validate = true, logger, global = false }) {
|
|
18443
19140
|
const paths = this.getSettablePaths({ global });
|
|
18444
|
-
const
|
|
18445
|
-
const
|
|
19141
|
+
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
19142
|
+
const existingContent = await readFileContentOrNull(filePath) ?? "";
|
|
19143
|
+
const existing = toMutableTable(smol_toml.parse(existingContent || smol_toml.stringify({})));
|
|
18446
19144
|
const newProfile = convertRulesyncToCodexProfile({
|
|
18447
19145
|
config: rulesyncPermissions.getJson(),
|
|
18448
19146
|
logger
|
|
18449
19147
|
});
|
|
18450
|
-
const permissionsTable = toMutableTable(
|
|
19148
|
+
const permissionsTable = toMutableTable(existing.permissions);
|
|
18451
19149
|
const { profile: existingProfile, domainsHadUnknown: existingDomainsHadUnknown } = toCodexProfile(permissionsTable[RULESYNC_PROFILE_NAME]);
|
|
18452
|
-
|
|
18453
|
-
|
|
18454
|
-
|
|
18455
|
-
|
|
19150
|
+
warnAboutPreservedProfileState({
|
|
19151
|
+
existingProfile,
|
|
19152
|
+
newProfile,
|
|
19153
|
+
existingDomainsHadUnknown,
|
|
19154
|
+
logger
|
|
19155
|
+
});
|
|
18456
19156
|
permissionsTable[RULESYNC_PROFILE_NAME] = mergeWithExistingProfile({
|
|
18457
19157
|
newProfile,
|
|
18458
19158
|
existingProfile
|
|
18459
19159
|
});
|
|
18460
|
-
|
|
18461
|
-
|
|
18462
|
-
applyCodexcliOverride({
|
|
18463
|
-
parsed,
|
|
19160
|
+
const overridePatch = computeCodexcliOverridePatch({
|
|
19161
|
+
existing,
|
|
18464
19162
|
override: rulesyncPermissions.getJson().codexcli,
|
|
18465
19163
|
logger
|
|
18466
19164
|
});
|
|
@@ -18468,7 +19166,17 @@ var CodexcliPermissions = class CodexcliPermissions extends ToolPermissions {
|
|
|
18468
19166
|
outputRoot,
|
|
18469
19167
|
relativeDirPath: paths.relativeDirPath,
|
|
18470
19168
|
relativeFilePath: paths.relativeFilePath,
|
|
18471
|
-
fileContent:
|
|
19169
|
+
fileContent: applySharedConfigPatch({
|
|
19170
|
+
fileKey: sharedConfigFileKey(paths),
|
|
19171
|
+
feature: "permissions",
|
|
19172
|
+
existingContent,
|
|
19173
|
+
patch: {
|
|
19174
|
+
permissions: permissionsTable,
|
|
19175
|
+
default_permissions: RULESYNC_PROFILE_NAME,
|
|
19176
|
+
...overridePatch
|
|
19177
|
+
},
|
|
19178
|
+
filePath
|
|
19179
|
+
}),
|
|
18472
19180
|
validate
|
|
18473
19181
|
});
|
|
18474
19182
|
}
|
|
@@ -18638,6 +19346,12 @@ function toCodexProfile(value) {
|
|
|
18638
19346
|
domainsHadUnknown
|
|
18639
19347
|
};
|
|
18640
19348
|
}
|
|
19349
|
+
function warnAboutPreservedProfileState({ existingProfile, newProfile, existingDomainsHadUnknown, logger }) {
|
|
19350
|
+
if (existingProfile?.extends !== void 0 && existingProfile.extends !== newProfile.extends) logger?.warn(`Existing "extends" value "${existingProfile.extends}" will be replaced by Rulesync-managed "${newProfile.extends ?? "(none)"}".`);
|
|
19351
|
+
if (existingProfile?.network?.unix_sockets !== void 0) logger?.warn(`Preserving existing "network.unix_sockets" from config. Review these entries manually as they may grant broad system access.`);
|
|
19352
|
+
if (existingProfile?.network?.mode !== void 0) logger?.warn(`Preserving existing "network.mode" from config. Review this value manually as it may grant broader network access than the Rulesync-managed domain rules.`);
|
|
19353
|
+
if (existingDomainsHadUnknown) logger?.warn(`Existing "network.domains" contained unrecognized values. These entries were skipped and will not be imported.`);
|
|
19354
|
+
}
|
|
18641
19355
|
function mergeWithExistingProfile({ newProfile, existingProfile }) {
|
|
18642
19356
|
if (!existingProfile) return newProfile;
|
|
18643
19357
|
const mergedNetwork = { ...newProfile.network };
|
|
@@ -18688,8 +19402,9 @@ function toMutableTable(value) {
|
|
|
18688
19402
|
function isPlainObject(value) {
|
|
18689
19403
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
18690
19404
|
}
|
|
18691
|
-
function
|
|
18692
|
-
|
|
19405
|
+
function computeCodexcliOverridePatch({ existing, override, logger }) {
|
|
19406
|
+
const patch = {};
|
|
19407
|
+
if (!override) return patch;
|
|
18693
19408
|
const allowed = new Set(CODEXCLI_OVERRIDE_KEYS);
|
|
18694
19409
|
for (const [key, value] of Object.entries(override)) {
|
|
18695
19410
|
if (!allowed.has(key)) {
|
|
@@ -18697,12 +19412,13 @@ function applyCodexcliOverride({ parsed, override, logger }) {
|
|
|
18697
19412
|
continue;
|
|
18698
19413
|
}
|
|
18699
19414
|
if (value === void 0) continue;
|
|
18700
|
-
const
|
|
18701
|
-
|
|
18702
|
-
...
|
|
19415
|
+
const existingValue = existing[key];
|
|
19416
|
+
patch[key] = isPlainObject(existingValue) && isPlainObject(value) ? {
|
|
19417
|
+
...existingValue,
|
|
18703
19418
|
...value
|
|
18704
19419
|
} : value;
|
|
18705
19420
|
}
|
|
19421
|
+
return patch;
|
|
18706
19422
|
}
|
|
18707
19423
|
function extractCodexcliOverride(table) {
|
|
18708
19424
|
const override = {};
|
|
@@ -19263,15 +19979,17 @@ var DevinPermissions = class DevinPermissions extends ToolPermissions {
|
|
|
19263
19979
|
else delete mergedPermissions.ask;
|
|
19264
19980
|
if (mergedDeny.length > 0) mergedPermissions.deny = mergedDeny;
|
|
19265
19981
|
else delete mergedPermissions.deny;
|
|
19266
|
-
const merged = {
|
|
19267
|
-
...settings,
|
|
19268
|
-
permissions: mergedPermissions
|
|
19269
|
-
};
|
|
19270
19982
|
return new DevinPermissions({
|
|
19271
19983
|
outputRoot,
|
|
19272
19984
|
relativeDirPath: paths.relativeDirPath,
|
|
19273
19985
|
relativeFilePath: paths.relativeFilePath,
|
|
19274
|
-
fileContent:
|
|
19986
|
+
fileContent: applySharedConfigPatch({
|
|
19987
|
+
fileKey: sharedConfigFileKey(paths),
|
|
19988
|
+
feature: "permissions",
|
|
19989
|
+
existingContent,
|
|
19990
|
+
patch: { permissions: mergedPermissions },
|
|
19991
|
+
filePath
|
|
19992
|
+
}),
|
|
19275
19993
|
validate
|
|
19276
19994
|
});
|
|
19277
19995
|
}
|
|
@@ -19364,9 +20082,11 @@ const FACTORYDROID_OVERRIDE_KEYS = [
|
|
|
19364
20082
|
"networkPolicy",
|
|
19365
20083
|
"sandbox",
|
|
19366
20084
|
"mcpPolicy",
|
|
20085
|
+
"mcpAutonomyOverrides",
|
|
19367
20086
|
"enableDroidShield",
|
|
19368
20087
|
"sessionDefaultSettings",
|
|
19369
20088
|
"maxAutonomyLevel",
|
|
20089
|
+
"subagentAutonomyLevel",
|
|
19370
20090
|
"interactionMode"
|
|
19371
20091
|
];
|
|
19372
20092
|
/**
|
|
@@ -19724,7 +20444,6 @@ function convertGoosePermissionConfigToRulesync(userPermission) {
|
|
|
19724
20444
|
}
|
|
19725
20445
|
//#endregion
|
|
19726
20446
|
//#region src/features/permissions/grokcli-permissions.ts
|
|
19727
|
-
const GROKCLI_GLOBAL_ONLY_MESSAGE = "Grok CLI permissions are global-only; use --global to sync ~/.grok/config.toml";
|
|
19728
20447
|
const GROKCLI_UI_KEY = "ui";
|
|
19729
20448
|
const GROKCLI_PERMISSION_MODE_KEY = "permission_mode";
|
|
19730
20449
|
const GROKCLI_PERMISSION_KEY = "permission";
|
|
@@ -19826,13 +20545,19 @@ function parseGrokEntry(entry) {
|
|
|
19826
20545
|
* compatibility with older Grok versions: `always-approve` when the config is
|
|
19827
20546
|
* pure-`allow`, otherwise `ask` (conservative — it is never `always-approve`
|
|
19828
20547
|
* while any `deny`/`ask` rule exists, so it never contradicts the fine-grained
|
|
19829
|
-
* arrays).
|
|
19830
|
-
*
|
|
19831
|
-
*
|
|
19832
|
-
*
|
|
19833
|
-
*
|
|
20548
|
+
* arrays). It is a user-level UI setting, so it is only written in global scope
|
|
20549
|
+
* (see below).
|
|
20550
|
+
*
|
|
20551
|
+
* Both scopes are supported: the adapter syncs the project-level
|
|
20552
|
+
* `./.grok/config.toml` (default) and the user-level `~/.grok/config.toml`
|
|
20553
|
+
* (with `--global`). Grok documents that "Project configs are limited to MCP
|
|
20554
|
+
* servers, plugins, and permission rules, not full user configs"
|
|
20555
|
+
* (https://docs.x.ai/build/settings), so the project config carries only the
|
|
20556
|
+
* fine-grained `[permission]` rules; the coarse `[ui] permission_mode` UI toggle
|
|
20557
|
+
* is written in global scope only. The shared config is merged in
|
|
19834
20558
|
* place: rulesync owns the `[permission]` `allow`/`deny`/`ask` entries for the
|
|
19835
|
-
* tools it models and the `[ui] permission_mode` value, while
|
|
20559
|
+
* tools it models and (in global scope) the `[ui] permission_mode` value, while
|
|
20560
|
+
* every other key
|
|
19836
20561
|
* (e.g. `[mcp_servers]`, `[permission] rules`, `[sandbox]`) and any user-authored
|
|
19837
20562
|
* entries for tools rulesync cannot model (e.g. `WebSearch`) are preserved. The
|
|
19838
20563
|
* file is never deleted.
|
|
@@ -19854,7 +20579,6 @@ var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
|
|
|
19854
20579
|
};
|
|
19855
20580
|
}
|
|
19856
20581
|
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
19857
|
-
if (!global) throw new Error(GROKCLI_GLOBAL_ONLY_MESSAGE);
|
|
19858
20582
|
const paths = GrokcliPermissions.getSettablePaths({ global });
|
|
19859
20583
|
const fileContent = await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "";
|
|
19860
20584
|
return new GrokcliPermissions({
|
|
@@ -19863,11 +20587,10 @@ var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
|
|
|
19863
20587
|
relativeFilePath: paths.relativeFilePath,
|
|
19864
20588
|
fileContent,
|
|
19865
20589
|
validate,
|
|
19866
|
-
global
|
|
20590
|
+
global
|
|
19867
20591
|
});
|
|
19868
20592
|
}
|
|
19869
20593
|
static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, logger, global = false }) {
|
|
19870
|
-
if (!global) throw new Error(GROKCLI_GLOBAL_ONLY_MESSAGE);
|
|
19871
20594
|
const paths = GrokcliPermissions.getSettablePaths({ global });
|
|
19872
20595
|
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
19873
20596
|
const existingContent = await readFileContentOrNull(filePath) ?? "";
|
|
@@ -19880,25 +20603,32 @@ var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
|
|
|
19880
20603
|
const config = rulesyncPermissions.getJson();
|
|
19881
20604
|
const existingPermission = isRecord(parsed[GROKCLI_PERMISSION_KEY]) ? parsed[GROKCLI_PERMISSION_KEY] : {};
|
|
19882
20605
|
const buckets = buildGrokPermissionArrays(config, existingPermission, logger);
|
|
19883
|
-
|
|
20606
|
+
const permission = {
|
|
19884
20607
|
...existingPermission,
|
|
19885
20608
|
allow: buckets.allow,
|
|
19886
20609
|
deny: buckets.deny,
|
|
19887
20610
|
ask: buckets.ask
|
|
19888
20611
|
};
|
|
19889
|
-
const
|
|
19890
|
-
|
|
19891
|
-
|
|
19892
|
-
|
|
19893
|
-
[GROKCLI_PERMISSION_MODE_KEY]: mode
|
|
19894
|
-
};
|
|
20612
|
+
const uiPatch = global ? { [GROKCLI_UI_KEY]: {
|
|
20613
|
+
...isRecord(parsed[GROKCLI_UI_KEY]) ? parsed[GROKCLI_UI_KEY] : {},
|
|
20614
|
+
[GROKCLI_PERMISSION_MODE_KEY]: deriveGrokPermissionMode(config)
|
|
20615
|
+
} } : {};
|
|
19895
20616
|
return new GrokcliPermissions({
|
|
19896
20617
|
outputRoot,
|
|
19897
20618
|
relativeDirPath: paths.relativeDirPath,
|
|
19898
20619
|
relativeFilePath: paths.relativeFilePath,
|
|
19899
|
-
fileContent:
|
|
20620
|
+
fileContent: applySharedConfigPatch({
|
|
20621
|
+
fileKey: sharedConfigFileKey(paths),
|
|
20622
|
+
feature: "permissions",
|
|
20623
|
+
existingContent,
|
|
20624
|
+
patch: {
|
|
20625
|
+
[GROKCLI_PERMISSION_KEY]: permission,
|
|
20626
|
+
...uiPatch
|
|
20627
|
+
},
|
|
20628
|
+
filePath
|
|
20629
|
+
}),
|
|
19900
20630
|
validate: true,
|
|
19901
|
-
global
|
|
20631
|
+
global
|
|
19902
20632
|
});
|
|
19903
20633
|
}
|
|
19904
20634
|
toRulesyncPermissions() {
|
|
@@ -19919,14 +20649,14 @@ var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
|
|
|
19919
20649
|
error: null
|
|
19920
20650
|
};
|
|
19921
20651
|
}
|
|
19922
|
-
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
20652
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
19923
20653
|
return new GrokcliPermissions({
|
|
19924
20654
|
outputRoot,
|
|
19925
20655
|
relativeDirPath,
|
|
19926
20656
|
relativeFilePath,
|
|
19927
20657
|
fileContent: "",
|
|
19928
20658
|
validate: false,
|
|
19929
|
-
global
|
|
20659
|
+
global
|
|
19930
20660
|
});
|
|
19931
20661
|
}
|
|
19932
20662
|
};
|
|
@@ -20599,7 +21329,16 @@ var KiroPermissions = class KiroPermissions extends ToolPermissions {
|
|
|
20599
21329
|
outputRoot,
|
|
20600
21330
|
relativeDirPath: paths.relativeDirPath,
|
|
20601
21331
|
relativeFilePath: paths.relativeFilePath,
|
|
20602
|
-
fileContent:
|
|
21332
|
+
fileContent: applySharedConfigPatch({
|
|
21333
|
+
fileKey: sharedConfigFileKey(paths),
|
|
21334
|
+
feature: "permissions",
|
|
21335
|
+
existingContent,
|
|
21336
|
+
patch: {
|
|
21337
|
+
allowedTools: next.allowedTools,
|
|
21338
|
+
toolsSettings: next.toolsSettings
|
|
21339
|
+
},
|
|
21340
|
+
filePath
|
|
21341
|
+
}),
|
|
20603
21342
|
validate
|
|
20604
21343
|
});
|
|
20605
21344
|
}
|
|
@@ -20832,6 +21571,22 @@ const CANONICAL_PERMISSION_CATEGORIES = /* @__PURE__ */ new Set([
|
|
|
20832
21571
|
"notebookedit",
|
|
20833
21572
|
"agent"
|
|
20834
21573
|
]);
|
|
21574
|
+
/**
|
|
21575
|
+
* Translate between rulesync's canonical permission category names and
|
|
21576
|
+
* OpenCode's native permission keys. OpenCode has no `agent` key — subagent
|
|
21577
|
+
* launches are gated by the `task` key (see the documented key list at
|
|
21578
|
+
* https://opencode.ai/docs/permissions/). Without this translation a canonical
|
|
21579
|
+
* `agent: deny` would be written verbatim into `opencode.json` and silently
|
|
21580
|
+
* ignored by OpenCode. Unknown names pass through unchanged.
|
|
21581
|
+
*/
|
|
21582
|
+
const CANONICAL_TO_OPENCODE_PERMISSION_KEYS = { agent: "task" };
|
|
21583
|
+
const OPENCODE_TO_CANONICAL_PERMISSION_KEYS = Object.fromEntries(Object.entries(CANONICAL_TO_OPENCODE_PERMISSION_KEYS).map(([canonical, opencode]) => [opencode, canonical]));
|
|
21584
|
+
function toOpencodePermissionKey(canonical) {
|
|
21585
|
+
return CANONICAL_TO_OPENCODE_PERMISSION_KEYS[canonical] ?? canonical;
|
|
21586
|
+
}
|
|
21587
|
+
function toCanonicalPermissionKey(opencodeKey) {
|
|
21588
|
+
return OPENCODE_TO_CANONICAL_PERMISSION_KEYS[opencodeKey] ?? opencodeKey;
|
|
21589
|
+
}
|
|
20835
21590
|
function isSharedPermissionCategory(category) {
|
|
20836
21591
|
return category === "*" || CANONICAL_PERMISSION_CATEGORIES.has(category) || category.startsWith("mcp__");
|
|
20837
21592
|
}
|
|
@@ -20896,21 +21651,24 @@ var OpencodePermissions = class OpencodePermissions extends ToolPermissions {
|
|
|
20896
21651
|
fileContent = await readFileContentOrNull(jsonPath);
|
|
20897
21652
|
if (fileContent) relativeFilePath = OPENCODE_JSON_FILE_NAME;
|
|
20898
21653
|
}
|
|
20899
|
-
const parsed = (0, jsonc_parser.parse)(fileContent ?? "{}");
|
|
20900
21654
|
const rulesyncJson = rulesyncPermissions.getJson();
|
|
20901
21655
|
const overridePermission = rulesyncJson.opencode?.permission ?? {};
|
|
20902
|
-
const
|
|
20903
|
-
|
|
20904
|
-
permission: {
|
|
20905
|
-
...rulesyncJson.permission,
|
|
20906
|
-
...overridePermission
|
|
20907
|
-
}
|
|
20908
|
-
};
|
|
21656
|
+
const sharedPermission = {};
|
|
21657
|
+
for (const [category, value] of Object.entries(rulesyncJson.permission ?? {})) sharedPermission[toOpencodePermissionKey(category)] = value;
|
|
20909
21658
|
return new OpencodePermissions({
|
|
20910
21659
|
outputRoot,
|
|
20911
21660
|
relativeDirPath: basePaths.relativeDirPath,
|
|
20912
21661
|
relativeFilePath,
|
|
20913
|
-
fileContent:
|
|
21662
|
+
fileContent: applySharedConfigPatch({
|
|
21663
|
+
fileKey: sharedConfigFileKey(basePaths),
|
|
21664
|
+
feature: "permissions",
|
|
21665
|
+
existingContent: fileContent ?? "",
|
|
21666
|
+
patch: { permission: {
|
|
21667
|
+
...sharedPermission,
|
|
21668
|
+
...overridePermission
|
|
21669
|
+
} },
|
|
21670
|
+
filePath: (0, node_path.join)(jsonDir, relativeFilePath)
|
|
21671
|
+
}),
|
|
20914
21672
|
validate: true
|
|
20915
21673
|
});
|
|
20916
21674
|
}
|
|
@@ -20922,8 +21680,11 @@ var OpencodePermissions = class OpencodePermissions extends ToolPermissions {
|
|
|
20922
21680
|
}
|
|
20923
21681
|
const shared = {};
|
|
20924
21682
|
const overrideOnly = {};
|
|
20925
|
-
for (const [category, value] of Object.entries(rawPermission))
|
|
20926
|
-
|
|
21683
|
+
for (const [category, value] of Object.entries(rawPermission)) {
|
|
21684
|
+
const canonicalCategory = toCanonicalPermissionKey(category);
|
|
21685
|
+
if (isSharedPermissionCategory(canonicalCategory)) shared[canonicalCategory] = typeof value === "string" ? { "*": value } : value;
|
|
21686
|
+
else overrideOnly[category] = value;
|
|
21687
|
+
}
|
|
20927
21688
|
const json = Object.keys(overrideOnly).length > 0 ? {
|
|
20928
21689
|
permission: shared,
|
|
20929
21690
|
opencode: { permission: overrideOnly }
|
|
@@ -21048,6 +21809,7 @@ const QWEN_OVERRIDE_TOOLS_KEYS = [
|
|
|
21048
21809
|
"disabled"
|
|
21049
21810
|
];
|
|
21050
21811
|
const QWEN_OVERRIDE_SECURITY_KEYS = ["folderTrust"];
|
|
21812
|
+
const QWEN_OVERRIDE_PERMISSIONS_KEYS = ["autoMode"];
|
|
21051
21813
|
function asPlainRecord(value) {
|
|
21052
21814
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
21053
21815
|
}
|
|
@@ -21119,20 +21881,24 @@ var QwencodePermissions = class QwencodePermissions extends ToolPermissions {
|
|
|
21119
21881
|
else delete mergedPermissions.ask;
|
|
21120
21882
|
if (mergedDeny.length > 0) mergedPermissions.deny = mergedDeny;
|
|
21121
21883
|
else delete mergedPermissions.deny;
|
|
21122
|
-
const merged = {
|
|
21123
|
-
...settings,
|
|
21124
|
-
permissions: mergedPermissions
|
|
21125
|
-
};
|
|
21126
21884
|
const override = config.qwencode;
|
|
21127
|
-
if (override?.
|
|
21885
|
+
if (override?.autoMode !== void 0) mergedPermissions.autoMode = override.autoMode;
|
|
21886
|
+
const patch = { permissions: mergedPermissions };
|
|
21887
|
+
if (override?.tools !== void 0) patch.tools = {
|
|
21128
21888
|
...asPlainRecord(settings.tools),
|
|
21129
21889
|
...asPlainRecord(override.tools)
|
|
21130
21890
|
};
|
|
21131
|
-
if (override?.security !== void 0)
|
|
21891
|
+
if (override?.security !== void 0) patch.security = {
|
|
21132
21892
|
...asPlainRecord(settings.security),
|
|
21133
21893
|
...asPlainRecord(override.security)
|
|
21134
21894
|
};
|
|
21135
|
-
const fileContent =
|
|
21895
|
+
const fileContent = applySharedConfigPatch({
|
|
21896
|
+
fileKey: sharedConfigFileKey(paths),
|
|
21897
|
+
feature: "permissions",
|
|
21898
|
+
existingContent,
|
|
21899
|
+
patch,
|
|
21900
|
+
filePath
|
|
21901
|
+
});
|
|
21136
21902
|
return new QwencodePermissions({
|
|
21137
21903
|
outputRoot,
|
|
21138
21904
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -21159,9 +21925,11 @@ var QwencodePermissions = class QwencodePermissions extends ToolPermissions {
|
|
|
21159
21925
|
});
|
|
21160
21926
|
const overrideTools = pickQwenOverrideKeys(settings.tools, QWEN_OVERRIDE_TOOLS_KEYS);
|
|
21161
21927
|
const overrideSecurity = pickQwenOverrideKeys(settings.security, QWEN_OVERRIDE_SECURITY_KEYS);
|
|
21928
|
+
const overridePermissions = pickQwenOverrideKeys(settings.permissions, QWEN_OVERRIDE_PERMISSIONS_KEYS);
|
|
21162
21929
|
const qwencodeOverride = {};
|
|
21163
21930
|
if (Object.keys(overrideTools).length > 0) qwencodeOverride.tools = overrideTools;
|
|
21164
21931
|
if (Object.keys(overrideSecurity).length > 0) qwencodeOverride.security = overrideSecurity;
|
|
21932
|
+
if (overridePermissions.autoMode !== void 0) qwencodeOverride.autoMode = overridePermissions.autoMode;
|
|
21165
21933
|
const result = { ...config };
|
|
21166
21934
|
if (Object.keys(qwencodeOverride).length > 0) result.qwencode = qwencodeOverride;
|
|
21167
21935
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
|
|
@@ -21367,7 +22135,9 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
21367
22135
|
}
|
|
21368
22136
|
static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, validate = true, logger, global = false }) {
|
|
21369
22137
|
const paths = this.getSettablePaths({ global });
|
|
21370
|
-
const
|
|
22138
|
+
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
22139
|
+
const existingContent = await readFileContentOrNull(filePath) ?? "";
|
|
22140
|
+
const parsed = parseReasonixConfig(existingContent);
|
|
21371
22141
|
const config = rulesyncPermissions.getJson();
|
|
21372
22142
|
const { allow, ask, deny } = convertRulesyncToReasonixPermissions(config);
|
|
21373
22143
|
const managedToolNames = new Set(Object.keys(config.permission).map((category) => toReasonixToolName(category)));
|
|
@@ -21392,25 +22162,27 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
21392
22162
|
else delete mergedPermissions.ask;
|
|
21393
22163
|
if (mergedDeny.length > 0) mergedPermissions.deny = mergedDeny;
|
|
21394
22164
|
else delete mergedPermissions.deny;
|
|
21395
|
-
const
|
|
21396
|
-
...parsed,
|
|
21397
|
-
permissions: mergedPermissions
|
|
21398
|
-
};
|
|
22165
|
+
const patch = { permissions: mergedPermissions };
|
|
21399
22166
|
const override = config.reasonix;
|
|
21400
|
-
if (override?.sandbox !== void 0)
|
|
22167
|
+
if (override?.sandbox !== void 0) patch.sandbox = {
|
|
21401
22168
|
...asReasonixRecord(parsed.sandbox),
|
|
21402
22169
|
...asReasonixRecord(override.sandbox)
|
|
21403
22170
|
};
|
|
21404
|
-
if (override?.agent !== void 0)
|
|
22171
|
+
if (override?.agent !== void 0) patch.agent = {
|
|
21405
22172
|
...asReasonixRecord(parsed.agent),
|
|
21406
22173
|
...asReasonixRecord(override.agent)
|
|
21407
22174
|
};
|
|
21408
|
-
const fileContent = smol_toml.stringify(merged);
|
|
21409
22175
|
return new ReasonixPermissions({
|
|
21410
22176
|
outputRoot,
|
|
21411
22177
|
relativeDirPath: paths.relativeDirPath,
|
|
21412
22178
|
relativeFilePath: paths.relativeFilePath,
|
|
21413
|
-
fileContent
|
|
22179
|
+
fileContent: applySharedConfigPatch({
|
|
22180
|
+
fileKey: sharedConfigFileKey(paths),
|
|
22181
|
+
feature: "permissions",
|
|
22182
|
+
existingContent,
|
|
22183
|
+
patch,
|
|
22184
|
+
filePath
|
|
22185
|
+
}),
|
|
21414
22186
|
validate
|
|
21415
22187
|
});
|
|
21416
22188
|
}
|
|
@@ -21981,7 +22753,9 @@ var VibePermissions = class VibePermissions extends ToolPermissions {
|
|
|
21981
22753
|
}
|
|
21982
22754
|
static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, validate = true, logger, global = false }) {
|
|
21983
22755
|
const paths = this.getSettablePaths({ global });
|
|
21984
|
-
const
|
|
22756
|
+
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
22757
|
+
const existingContent = await readFileContentOrNull(filePath) ?? "";
|
|
22758
|
+
const config = parseVibeConfig(existingContent);
|
|
21985
22759
|
const permission = rulesyncPermissions.getJson().permission;
|
|
21986
22760
|
const tools = toVibeToolsRecord(config.tools);
|
|
21987
22761
|
const enabledTools = new Set(toStringArray(config.enabled_tools));
|
|
@@ -22026,16 +22800,21 @@ var VibePermissions = class VibePermissions extends ToolPermissions {
|
|
|
22026
22800
|
tools[vibeToolName] = nextTool;
|
|
22027
22801
|
}
|
|
22028
22802
|
applyVibeSensitivePatterns(tools, rulesyncPermissions.getJson().vibe);
|
|
22029
|
-
config.tools = tools;
|
|
22030
|
-
if (enabledTools.size > 0) config.enabled_tools = [...enabledTools].toSorted();
|
|
22031
|
-
else delete config.enabled_tools;
|
|
22032
|
-
if (disabledTools.size > 0) config.disabled_tools = [...disabledTools].toSorted();
|
|
22033
|
-
else delete config.disabled_tools;
|
|
22034
22803
|
return new VibePermissions({
|
|
22035
22804
|
outputRoot,
|
|
22036
22805
|
relativeDirPath: paths.relativeDirPath,
|
|
22037
22806
|
relativeFilePath: paths.relativeFilePath,
|
|
22038
|
-
fileContent:
|
|
22807
|
+
fileContent: applySharedConfigPatch({
|
|
22808
|
+
fileKey: sharedConfigFileKey(paths),
|
|
22809
|
+
feature: "permissions",
|
|
22810
|
+
existingContent,
|
|
22811
|
+
patch: {
|
|
22812
|
+
tools,
|
|
22813
|
+
enabled_tools: enabledTools.size > 0 ? [...enabledTools].toSorted() : void 0,
|
|
22814
|
+
disabled_tools: disabledTools.size > 0 ? [...disabledTools].toSorted() : void 0
|
|
22815
|
+
},
|
|
22816
|
+
filePath
|
|
22817
|
+
}),
|
|
22039
22818
|
validate,
|
|
22040
22819
|
global
|
|
22041
22820
|
});
|
|
@@ -22498,24 +23277,26 @@ var ZedPermissions = class ZedPermissions extends ToolPermissions {
|
|
|
22498
23277
|
}
|
|
22499
23278
|
const managedToolNames = new Set(Object.keys(managedTools));
|
|
22500
23279
|
const preservedTools = Object.fromEntries(Object.entries(existingTools).filter(([toolName]) => !managedToolNames.has(toolName)));
|
|
22501
|
-
const mergedSettings = {
|
|
22502
|
-
...settings,
|
|
22503
|
-
agent: {
|
|
22504
|
-
...agent,
|
|
22505
|
-
tool_permissions: {
|
|
22506
|
-
...toolPermissions,
|
|
22507
|
-
tools: {
|
|
22508
|
-
...preservedTools,
|
|
22509
|
-
...managedTools
|
|
22510
|
-
}
|
|
22511
|
-
}
|
|
22512
|
-
}
|
|
22513
|
-
};
|
|
22514
23280
|
return new ZedPermissions({
|
|
22515
23281
|
outputRoot,
|
|
22516
23282
|
relativeDirPath: paths.relativeDirPath,
|
|
22517
23283
|
relativeFilePath: paths.relativeFilePath,
|
|
22518
|
-
fileContent:
|
|
23284
|
+
fileContent: applySharedConfigPatch({
|
|
23285
|
+
fileKey: sharedConfigFileKey(paths),
|
|
23286
|
+
feature: "permissions",
|
|
23287
|
+
existingContent,
|
|
23288
|
+
patch: { agent: {
|
|
23289
|
+
...agent,
|
|
23290
|
+
tool_permissions: {
|
|
23291
|
+
...toolPermissions,
|
|
23292
|
+
tools: {
|
|
23293
|
+
...preservedTools,
|
|
23294
|
+
...managedTools
|
|
23295
|
+
}
|
|
23296
|
+
}
|
|
23297
|
+
} },
|
|
23298
|
+
filePath
|
|
23299
|
+
}),
|
|
22519
23300
|
validate: true
|
|
22520
23301
|
});
|
|
22521
23302
|
}
|
|
@@ -22657,7 +23438,7 @@ const toolPermissionsFactories = /* @__PURE__ */ new Map([
|
|
|
22657
23438
|
["grokcli", {
|
|
22658
23439
|
class: GrokcliPermissions,
|
|
22659
23440
|
meta: {
|
|
22660
|
-
supportsProject:
|
|
23441
|
+
supportsProject: true,
|
|
22661
23442
|
supportsGlobal: true,
|
|
22662
23443
|
supportsImport: true
|
|
22663
23444
|
}
|
|
@@ -38205,8 +38986,13 @@ const collectFactoryPaths = (factory) => [...settablePathsForScope({
|
|
|
38205
38986
|
* Derive, from the processor registry, every on-disk file that two or more
|
|
38206
38987
|
* features read-modify-write. Source of truth the generation step graph's
|
|
38207
38988
|
* `writesSharedFile` declarations must match.
|
|
38989
|
+
*
|
|
38990
|
+
* `minWriters: 1` widens the result to single-feature files as well — used to
|
|
38991
|
+
* validate `SHARED_CONFIG_OWNERSHIP` declarations for files that route through
|
|
38992
|
+
* the gateway without being cross-feature shared (e.g. a global-scope twin
|
|
38993
|
+
* only one feature writes).
|
|
38208
38994
|
*/
|
|
38209
|
-
const deriveSharedFileWriters = () => {
|
|
38995
|
+
const deriveSharedFileWriters = ({ minWriters = 2 } = {}) => {
|
|
38210
38996
|
const byKey = /* @__PURE__ */ new Map();
|
|
38211
38997
|
const pathByKey = /* @__PURE__ */ new Map();
|
|
38212
38998
|
for (const entry of PROCESSOR_REGISTRY) {
|
|
@@ -38232,7 +39018,7 @@ const deriveSharedFileWriters = () => {
|
|
|
38232
39018
|
}
|
|
38233
39019
|
const writers = [];
|
|
38234
39020
|
for (const [key, features] of byKey) {
|
|
38235
|
-
if (features.size <
|
|
39021
|
+
if (features.size < minWriters) continue;
|
|
38236
39022
|
const path = pathByKey.get(key);
|
|
38237
39023
|
const toolsByFeature = /* @__PURE__ */ new Map();
|
|
38238
39024
|
for (const [feature, tools] of features) toolsByFeature.set(feature, [...tools].toSorted());
|