rulesync 16.7.0 → 16.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/dist/cli/index.cjs +4 -9
- package/dist/cli/index.js +4 -9
- package/dist/cli/index.js.map +1 -1
- package/dist/{import-XhKI9ZRY.cjs → import-DEK1TMmW.cjs} +968 -405
- package/dist/{import-zyfw1Cq_.js → import-KXnvmbzr.js} +968 -405
- package/dist/import-KXnvmbzr.js.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +10 -0
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/import-zyfw1Cq_.js.map +0 -1
|
@@ -1867,7 +1867,7 @@ var RulesyncFile = class extends AiFile {
|
|
|
1867
1867
|
* Type guard to check if a value is a plain object (Record<string, unknown>).
|
|
1868
1868
|
* This excludes arrays and null values.
|
|
1869
1869
|
*/
|
|
1870
|
-
function isRecord(value) {
|
|
1870
|
+
function isRecord$1(value) {
|
|
1871
1871
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1872
1872
|
}
|
|
1873
1873
|
/**
|
|
@@ -1882,7 +1882,7 @@ function isRecord(value) {
|
|
|
1882
1882
|
* malicious accessor descriptors.
|
|
1883
1883
|
*/
|
|
1884
1884
|
function isPlainObject$1(value) {
|
|
1885
|
-
if (!isRecord(value)) return false;
|
|
1885
|
+
if (!isRecord$1(value)) return false;
|
|
1886
1886
|
const proto = Object.getPrototypeOf(value);
|
|
1887
1887
|
return proto === null || proto === Object.prototype;
|
|
1888
1888
|
}
|
|
@@ -2188,6 +2188,7 @@ const HookDefinitionSchema = zod_mini.z.looseObject({
|
|
|
2188
2188
|
timeout: zod_mini.z.optional(zod_mini.z.number()),
|
|
2189
2189
|
cacheTtl: zod_mini.z.optional(zod_mini.z.number().check((0, zod_mini.nonnegative)())),
|
|
2190
2190
|
matcher: zod_mini.z.optional(safeString),
|
|
2191
|
+
enabled: zod_mini.z.optional(zod_mini.z.boolean()),
|
|
2191
2192
|
prompt: zod_mini.z.optional(safeString),
|
|
2192
2193
|
loop_limit: zod_mini.z.optional(zod_mini.z.nullable(zod_mini.z.number())),
|
|
2193
2194
|
name: zod_mini.z.optional(safeString),
|
|
@@ -2506,8 +2507,9 @@ const FACTORYDROID_HOOK_EVENTS = [
|
|
|
2506
2507
|
/**
|
|
2507
2508
|
* Hook events supported by deepagents-cli (`deepagents-code` / `dcode`).
|
|
2508
2509
|
*
|
|
2509
|
-
*
|
|
2510
|
-
*
|
|
2510
|
+
* These are the twelve Hooks v2 `HookEvent` members, GA since deepagents-code
|
|
2511
|
+
* 0.1.52. Canonical `contextOffload` is deliberately absent — see
|
|
2512
|
+
* {@link CANONICAL_TO_DEEPAGENTS_EVENT_NAMES}.
|
|
2511
2513
|
* https://docs.langchain.com/oss/python/deepagents/cli/configuration
|
|
2512
2514
|
*/
|
|
2513
2515
|
const DEEPAGENTS_HOOK_EVENTS = [
|
|
@@ -2520,8 +2522,9 @@ const DEEPAGENTS_HOOK_EVENTS = [
|
|
|
2520
2522
|
"postToolUseFailure",
|
|
2521
2523
|
"stop",
|
|
2522
2524
|
"preCompact",
|
|
2523
|
-
"
|
|
2524
|
-
"
|
|
2525
|
+
"notification",
|
|
2526
|
+
"subagentStart",
|
|
2527
|
+
"subagentStop"
|
|
2525
2528
|
];
|
|
2526
2529
|
/** Hook events supported by Codex CLI. */
|
|
2527
2530
|
const CODEXCLI_HOOK_EVENTS = [
|
|
@@ -3209,26 +3212,59 @@ const CANONICAL_TO_GOOSE_EVENT_NAMES = {
|
|
|
3209
3212
|
*/
|
|
3210
3213
|
const GOOSE_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_GOOSE_EVENT_NAMES).map(([k, v]) => [v, k]));
|
|
3211
3214
|
/**
|
|
3212
|
-
* Map canonical camelCase event names to deepagents-cli
|
|
3215
|
+
* Map canonical camelCase event names to the deepagents-cli Hooks v2
|
|
3216
|
+
* `HookEvent` values.
|
|
3217
|
+
*
|
|
3218
|
+
* Hooks v2 went GA in deepagents-code 0.1.52 (2026-08-04) and replaced the
|
|
3219
|
+
* legacy dot-notation names (`session.start`, `tool.use`, …) with these twelve
|
|
3220
|
+
* PascalCase members. The legacy list format is still read, but is scheduled for
|
|
3221
|
+
* removal on 2026-09-01 (`_LEGACY_HOOKS_REMOVAL_DATE` in `hooks/loading.py`).
|
|
3222
|
+
*
|
|
3223
|
+
* Canonical `contextOffload` has no v2 counterpart. Its legacy event
|
|
3224
|
+
* (`context.offload`) is gone, and folding it onto `PreCompact` would silently
|
|
3225
|
+
* merge two distinct canonical events into one — so it is dropped for
|
|
3226
|
+
* deepagents instead, and reported by the hooks processor as an unsupported
|
|
3227
|
+
* event like any other.
|
|
3228
|
+
*
|
|
3229
|
+
* @see https://github.com/langchain-ai/deepagents `libs/code/deepagents_code/hooks/models/domain.py`
|
|
3213
3230
|
*/
|
|
3214
3231
|
const CANONICAL_TO_DEEPAGENTS_EVENT_NAMES = {
|
|
3215
|
-
sessionStart: "
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
permissionRequest: "
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
preCompact: "
|
|
3224
|
-
|
|
3225
|
-
|
|
3232
|
+
sessionStart: "SessionStart",
|
|
3233
|
+
beforeSubmitPrompt: "UserPromptSubmit",
|
|
3234
|
+
sessionEnd: "SessionEnd",
|
|
3235
|
+
permissionRequest: "PermissionRequest",
|
|
3236
|
+
notification: "Notification",
|
|
3237
|
+
preToolUse: "PreToolUse",
|
|
3238
|
+
postToolUse: "PostToolUse",
|
|
3239
|
+
postToolUseFailure: "PostToolUseFailure",
|
|
3240
|
+
preCompact: "PreCompact",
|
|
3241
|
+
stop: "Stop",
|
|
3242
|
+
subagentStart: "SubagentStart",
|
|
3243
|
+
subagentStop: "SubagentStop"
|
|
3226
3244
|
};
|
|
3227
3245
|
/**
|
|
3228
|
-
* Map deepagents-cli
|
|
3246
|
+
* Map deepagents-cli `HookEvent` values to canonical camelCase.
|
|
3229
3247
|
*/
|
|
3230
3248
|
const DEEPAGENTS_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_DEEPAGENTS_EVENT_NAMES).map(([k, v]) => [v, k]));
|
|
3231
3249
|
/**
|
|
3250
|
+
* The legacy dot-notation event names deepagents-cli used before Hooks v2.
|
|
3251
|
+
* Kept for the read-only import path so a `hooks.json` still in the old format
|
|
3252
|
+
* round-trips into canonical events instead of being silently discarded.
|
|
3253
|
+
*/
|
|
3254
|
+
const DEEPAGENTS_LEGACY_TO_CANONICAL_EVENT_NAMES = {
|
|
3255
|
+
"session.start": "sessionStart",
|
|
3256
|
+
"session.end": "sessionEnd",
|
|
3257
|
+
"user.prompt": "beforeSubmitPrompt",
|
|
3258
|
+
"permission.request": "permissionRequest",
|
|
3259
|
+
"tool.use": "preToolUse",
|
|
3260
|
+
"tool.result": "postToolUse",
|
|
3261
|
+
"tool.error": "postToolUseFailure",
|
|
3262
|
+
"task.complete": "stop",
|
|
3263
|
+
"context.compact": "preCompact",
|
|
3264
|
+
"context.offload": "contextOffload",
|
|
3265
|
+
"input.required": "notification"
|
|
3266
|
+
};
|
|
3267
|
+
/**
|
|
3232
3268
|
* Map canonical camelCase event names to Kiro CLI camelCase.
|
|
3233
3269
|
* Kiro CLI uses its own event naming: agentSpawn, userPromptSubmit, preToolUse,
|
|
3234
3270
|
* postToolUse, stop. Both `sessionEnd` and `stop` canonical events map to
|
|
@@ -3846,7 +3882,7 @@ var RulesyncMcp = class RulesyncMcp extends RulesyncFile {
|
|
|
3846
3882
|
logger
|
|
3847
3883
|
});
|
|
3848
3884
|
for (const ignoredKey of MCP_IGNORED_ALIAS_SOURCE_KEYS) {
|
|
3849
|
-
if (!isRecord(json[ignoredKey])) continue;
|
|
3885
|
+
if (!isRecord$1(json[ignoredKey])) continue;
|
|
3850
3886
|
this.warnOncePerFile(`alias:${ignoredKey}`, `The "${ignoredKey}" block in ${(0, node_path.join)(this.relativeDirPath, this.relativeFilePath)} is ignored. Author it under the "${MCP_BLOCK_KEY_ALIASES[ignoredKey]}" key instead.`, logger);
|
|
3851
3887
|
}
|
|
3852
3888
|
const toolBlockKeys = Object.keys(json).filter((key) => MCP_TOOL_BLOCK_KEYS.has(key));
|
|
@@ -3858,7 +3894,7 @@ var RulesyncMcp = class RulesyncMcp extends RulesyncFile {
|
|
|
3858
3894
|
}));
|
|
3859
3895
|
for (const blockKey of blockKeys) {
|
|
3860
3896
|
const toolBlock = json[blockKey];
|
|
3861
|
-
const toolServers = isRecord(toolBlock) && isRecord(toolBlock.mcpServers) ? toolBlock.mcpServers : void 0;
|
|
3897
|
+
const toolServers = isRecord$1(toolBlock) && isRecord$1(toolBlock.mcpServers) ? toolBlock.mcpServers : void 0;
|
|
3862
3898
|
for (const [serverName, serverConfig] of Object.entries(toolServers ?? {})) {
|
|
3863
3899
|
if (isPrototypePollutionKey(serverName)) continue;
|
|
3864
3900
|
if (serverConfig === null) delete effectiveServers[serverName];
|
|
@@ -4875,9 +4911,9 @@ var RulesyncPermissions = class RulesyncPermissions extends RulesyncFile {
|
|
|
4875
4911
|
if (NATIVE_PERMISSION_OVERRIDE_TARGETS.has(toolTarget)) return this;
|
|
4876
4912
|
const overrideKey = PERMISSION_OVERRIDE_KEY_ALIASES[toolTarget] ?? toolTarget;
|
|
4877
4913
|
const json = this.json;
|
|
4878
|
-
if (overrideKey !== toolTarget && isRecord(json[toolTarget])) logger?.warn(`The "${toolTarget}" block in ${(0, node_path.join)(this.relativeDirPath, this.relativeFilePath)} is ignored. Author it under the "${overrideKey}" key instead (the ${toolTarget} target reads that block).`);
|
|
4914
|
+
if (overrideKey !== toolTarget && isRecord$1(json[toolTarget])) logger?.warn(`The "${toolTarget}" block in ${(0, node_path.join)(this.relativeDirPath, this.relativeFilePath)} is ignored. Author it under the "${overrideKey}" key instead (the ${toolTarget} target reads that block).`);
|
|
4879
4915
|
const overrideBlock = json[overrideKey];
|
|
4880
|
-
if (!isRecord(overrideBlock) || !isRecord(overrideBlock.permission)) return this;
|
|
4916
|
+
if (!isRecord$1(overrideBlock) || !isRecord$1(overrideBlock.permission)) return this;
|
|
4881
4917
|
const { permission: toolScopedPermission, ...restOverride } = overrideBlock;
|
|
4882
4918
|
const merged = {
|
|
4883
4919
|
...json,
|
|
@@ -5187,6 +5223,11 @@ const RulesyncSkillFrontmatterSchema = zod_mini.z.looseObject({
|
|
|
5187
5223
|
compatibility: zod_mini.z.optional(zod_mini.z.looseObject({})),
|
|
5188
5224
|
metadata: zod_mini.z.optional(zod_mini.z.looseObject({}))
|
|
5189
5225
|
})),
|
|
5226
|
+
kiro: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
5227
|
+
license: zod_mini.z.optional(zod_mini.z.string()),
|
|
5228
|
+
compatibility: zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.looseObject({})])),
|
|
5229
|
+
metadata: zod_mini.z.optional(zod_mini.z.looseObject({}))
|
|
5230
|
+
})),
|
|
5190
5231
|
deepagents: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
5191
5232
|
"allowed-tools": zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
5192
5233
|
license: zod_mini.z.optional(zod_mini.z.string()),
|
|
@@ -8281,7 +8322,7 @@ var AntigravitySharedCommand = class extends ToolCommand {
|
|
|
8281
8322
|
}
|
|
8282
8323
|
static extractAntigravityConfig(rulesyncCommand) {
|
|
8283
8324
|
const antigravity = rulesyncCommand.getFrontmatter().antigravity;
|
|
8284
|
-
return isRecord(antigravity) ? antigravity : void 0;
|
|
8325
|
+
return isRecord$1(antigravity) ? antigravity : void 0;
|
|
8285
8326
|
}
|
|
8286
8327
|
static resolveTrigger(rulesyncCommand, antigravityConfig) {
|
|
8287
8328
|
const rulesyncFrontmatter = rulesyncCommand.getFrontmatter();
|
|
@@ -12071,8 +12112,8 @@ async function lookupPromptDescription({ outputRoot, relativeFilePath, name }) {
|
|
|
12071
12112
|
}
|
|
12072
12113
|
if (!isPlainObject$1(parsed) || !Array.isArray(parsed.prompts)) return "";
|
|
12073
12114
|
const expectedContentFile = toPosixPath((0, node_path.join)("prompts", relativeFilePath));
|
|
12074
|
-
const entry = parsed.prompts.find((candidate) => isRecord(candidate) && (candidate.content_file === expectedContentFile || candidate.name === name));
|
|
12075
|
-
return isRecord(entry) && typeof entry.description === "string" ? entry.description : "";
|
|
12115
|
+
const entry = parsed.prompts.find((candidate) => isRecord$1(candidate) && (candidate.content_file === expectedContentFile || candidate.name === name));
|
|
12116
|
+
return isRecord$1(entry) && typeof entry.description === "string" ? entry.description : "";
|
|
12076
12117
|
}
|
|
12077
12118
|
/**
|
|
12078
12119
|
* The shared `.rovodev/prompts.yml` manifest that indexes every saved prompt.
|
|
@@ -13185,97 +13226,140 @@ function applyCommandPrefix({ def, converterConfig }) {
|
|
|
13185
13226
|
return `"${converterConfig.projectDirVar}"/${relativeCommand}`;
|
|
13186
13227
|
}
|
|
13187
13228
|
/**
|
|
13188
|
-
*
|
|
13189
|
-
*
|
|
13190
|
-
*
|
|
13191
|
-
*/
|
|
13192
|
-
function emitBooleanPassthroughFields({ def, hookType, converterConfig }) {
|
|
13193
|
-
return Object.fromEntries((converterConfig.booleanPassthroughFields ?? []).filter(({ canonical, commandOnly }) => {
|
|
13194
|
-
if (commandOnly === true && hookType !== "command") return false;
|
|
13195
|
-
return typeof def[canonical] === "boolean";
|
|
13196
|
-
}).map(({ canonical, tool }) => [tool, def[canonical]]));
|
|
13197
|
-
}
|
|
13198
|
-
/**
|
|
13199
|
-
* Import the configured boolean passthrough fields back into canonical fields,
|
|
13200
|
-
* reversing {@link emitBooleanPassthroughFields}. Only boolean values are read.
|
|
13201
|
-
*/
|
|
13202
|
-
function importBooleanPassthroughFields({ h, converterConfig }) {
|
|
13203
|
-
return Object.fromEntries((converterConfig.booleanPassthroughFields ?? []).filter(({ tool }) => typeof h[tool] === "boolean").map(({ canonical, tool }) => [canonical, h[tool]]));
|
|
13204
|
-
}
|
|
13205
|
-
/**
|
|
13206
|
-
* Emit the configured number passthrough fields on the tool side, mapping each
|
|
13207
|
-
* canonical field name to its (possibly renamed) tool field name. Only finite
|
|
13208
|
-
* numbers are carried through.
|
|
13209
|
-
*/
|
|
13210
|
-
function emitNumberPassthroughFields({ def, hookType, converterConfig }) {
|
|
13211
|
-
return Object.fromEntries((converterConfig.numberPassthroughFields ?? []).filter(({ canonical, commandOnly }) => {
|
|
13212
|
-
if (commandOnly === true && hookType !== "command") return false;
|
|
13213
|
-
return Number.isFinite(def[canonical]);
|
|
13214
|
-
}).map(({ canonical, tool }) => [tool, def[canonical]]));
|
|
13215
|
-
}
|
|
13216
|
-
/**
|
|
13217
|
-
* Import the configured number passthrough fields back into canonical fields,
|
|
13218
|
-
* reversing {@link emitNumberPassthroughFields}. Only finite numbers are read.
|
|
13219
|
-
*/
|
|
13220
|
-
function importNumberPassthroughFields({ h, converterConfig }) {
|
|
13221
|
-
return Object.fromEntries((converterConfig.numberPassthroughFields ?? []).filter(({ tool }) => Number.isFinite(h[tool])).map(({ canonical, tool }) => [canonical, h[tool]]));
|
|
13222
|
-
}
|
|
13223
|
-
/**
|
|
13224
|
-
* Emit the configured string passthrough fields on the tool side, mapping each
|
|
13225
|
-
* canonical field name to its (possibly renamed) tool field name. Only non-empty
|
|
13226
|
-
* string values are carried through.
|
|
13227
|
-
*/
|
|
13228
|
-
function emitStringPassthroughFields({ def, hookType, converterConfig }) {
|
|
13229
|
-
return Object.fromEntries((converterConfig.stringPassthroughFields ?? []).filter(({ canonical, commandOnly }) => {
|
|
13230
|
-
if (commandOnly === true && hookType !== "command") return false;
|
|
13231
|
-
return typeof def[canonical] === "string" && def[canonical] !== "";
|
|
13232
|
-
}).map(({ canonical, tool }) => [tool, def[canonical]]));
|
|
13233
|
-
}
|
|
13234
|
-
/**
|
|
13235
|
-
* Import the configured string passthrough fields back into canonical fields,
|
|
13236
|
-
* reversing {@link emitStringPassthroughFields}. Only non-empty string values
|
|
13237
|
-
* are read.
|
|
13238
|
-
*/
|
|
13239
|
-
function importStringPassthroughFields({ h, converterConfig }) {
|
|
13240
|
-
return Object.fromEntries((converterConfig.stringPassthroughFields ?? []).filter(({ tool }) => typeof h[tool] === "string" && h[tool] !== "").map(({ canonical, tool }) => [canonical, h[tool]]));
|
|
13241
|
-
}
|
|
13242
|
-
/**
|
|
13243
|
-
* Emit the configured string-array passthrough fields on the tool side.
|
|
13244
|
-
*/
|
|
13245
|
-
function emitArrayPassthroughFields({ def, hookType, converterConfig }) {
|
|
13246
|
-
return Object.fromEntries((converterConfig.arrayPassthroughFields ?? []).filter(({ canonical, commandOnly }) => {
|
|
13247
|
-
if (commandOnly === true && hookType !== "command") return false;
|
|
13248
|
-
return isStringArray(def[canonical]);
|
|
13249
|
-
}).map(({ canonical, tool }) => [tool, def[canonical]]));
|
|
13250
|
-
}
|
|
13251
|
-
/**
|
|
13252
|
-
* Import the configured string-array passthrough fields, reversing
|
|
13253
|
-
* {@link emitArrayPassthroughFields}.
|
|
13229
|
+
* Whether a field registered for `command` hooks only applies to this hook.
|
|
13230
|
+
* Applied on both export and import: a value imported into a canonical field
|
|
13231
|
+
* the exporter would then drop is silently deleted on the next generate.
|
|
13254
13232
|
*/
|
|
13255
|
-
function
|
|
13256
|
-
|
|
13257
|
-
for (const { tool } of fields) if (h[tool] !== void 0 && !isSafeStringArray(h[tool])) logger?.warn(`Dropping "${tool}" while importing a hook: it must be a list of strings without newline, carriage return or NUL characters.`);
|
|
13258
|
-
return Object.fromEntries(fields.filter(({ tool }) => isSafeStringArray(h[tool])).map(({ canonical, tool }) => [canonical, h[tool]]));
|
|
13233
|
+
function isFieldApplicable({ commandOnly, hookType }) {
|
|
13234
|
+
return commandOnly !== true || hookType === "command";
|
|
13259
13235
|
}
|
|
13260
13236
|
/**
|
|
13261
|
-
* Emit the configured
|
|
13262
|
-
*
|
|
13263
|
-
|
|
13264
|
-
|
|
13265
|
-
|
|
13266
|
-
|
|
13267
|
-
|
|
13268
|
-
|
|
13269
|
-
|
|
13270
|
-
|
|
13271
|
-
* Import the configured string-map passthrough fields, reversing
|
|
13272
|
-
* {@link emitRecordPassthroughFields}.
|
|
13237
|
+
* Emit the configured passthrough fields on the tool side, mapping each
|
|
13238
|
+
* canonical field name to its (possibly renamed) tool field name. Only values
|
|
13239
|
+
* accepted by `isValid` are carried through, so a malformed field can't leak
|
|
13240
|
+
* into a config the tool would reject.
|
|
13241
|
+
*
|
|
13242
|
+
* A field the tool documents on `command` hooks only is dropped when authored
|
|
13243
|
+
* on another hook type. That is a value the user hand-wrote in
|
|
13244
|
+
* `.rulesync/hooks.*` and the canonical schema does not cross-validate it
|
|
13245
|
+
* against the hook's type, so it is warned about rather than deleted in
|
|
13246
|
+
* silence — the mirror of the import side.
|
|
13273
13247
|
*/
|
|
13274
|
-
function
|
|
13275
|
-
|
|
13276
|
-
|
|
13277
|
-
|
|
13278
|
-
|
|
13248
|
+
function emitPassthroughFields({ def, hookType, eventName, fields, isValid, warn }) {
|
|
13249
|
+
for (const { canonical, tool, commandOnly } of fields) {
|
|
13250
|
+
const value = def[canonical];
|
|
13251
|
+
if (value === void 0) continue;
|
|
13252
|
+
if (!isFieldApplicable({
|
|
13253
|
+
commandOnly,
|
|
13254
|
+
hookType
|
|
13255
|
+
})) {
|
|
13256
|
+
warn?.(`Dropping "${canonical}" from a "${hookType}" hook on "${eventName}": this tool documents "${tool}" on "command" hooks only, so it is not generated.`);
|
|
13257
|
+
continue;
|
|
13258
|
+
}
|
|
13259
|
+
if (!isValid({
|
|
13260
|
+
value,
|
|
13261
|
+
canonical
|
|
13262
|
+
})) warn?.(`Dropping "${canonical}" from a "${hookType}" hook on "${eventName}": ${JSON.stringify(value)} is not a value this tool can express as "${tool}".`);
|
|
13263
|
+
}
|
|
13264
|
+
return Object.fromEntries(fields.filter(({ canonical, commandOnly }) => isFieldApplicable({
|
|
13265
|
+
commandOnly,
|
|
13266
|
+
hookType
|
|
13267
|
+
}) && isValid({
|
|
13268
|
+
value: def[canonical],
|
|
13269
|
+
canonical
|
|
13270
|
+
})).map(({ canonical, tool }) => [tool, def[canonical]]));
|
|
13271
|
+
}
|
|
13272
|
+
/**
|
|
13273
|
+
* Import the configured passthrough fields back into canonical fields,
|
|
13274
|
+
* reversing {@link emitPassthroughFields}. A field the tool documents on
|
|
13275
|
+
* `command` hooks only is skipped here too, and `describeInvalid` — when the
|
|
13276
|
+
* kind has a rule an authored file can plausibly violate — turns a rejected
|
|
13277
|
+
* value into a warning instead of a silent drop.
|
|
13278
|
+
*/
|
|
13279
|
+
function importPassthroughFields({ h, hookType, fields, isValid, describeInvalid, warn }) {
|
|
13280
|
+
const applicable = fields.filter(({ commandOnly }) => isFieldApplicable({
|
|
13281
|
+
commandOnly,
|
|
13282
|
+
hookType
|
|
13283
|
+
}));
|
|
13284
|
+
const skipped = fields.filter(({ commandOnly }) => !isFieldApplicable({
|
|
13285
|
+
commandOnly,
|
|
13286
|
+
hookType
|
|
13287
|
+
}));
|
|
13288
|
+
for (const { tool } of skipped) if (h[tool] !== void 0) warn?.(`Dropping "${tool}" from an imported "${hookType}" hook: this tool documents it on "command" hooks only, so it is not imported.`);
|
|
13289
|
+
for (const { tool, canonical } of applicable) if (describeInvalid !== void 0 && h[tool] !== void 0 && !isValid({
|
|
13290
|
+
value: h[tool],
|
|
13291
|
+
canonical
|
|
13292
|
+
})) warn?.(describeInvalid({
|
|
13293
|
+
tool,
|
|
13294
|
+
canonical,
|
|
13295
|
+
value: h[tool]
|
|
13296
|
+
}));
|
|
13297
|
+
return Object.fromEntries(applicable.filter(({ tool, canonical }) => isValid({
|
|
13298
|
+
value: h[tool],
|
|
13299
|
+
canonical
|
|
13300
|
+
})).map(({ canonical, tool }) => [canonical, h[tool]]));
|
|
13301
|
+
}
|
|
13302
|
+
const isBooleanValue = ({ value }) => typeof value === "boolean";
|
|
13303
|
+
const isFiniteNumber = ({ value }) => Number.isFinite(value);
|
|
13304
|
+
const isNonEmptyString = (value) => typeof value === "string" && value !== "";
|
|
13305
|
+
const isEmittableString = ({ value }) => isNonEmptyString(value);
|
|
13306
|
+
const isEmittableArray = ({ value }) => isStringArray(value);
|
|
13307
|
+
const isEmittableRecord = ({ value }) => isSafeStringRecord(value);
|
|
13308
|
+
const isImportableArray = ({ value }) => isSafeStringArray(value);
|
|
13309
|
+
/**
|
|
13310
|
+
* The canonical schema of one hook field, looked up by name. Read off the
|
|
13311
|
+
* schema's own shape rather than by parsing a one-field object, so a `canonical`
|
|
13312
|
+
* name that no longer exists resolves to `undefined` (a `looseObject` would
|
|
13313
|
+
* accept an unknown key and silently validate nothing) and a typo is caught by
|
|
13314
|
+
* the tests instead of quietly disabling the check.
|
|
13315
|
+
*/
|
|
13316
|
+
const CANONICAL_FIELD_SCHEMAS = HookDefinitionSchema.def.shape;
|
|
13317
|
+
/**
|
|
13318
|
+
* Whether a value satisfies the constraints the canonical schema puts on the
|
|
13319
|
+
* field it would be imported into.
|
|
13320
|
+
*
|
|
13321
|
+
* Import needs this on top of the kind's shape check: a hand-written tool
|
|
13322
|
+
* settings file can hold `"shell": "zsh"`, which is a non-empty string but not
|
|
13323
|
+
* a member of the canonical `z.enum(["bash", "powershell"])`. Letting it in
|
|
13324
|
+
* would write a `.rulesync/hooks.jsonc` that fails validation on the *next*
|
|
13325
|
+
* run, taking the whole hooks feature down with it.
|
|
13326
|
+
*/
|
|
13327
|
+
function satisfiesCanonicalField({ value, canonical }) {
|
|
13328
|
+
const schema = canonicalFieldSchema(canonical);
|
|
13329
|
+
return schema !== void 0 && zod_mini.z.safeParse(schema, value).success;
|
|
13330
|
+
}
|
|
13331
|
+
/** Own properties only, so a name like `toString` resolves to nothing. */
|
|
13332
|
+
function canonicalFieldSchema(canonical) {
|
|
13333
|
+
return Object.hasOwn(CANONICAL_FIELD_SCHEMAS, canonical) ? CANONICAL_FIELD_SCHEMAS[canonical] : void 0;
|
|
13334
|
+
}
|
|
13335
|
+
const isImportableString = ({ value, canonical }) => isNonEmptyString(value) && satisfiesCanonicalField({
|
|
13336
|
+
value,
|
|
13337
|
+
canonical
|
|
13338
|
+
});
|
|
13339
|
+
const isImportableNumber = ({ value, canonical }) => Number.isFinite(value) && satisfiesCanonicalField({
|
|
13340
|
+
value,
|
|
13341
|
+
canonical
|
|
13342
|
+
});
|
|
13343
|
+
/**
|
|
13344
|
+
* Say which rule the value broke, so the warning names the actual constraint
|
|
13345
|
+
* rather than asserting a canonical rejection that may not be the reason. A
|
|
13346
|
+
* closed enum lists its members; a rule carrying its own message (the
|
|
13347
|
+
* control-character check behind `safeString`) reuses it.
|
|
13348
|
+
*/
|
|
13349
|
+
function describeScalarConstraint({ canonical, value }) {
|
|
13350
|
+
const schema = canonicalFieldSchema(canonical);
|
|
13351
|
+
const result = schema === void 0 ? void 0 : zod_mini.z.safeParse(schema, value);
|
|
13352
|
+
if (result === void 0 || result.success) return `it is not a value this field carries through.`;
|
|
13353
|
+
const issue = result.error.issues[0];
|
|
13354
|
+
if (issue === void 0) return `it is not a value the canonical "${canonical}" field accepts.`;
|
|
13355
|
+
return `it does not satisfy the canonical "${canonical}" field: ${issue.message}.`;
|
|
13356
|
+
}
|
|
13357
|
+
const describeInvalidScalar = ({ tool, canonical, value }) => `Dropping "${tool}" (${JSON.stringify(value)}) while importing a hook: ${describeScalarConstraint({
|
|
13358
|
+
canonical,
|
|
13359
|
+
value
|
|
13360
|
+
})} Importing it would fail validation on the next run.`;
|
|
13361
|
+
const describeInvalidArray = ({ tool }) => `Dropping "${tool}" while importing a hook: it must be a list of strings without newline, carriage return or NUL characters.`;
|
|
13362
|
+
const describeInvalidRecord = ({ tool }) => `Dropping "${tool}" while importing a hook: it must be a map of strings whose keys are non-empty and free of "=", and whose keys and values carry no newline, carriage return or NUL characters.`;
|
|
13279
13363
|
/**
|
|
13280
13364
|
* Check a value against the shape its field documents. A string field also
|
|
13281
13365
|
* rejects control characters, matching the canonical `safeString` so an
|
|
@@ -13336,7 +13420,54 @@ function emitTypePayloadFields({ def, hookType, converterConfig }) {
|
|
|
13336
13420
|
function isSupportedHookType({ type, converterConfig }) {
|
|
13337
13421
|
return converterConfig.supportedHookTypes?.has(type ?? "command") ?? true;
|
|
13338
13422
|
}
|
|
13339
|
-
|
|
13423
|
+
/**
|
|
13424
|
+
* Emit every per-hook passthrough kind for one canonical definition.
|
|
13425
|
+
*/
|
|
13426
|
+
function emitAllPassthroughFields({ def, hookType, eventName, converterConfig, warn }) {
|
|
13427
|
+
return {
|
|
13428
|
+
...emitPassthroughFields({
|
|
13429
|
+
def,
|
|
13430
|
+
hookType,
|
|
13431
|
+
eventName,
|
|
13432
|
+
fields: converterConfig.booleanPassthroughFields ?? [],
|
|
13433
|
+
isValid: isBooleanValue,
|
|
13434
|
+
warn
|
|
13435
|
+
}),
|
|
13436
|
+
...emitPassthroughFields({
|
|
13437
|
+
def,
|
|
13438
|
+
hookType,
|
|
13439
|
+
eventName,
|
|
13440
|
+
fields: converterConfig.numberPassthroughFields ?? [],
|
|
13441
|
+
isValid: isFiniteNumber,
|
|
13442
|
+
warn
|
|
13443
|
+
}),
|
|
13444
|
+
...emitPassthroughFields({
|
|
13445
|
+
def,
|
|
13446
|
+
hookType,
|
|
13447
|
+
eventName,
|
|
13448
|
+
fields: converterConfig.stringPassthroughFields ?? [],
|
|
13449
|
+
isValid: isEmittableString,
|
|
13450
|
+
warn
|
|
13451
|
+
}),
|
|
13452
|
+
...emitPassthroughFields({
|
|
13453
|
+
def,
|
|
13454
|
+
hookType,
|
|
13455
|
+
eventName,
|
|
13456
|
+
fields: converterConfig.arrayPassthroughFields ?? [],
|
|
13457
|
+
isValid: isEmittableArray,
|
|
13458
|
+
warn
|
|
13459
|
+
}),
|
|
13460
|
+
...emitPassthroughFields({
|
|
13461
|
+
def,
|
|
13462
|
+
hookType,
|
|
13463
|
+
eventName,
|
|
13464
|
+
fields: converterConfig.recordPassthroughFields ?? [],
|
|
13465
|
+
isValid: isEmittableRecord,
|
|
13466
|
+
warn
|
|
13467
|
+
})
|
|
13468
|
+
};
|
|
13469
|
+
}
|
|
13470
|
+
function buildToolHooks({ defs, eventName, converterConfig, warn }) {
|
|
13340
13471
|
const hooks = [];
|
|
13341
13472
|
for (const def of defs) {
|
|
13342
13473
|
const hookType = def.type ?? "command";
|
|
@@ -13349,30 +13480,12 @@ function buildToolHooks({ defs, converterConfig }) {
|
|
|
13349
13480
|
converterConfig
|
|
13350
13481
|
});
|
|
13351
13482
|
hooks.push({
|
|
13352
|
-
...
|
|
13353
|
-
def,
|
|
13354
|
-
hookType,
|
|
13355
|
-
converterConfig
|
|
13356
|
-
}),
|
|
13357
|
-
...emitNumberPassthroughFields({
|
|
13358
|
-
def,
|
|
13359
|
-
hookType,
|
|
13360
|
-
converterConfig
|
|
13361
|
-
}),
|
|
13362
|
-
...emitStringPassthroughFields({
|
|
13483
|
+
...emitAllPassthroughFields({
|
|
13363
13484
|
def,
|
|
13364
13485
|
hookType,
|
|
13365
|
-
|
|
13366
|
-
|
|
13367
|
-
|
|
13368
|
-
def,
|
|
13369
|
-
hookType,
|
|
13370
|
-
converterConfig
|
|
13371
|
-
}),
|
|
13372
|
-
...emitRecordPassthroughFields({
|
|
13373
|
-
def,
|
|
13374
|
-
hookType,
|
|
13375
|
-
converterConfig
|
|
13486
|
+
eventName,
|
|
13487
|
+
converterConfig,
|
|
13488
|
+
warn
|
|
13376
13489
|
}),
|
|
13377
13490
|
type: hookType,
|
|
13378
13491
|
...command !== void 0 && command !== null && { command },
|
|
@@ -13390,6 +13503,17 @@ function buildToolHooks({ defs, converterConfig }) {
|
|
|
13390
13503
|
return hooks;
|
|
13391
13504
|
}
|
|
13392
13505
|
/**
|
|
13506
|
+
* A `warn` that says each distinct thing once per conversion.
|
|
13507
|
+
*/
|
|
13508
|
+
function warnOnce(logger) {
|
|
13509
|
+
const seen = /* @__PURE__ */ new Set();
|
|
13510
|
+
return (message) => {
|
|
13511
|
+
if (seen.has(message)) return;
|
|
13512
|
+
seen.add(message);
|
|
13513
|
+
logger?.warn(message);
|
|
13514
|
+
};
|
|
13515
|
+
}
|
|
13516
|
+
/**
|
|
13393
13517
|
* Convert canonical hooks config to tool-specific format (shared by Claude and Factory Droid).
|
|
13394
13518
|
* Uses explicit event name mapping tables rather than algorithmic case conversion,
|
|
13395
13519
|
* since tool event names may differ entirely from canonical names
|
|
@@ -13401,6 +13525,7 @@ function canonicalToToolHooks({ config, toolOverrideHooks, converterConfig, logg
|
|
|
13401
13525
|
toolOverrideHooks,
|
|
13402
13526
|
supportedEvents: converterConfig.supportedEvents
|
|
13403
13527
|
});
|
|
13528
|
+
const warn = warnOnce(logger);
|
|
13404
13529
|
const result = {};
|
|
13405
13530
|
for (const [eventName, definitions] of Object.entries(effectiveHooks)) {
|
|
13406
13531
|
const toolEventName = converterConfig.canonicalToToolEventNames[eventName] ?? eventName;
|
|
@@ -13414,7 +13539,9 @@ function canonicalToToolHooks({ config, toolOverrideHooks, converterConfig, logg
|
|
|
13414
13539
|
if (isNoMatcherEvent && matcherKey) logger?.warn(`matcher "${matcherKey}" on "${eventName}" hook will be ignored — this event does not support matchers`);
|
|
13415
13540
|
const hooks = buildToolHooks({
|
|
13416
13541
|
defs,
|
|
13417
|
-
|
|
13542
|
+
eventName,
|
|
13543
|
+
converterConfig,
|
|
13544
|
+
warn
|
|
13418
13545
|
});
|
|
13419
13546
|
if (hooks.length === 0) continue;
|
|
13420
13547
|
const includeMatcher = matcherKey && !isNoMatcherEvent;
|
|
@@ -13520,85 +13647,283 @@ function isSafeStringArray(value) {
|
|
|
13520
13647
|
return isStringArray(value) && value.every((entry) => !CONTROL_CHARS.some((char) => entry.includes(char)));
|
|
13521
13648
|
}
|
|
13522
13649
|
/**
|
|
13650
|
+
* A raw string kept only if the canonical field it would land in accepts it,
|
|
13651
|
+
* warning when it does not. The canonical string fields are guarded by
|
|
13652
|
+
* `safeString`, so an existing tool config carrying a control character would
|
|
13653
|
+
* otherwise be imported into a file the next generate refuses to read.
|
|
13654
|
+
*/
|
|
13655
|
+
function importCanonicalString({ value, canonical, warn }) {
|
|
13656
|
+
if (typeof value !== "string") return;
|
|
13657
|
+
if (satisfiesCanonicalField({
|
|
13658
|
+
value,
|
|
13659
|
+
canonical
|
|
13660
|
+
})) return value;
|
|
13661
|
+
warn?.(describeInvalidScalar({
|
|
13662
|
+
tool: canonical,
|
|
13663
|
+
canonical,
|
|
13664
|
+
value
|
|
13665
|
+
}));
|
|
13666
|
+
}
|
|
13667
|
+
/**
|
|
13523
13668
|
* Import the payload fields specific to a hook type, type-checking each raw
|
|
13524
13669
|
* value before it enters the canonical definition.
|
|
13525
13670
|
*/
|
|
13526
|
-
function importTypePayloadFields({ h, hookType }) {
|
|
13527
|
-
if (hookType === "http")
|
|
13528
|
-
|
|
13529
|
-
|
|
13530
|
-
|
|
13531
|
-
|
|
13532
|
-
|
|
13533
|
-
|
|
13534
|
-
|
|
13535
|
-
|
|
13536
|
-
|
|
13537
|
-
|
|
13671
|
+
function importTypePayloadFields({ h, hookType, warn }) {
|
|
13672
|
+
if (hookType === "http") {
|
|
13673
|
+
const url = importCanonicalString({
|
|
13674
|
+
value: h.url,
|
|
13675
|
+
canonical: "url",
|
|
13676
|
+
warn
|
|
13677
|
+
});
|
|
13678
|
+
const headers = isStringRecord(h.headers) && !satisfiesCanonicalField({
|
|
13679
|
+
value: h.headers,
|
|
13680
|
+
canonical: "headers"
|
|
13681
|
+
}) ? (warn?.(describeInvalidScalar({
|
|
13682
|
+
tool: "headers",
|
|
13683
|
+
canonical: "headers",
|
|
13684
|
+
value: h.headers
|
|
13685
|
+
})), void 0) : h.headers;
|
|
13686
|
+
return {
|
|
13687
|
+
...url !== void 0 && { url },
|
|
13688
|
+
...isStringRecord(headers) && { headers },
|
|
13689
|
+
...isStringArray(h.allowedEnvVars) && { allowedEnvVars: h.allowedEnvVars }
|
|
13690
|
+
};
|
|
13691
|
+
}
|
|
13692
|
+
if (hookType === "mcp_tool") {
|
|
13693
|
+
const server = importCanonicalString({
|
|
13694
|
+
value: h.server,
|
|
13695
|
+
canonical: "server",
|
|
13696
|
+
warn
|
|
13697
|
+
});
|
|
13698
|
+
const tool = importCanonicalString({
|
|
13699
|
+
value: h.tool,
|
|
13700
|
+
canonical: "tool",
|
|
13701
|
+
warn
|
|
13702
|
+
});
|
|
13703
|
+
return {
|
|
13704
|
+
...server !== void 0 && { server },
|
|
13705
|
+
...tool !== void 0 && { tool },
|
|
13706
|
+
...h.input !== null && typeof h.input === "object" && !Array.isArray(h.input) && { input: h.input }
|
|
13707
|
+
};
|
|
13708
|
+
}
|
|
13709
|
+
if (hookType === "prompt" || hookType === "agent") {
|
|
13710
|
+
const model = importCanonicalString({
|
|
13711
|
+
value: h.model,
|
|
13712
|
+
canonical: "model",
|
|
13713
|
+
warn
|
|
13714
|
+
});
|
|
13715
|
+
return model !== void 0 ? { model } : {};
|
|
13716
|
+
}
|
|
13538
13717
|
return {};
|
|
13539
13718
|
}
|
|
13540
13719
|
/**
|
|
13541
|
-
*
|
|
13720
|
+
* Import every per-hook passthrough kind for one tool hook record, reversing
|
|
13721
|
+
* the emit side in {@link buildToolHooks}.
|
|
13542
13722
|
*/
|
|
13543
|
-
function
|
|
13544
|
-
const command = stripCommandPrefix({
|
|
13545
|
-
command: h.command,
|
|
13546
|
-
converterConfig
|
|
13547
|
-
});
|
|
13548
|
-
const hookType = isImportedHookType(h.type) ? h.type : "command";
|
|
13549
|
-
const timeout = typeof h.timeout === "number" ? h.timeout : void 0;
|
|
13550
|
-
const prompt = typeof h.prompt === "string" ? h.prompt : void 0;
|
|
13723
|
+
function importAllPassthroughFields({ h, hookType, converterConfig, warn }) {
|
|
13551
13724
|
return {
|
|
13552
|
-
|
|
13553
|
-
...command !== void 0 && command !== null && { command },
|
|
13554
|
-
...timeout !== void 0 && timeout !== null && { timeout },
|
|
13555
|
-
...prompt !== void 0 && prompt !== null && { prompt },
|
|
13556
|
-
...importTypePayloadFields({
|
|
13725
|
+
...importPassthroughFields({
|
|
13557
13726
|
h,
|
|
13558
|
-
hookType
|
|
13727
|
+
hookType,
|
|
13728
|
+
fields: converterConfig.booleanPassthroughFields ?? [],
|
|
13729
|
+
isValid: isBooleanValue,
|
|
13730
|
+
warn
|
|
13559
13731
|
}),
|
|
13560
|
-
...
|
|
13561
|
-
...converterConfig.passthroughFields?.includes("description") && typeof h.description === "string" && { description: h.description },
|
|
13562
|
-
...importBooleanPassthroughFields({
|
|
13732
|
+
...importPassthroughFields({
|
|
13563
13733
|
h,
|
|
13564
|
-
|
|
13734
|
+
hookType,
|
|
13735
|
+
fields: converterConfig.numberPassthroughFields ?? [],
|
|
13736
|
+
isValid: isImportableNumber,
|
|
13737
|
+
describeInvalid: describeInvalidScalar,
|
|
13738
|
+
warn
|
|
13565
13739
|
}),
|
|
13566
|
-
...
|
|
13740
|
+
...importPassthroughFields({
|
|
13567
13741
|
h,
|
|
13568
|
-
|
|
13742
|
+
hookType,
|
|
13743
|
+
fields: converterConfig.stringPassthroughFields ?? [],
|
|
13744
|
+
isValid: isImportableString,
|
|
13745
|
+
describeInvalid: describeInvalidScalar,
|
|
13746
|
+
warn
|
|
13747
|
+
}),
|
|
13748
|
+
...importPassthroughFields({
|
|
13749
|
+
h,
|
|
13750
|
+
hookType,
|
|
13751
|
+
fields: converterConfig.arrayPassthroughFields ?? [],
|
|
13752
|
+
isValid: isImportableArray,
|
|
13753
|
+
describeInvalid: describeInvalidArray,
|
|
13754
|
+
warn
|
|
13569
13755
|
}),
|
|
13570
|
-
...
|
|
13756
|
+
...importPassthroughFields({
|
|
13571
13757
|
h,
|
|
13758
|
+
hookType,
|
|
13759
|
+
fields: converterConfig.recordPassthroughFields ?? [],
|
|
13760
|
+
isValid: isEmittableRecord,
|
|
13761
|
+
describeInvalid: describeInvalidRecord,
|
|
13762
|
+
warn
|
|
13763
|
+
})
|
|
13764
|
+
};
|
|
13765
|
+
}
|
|
13766
|
+
/**
|
|
13767
|
+
* Convert a single tool hook record into a canonical hook definition.
|
|
13768
|
+
*/
|
|
13769
|
+
function toolHookToCanonical({ h, rawEntry, converterConfig, warn }) {
|
|
13770
|
+
const hookType = isImportedHookType(h.type) ? h.type : "command";
|
|
13771
|
+
const command = importCanonicalString({
|
|
13772
|
+
value: stripCommandPrefix({
|
|
13773
|
+
command: h.command,
|
|
13572
13774
|
converterConfig
|
|
13573
13775
|
}),
|
|
13574
|
-
|
|
13776
|
+
canonical: "command",
|
|
13777
|
+
warn
|
|
13778
|
+
});
|
|
13779
|
+
const timeout = typeof h.timeout === "number" ? h.timeout : void 0;
|
|
13780
|
+
const prompt = importCanonicalString({
|
|
13781
|
+
value: h.prompt,
|
|
13782
|
+
canonical: "prompt",
|
|
13783
|
+
warn
|
|
13784
|
+
});
|
|
13785
|
+
const name = importCanonicalString({
|
|
13786
|
+
value: h.name,
|
|
13787
|
+
canonical: "name",
|
|
13788
|
+
warn
|
|
13789
|
+
});
|
|
13790
|
+
const description = importCanonicalString({
|
|
13791
|
+
value: h.description,
|
|
13792
|
+
canonical: "description",
|
|
13793
|
+
warn
|
|
13794
|
+
});
|
|
13795
|
+
const matcher = rawEntry.matcher;
|
|
13796
|
+
return {
|
|
13797
|
+
type: hookType,
|
|
13798
|
+
...command !== void 0 && command !== null && { command },
|
|
13799
|
+
...timeout !== void 0 && timeout !== null && { timeout },
|
|
13800
|
+
...prompt !== void 0 && prompt !== null && { prompt },
|
|
13801
|
+
...importTypePayloadFields({
|
|
13575
13802
|
h,
|
|
13576
|
-
|
|
13577
|
-
|
|
13803
|
+
hookType,
|
|
13804
|
+
warn
|
|
13578
13805
|
}),
|
|
13579
|
-
...
|
|
13806
|
+
...converterConfig.passthroughFields?.includes("name") && name !== void 0 && { name },
|
|
13807
|
+
...converterConfig.passthroughFields?.includes("description") && description !== void 0 && { description },
|
|
13808
|
+
...importAllPassthroughFields({
|
|
13580
13809
|
h,
|
|
13581
13810
|
hookType,
|
|
13582
13811
|
converterConfig,
|
|
13583
|
-
|
|
13812
|
+
warn
|
|
13584
13813
|
}),
|
|
13585
13814
|
...importGroupPassthroughFields({
|
|
13586
13815
|
rawEntry,
|
|
13587
13816
|
converterConfig
|
|
13588
13817
|
}),
|
|
13589
|
-
...
|
|
13818
|
+
...matcher !== void 0 && matcher !== null && matcher !== "" && { matcher }
|
|
13590
13819
|
};
|
|
13591
13820
|
}
|
|
13592
13821
|
/**
|
|
13593
|
-
*
|
|
13822
|
+
* The fields whose value decides what a hook *is*, listed per hook type. When
|
|
13823
|
+
* one of them cannot be imported, dropping just the field would leave
|
|
13824
|
+
* something worse than nothing: a hook that loses its body runs nothing, and
|
|
13825
|
+
* one that loses its `matcher` fires on *everything* — a silent widening of
|
|
13826
|
+
* what the imported rule does. So the whole definition is skipped instead,
|
|
13827
|
+
* with the reason named. A field that does not define *this* type (a `prompt`
|
|
13828
|
+
* left on a command hook) is not one of them: it is dropped on its own, the
|
|
13829
|
+
* way any other unusable field is.
|
|
13594
13830
|
*/
|
|
13595
|
-
function
|
|
13596
|
-
|
|
13831
|
+
function definingFields({ h, rawEntry, hookType, converterConfig }) {
|
|
13832
|
+
const fields = [{
|
|
13833
|
+
field: "matcher",
|
|
13834
|
+
value: rawEntry.matcher
|
|
13835
|
+
}];
|
|
13836
|
+
if (hookType === "command") fields.push({
|
|
13837
|
+
field: "command",
|
|
13838
|
+
value: typeof h.command === "string" ? stripCommandPrefix({
|
|
13839
|
+
command: h.command,
|
|
13840
|
+
converterConfig
|
|
13841
|
+
}) : h.command
|
|
13842
|
+
});
|
|
13843
|
+
if (hookType === "prompt" || hookType === "agent") fields.push({
|
|
13844
|
+
field: "prompt",
|
|
13845
|
+
value: h.prompt
|
|
13846
|
+
});
|
|
13847
|
+
if (hookType === "http") fields.push({
|
|
13848
|
+
field: "url",
|
|
13849
|
+
value: h.url
|
|
13850
|
+
});
|
|
13851
|
+
if (hookType === "mcp_tool") fields.push({
|
|
13852
|
+
field: "server",
|
|
13853
|
+
value: h.server
|
|
13854
|
+
}, {
|
|
13855
|
+
field: "tool",
|
|
13856
|
+
value: h.tool
|
|
13857
|
+
});
|
|
13858
|
+
return fields;
|
|
13859
|
+
}
|
|
13860
|
+
/**
|
|
13861
|
+
* Why no hook of this matcher group can be imported, or `undefined` when they
|
|
13862
|
+
* can. A group field that *restricts* when its hooks run (`subdividesGroup`)
|
|
13863
|
+
* is the group-level twin of `matcher`: importing the group without it would
|
|
13864
|
+
* widen every hook in it, so the group is skipped instead.
|
|
13865
|
+
*/
|
|
13866
|
+
function describeGroupSkipReason({ rawEntry, converterConfig }) {
|
|
13867
|
+
const entry = rawEntry;
|
|
13868
|
+
for (const { tool, valueType, subdividesGroup } of converterConfig.groupPassthroughFields ?? []) {
|
|
13869
|
+
const value = entry[tool];
|
|
13870
|
+
if (subdividesGroup !== true || value === void 0) continue;
|
|
13871
|
+
if (!isGroupPassthroughValue(value, valueType)) return `Skipping the hooks of a matcher group while importing: its "${tool}" (${JSON.stringify(value)}) is unusable, and these hooks run only where it matches. Importing them without it would widen when they fire, so they are skipped.`;
|
|
13872
|
+
}
|
|
13873
|
+
}
|
|
13874
|
+
/**
|
|
13875
|
+
* Why this hook cannot be imported, or `undefined` when it can.
|
|
13876
|
+
*/
|
|
13877
|
+
function describeHookSkipReason({ h, rawEntry, hookType, converterConfig }) {
|
|
13878
|
+
for (const { field, value } of definingFields({
|
|
13597
13879
|
h,
|
|
13598
13880
|
rawEntry,
|
|
13599
|
-
|
|
13600
|
-
|
|
13601
|
-
}))
|
|
13881
|
+
hookType,
|
|
13882
|
+
converterConfig
|
|
13883
|
+
})) {
|
|
13884
|
+
if (value === void 0 || satisfiesCanonicalField({
|
|
13885
|
+
value,
|
|
13886
|
+
canonical: field
|
|
13887
|
+
})) continue;
|
|
13888
|
+
return `Skipping a hook while importing: its "${field}" (${JSON.stringify(value)}) is unusable — ${describeScalarConstraint({
|
|
13889
|
+
canonical: field,
|
|
13890
|
+
value
|
|
13891
|
+
})} Keeping the hook without it would change what it does, so the whole hook is skipped.`;
|
|
13892
|
+
}
|
|
13893
|
+
}
|
|
13894
|
+
/**
|
|
13895
|
+
* Convert a single tool matcher entry into canonical hook definitions.
|
|
13896
|
+
*/
|
|
13897
|
+
function toolMatcherEntryToCanonical({ rawEntry, converterConfig, warn }) {
|
|
13898
|
+
const hookDefs = rawEntry.hooks ?? [];
|
|
13899
|
+
const groupSkipReason = describeGroupSkipReason({
|
|
13900
|
+
rawEntry,
|
|
13901
|
+
converterConfig
|
|
13902
|
+
});
|
|
13903
|
+
if (groupSkipReason !== void 0) {
|
|
13904
|
+
warn?.(groupSkipReason);
|
|
13905
|
+
return [];
|
|
13906
|
+
}
|
|
13907
|
+
const definitions = [];
|
|
13908
|
+
for (const h of hookDefs) {
|
|
13909
|
+
const skipReason = describeHookSkipReason({
|
|
13910
|
+
h,
|
|
13911
|
+
rawEntry,
|
|
13912
|
+
hookType: isImportedHookType(h.type) ? h.type : "command",
|
|
13913
|
+
converterConfig
|
|
13914
|
+
});
|
|
13915
|
+
if (skipReason !== void 0) {
|
|
13916
|
+
warn?.(skipReason);
|
|
13917
|
+
continue;
|
|
13918
|
+
}
|
|
13919
|
+
definitions.push(toolHookToCanonical({
|
|
13920
|
+
h,
|
|
13921
|
+
rawEntry,
|
|
13922
|
+
converterConfig,
|
|
13923
|
+
warn
|
|
13924
|
+
}));
|
|
13925
|
+
}
|
|
13926
|
+
return definitions;
|
|
13602
13927
|
}
|
|
13603
13928
|
/**
|
|
13604
13929
|
* Assemble the canonical hooks config a tool importer writes to
|
|
@@ -13628,6 +13953,7 @@ function buildImportedHooksConfig({ hooks, overrideKey, version = 1, extraOverri
|
|
|
13628
13953
|
}
|
|
13629
13954
|
function toolHooksToCanonical({ hooks, converterConfig, logger }) {
|
|
13630
13955
|
if (hooks === null || hooks === void 0 || typeof hooks !== "object") return {};
|
|
13956
|
+
const warn = warnOnce(logger);
|
|
13631
13957
|
const canonical = {};
|
|
13632
13958
|
for (const [toolEventName, matcherEntries] of Object.entries(hooks)) {
|
|
13633
13959
|
const eventName = converterConfig.toolToCanonicalEventNames[toolEventName] ?? toolEventName;
|
|
@@ -13638,7 +13964,7 @@ function toolHooksToCanonical({ hooks, converterConfig, logger }) {
|
|
|
13638
13964
|
defs.push(...toolMatcherEntryToCanonical({
|
|
13639
13965
|
rawEntry,
|
|
13640
13966
|
converterConfig,
|
|
13641
|
-
|
|
13967
|
+
warn
|
|
13642
13968
|
}));
|
|
13643
13969
|
}
|
|
13644
13970
|
if (defs.length > 0) canonical[eventName] = defs;
|
|
@@ -13677,7 +14003,9 @@ const ANTIGRAVITY_HOOK_NAME = "rulesync";
|
|
|
13677
14003
|
* map for import. Accepts both the documented named-hook shape
|
|
13678
14004
|
* (`{ "<name>": { "<Event>": [...], "enabled"?: bool } }`) and a legacy flat
|
|
13679
14005
|
* shape (`{ "<Event>": [...] }`) so older or hand-written files still import.
|
|
13680
|
-
* The per-hook `enabled` flag is ignored
|
|
14006
|
+
* The per-hook `enabled` flag is ignored. The canonical `enabled` field is a
|
|
14007
|
+
* property of a single hook definition, whereas Antigravity's flag gates a whole
|
|
14008
|
+
* named group, so the two do not map onto each other.
|
|
13681
14009
|
*/
|
|
13682
14010
|
function flattenAntigravityHooks(parsed) {
|
|
13683
14011
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
@@ -13759,7 +14087,7 @@ var AntigravityHooks = class extends ToolHooks {
|
|
|
13759
14087
|
validate
|
|
13760
14088
|
});
|
|
13761
14089
|
}
|
|
13762
|
-
toRulesyncHooks() {
|
|
14090
|
+
toRulesyncHooks({ logger } = {}) {
|
|
13763
14091
|
let parsed;
|
|
13764
14092
|
try {
|
|
13765
14093
|
parsed = JSON.parse(this.getFileContent());
|
|
@@ -13768,7 +14096,8 @@ var AntigravityHooks = class extends ToolHooks {
|
|
|
13768
14096
|
}
|
|
13769
14097
|
const hooks = toolHooksToCanonical({
|
|
13770
14098
|
hooks: flattenAntigravityHooks(parsed),
|
|
13771
|
-
converterConfig: ANTIGRAVITY_CONVERTER_CONFIG
|
|
14099
|
+
converterConfig: ANTIGRAVITY_CONVERTER_CONFIG,
|
|
14100
|
+
logger
|
|
13772
14101
|
});
|
|
13773
14102
|
const overrideKey = this.constructor.getOverrideKey();
|
|
13774
14103
|
return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
|
|
@@ -14150,7 +14479,7 @@ var ClaudecodeHooks = class extends ToolHooks {
|
|
|
14150
14479
|
validate
|
|
14151
14480
|
});
|
|
14152
14481
|
}
|
|
14153
|
-
toRulesyncHooks() {
|
|
14482
|
+
toRulesyncHooks({ logger } = {}) {
|
|
14154
14483
|
let settings;
|
|
14155
14484
|
try {
|
|
14156
14485
|
settings = JSON.parse(this.getFileContent());
|
|
@@ -14159,7 +14488,8 @@ var ClaudecodeHooks = class extends ToolHooks {
|
|
|
14159
14488
|
}
|
|
14160
14489
|
const hooks = toolHooksToCanonical({
|
|
14161
14490
|
hooks: settings.hooks,
|
|
14162
|
-
converterConfig: this.constructor.getConverterConfig()
|
|
14491
|
+
converterConfig: this.constructor.getConverterConfig(),
|
|
14492
|
+
logger
|
|
14163
14493
|
});
|
|
14164
14494
|
return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
|
|
14165
14495
|
hooks,
|
|
@@ -14318,13 +14648,14 @@ var CodexcliHooks = class CodexcliHooks extends ToolHooks {
|
|
|
14318
14648
|
validate
|
|
14319
14649
|
});
|
|
14320
14650
|
}
|
|
14321
|
-
static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false }) {
|
|
14651
|
+
static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false, logger }) {
|
|
14322
14652
|
const paths = CodexcliHooks.getSettablePaths({ global });
|
|
14323
14653
|
const config = rulesyncHooks.getJson();
|
|
14324
14654
|
const codexHooks = canonicalToToolHooks({
|
|
14325
14655
|
config,
|
|
14326
14656
|
toolOverrideHooks: config.codexcli?.hooks,
|
|
14327
|
-
converterConfig: CODEXCLI_CONVERTER_CONFIG
|
|
14657
|
+
converterConfig: CODEXCLI_CONVERTER_CONFIG,
|
|
14658
|
+
logger
|
|
14328
14659
|
});
|
|
14329
14660
|
const fileContent = JSON.stringify({ hooks: codexHooks }, null, 2);
|
|
14330
14661
|
return new CodexcliHooks({
|
|
@@ -14335,7 +14666,7 @@ var CodexcliHooks = class CodexcliHooks extends ToolHooks {
|
|
|
14335
14666
|
validate
|
|
14336
14667
|
});
|
|
14337
14668
|
}
|
|
14338
|
-
toRulesyncHooks() {
|
|
14669
|
+
toRulesyncHooks({ logger } = {}) {
|
|
14339
14670
|
let parsed;
|
|
14340
14671
|
try {
|
|
14341
14672
|
parsed = JSON.parse(this.getFileContent());
|
|
@@ -14344,7 +14675,8 @@ var CodexcliHooks = class CodexcliHooks extends ToolHooks {
|
|
|
14344
14675
|
}
|
|
14345
14676
|
const hooks = toolHooksToCanonical({
|
|
14346
14677
|
hooks: parsed.hooks,
|
|
14347
|
-
converterConfig: CODEXCLI_CONVERTER_CONFIG
|
|
14678
|
+
converterConfig: CODEXCLI_CONVERTER_CONFIG,
|
|
14679
|
+
logger
|
|
14348
14680
|
});
|
|
14349
14681
|
return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
|
|
14350
14682
|
hooks,
|
|
@@ -14583,13 +14915,13 @@ var CopilotHooks = class CopilotHooks extends ToolHooks {
|
|
|
14583
14915
|
* under `.github/hooks/` is picked up automatically when the CLI is
|
|
14584
14916
|
* invoked from the project root.
|
|
14585
14917
|
*
|
|
14586
|
-
* - **Global scope**: `~/.copilot/hooks/copilot-hooks.json` —
|
|
14587
|
-
*
|
|
14588
|
-
*
|
|
14589
|
-
*
|
|
14590
|
-
*
|
|
14591
|
-
*
|
|
14592
|
-
*
|
|
14918
|
+
* - **Global scope**: `~/.copilot/hooks/copilot-hooks.json` — the directory is
|
|
14919
|
+
* the documented user-level hooks location ("`*.json` files in the
|
|
14920
|
+
* user-level hooks directory. By default this is `~/.copilot/hooks/` on
|
|
14921
|
+
* macOS and Linux, or `%USERPROFILE%\.copilot\hooks\` on Windows"). Every
|
|
14922
|
+
* `*.json` in it is loaded, so the filename remains rulesync's choice, as it
|
|
14923
|
+
* is for project scope. `COPILOT_HOME` relocates the directory upstream
|
|
14924
|
+
* (`$COPILOT_HOME/hooks/`); rulesync does not read that variable yet.
|
|
14593
14925
|
*
|
|
14594
14926
|
* Hook entries on the six matcher-aware events (see
|
|
14595
14927
|
* {@link COPILOTCLI_MATCHER_EVENTS}) may carry an optional `matcher` regex; it
|
|
@@ -15000,17 +15332,18 @@ const DEEPAGENTS_MCP_FILE_NAME = ".mcp.json";
|
|
|
15000
15332
|
const DEEPAGENTS_HOOKS_FILE_NAME = "hooks.json";
|
|
15001
15333
|
//#endregion
|
|
15002
15334
|
//#region src/features/hooks/deepagents-hooks.ts
|
|
15003
|
-
function
|
|
15004
|
-
|
|
15005
|
-
return Array.isArray(val.hooks);
|
|
15335
|
+
function isRecord(value) {
|
|
15336
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
15006
15337
|
}
|
|
15007
15338
|
/**
|
|
15008
|
-
* Convert canonical hooks config to deepagents
|
|
15339
|
+
* Convert the canonical hooks config to the deepagents Hooks v2 document.
|
|
15009
15340
|
*
|
|
15010
|
-
*
|
|
15011
|
-
* { "hooks":
|
|
15341
|
+
* ```json
|
|
15342
|
+
* { "hooks": { "PreToolUse": [{ "matcher": "Bash", "hooks": [{ "type": "command", "command": "…" }] }] } }
|
|
15343
|
+
* ```
|
|
15012
15344
|
*
|
|
15013
|
-
*
|
|
15345
|
+
* Definitions sharing an event and matcher land in one group, preserving their
|
|
15346
|
+
* authored order — upstream runs a group's handlers in sequence.
|
|
15014
15347
|
*/
|
|
15015
15348
|
function canonicalToDeepagentsHooks(config) {
|
|
15016
15349
|
const supported = new Set(DEEPAGENTS_HOOK_EVENTS);
|
|
@@ -15018,7 +15351,7 @@ function canonicalToDeepagentsHooks(config) {
|
|
|
15018
15351
|
...config.hooks,
|
|
15019
15352
|
...config.deepagents?.hooks
|
|
15020
15353
|
};
|
|
15021
|
-
const
|
|
15354
|
+
const hooks = {};
|
|
15022
15355
|
for (const [canonicalEvent, definitions] of Object.entries(effectiveHooks)) {
|
|
15023
15356
|
if (!supported.has(canonicalEvent)) continue;
|
|
15024
15357
|
const deepagentsEvent = CANONICAL_TO_DEEPAGENTS_EVENT_NAMES[canonicalEvent];
|
|
@@ -15026,43 +15359,69 @@ function canonicalToDeepagentsHooks(config) {
|
|
|
15026
15359
|
for (const def of definitions) {
|
|
15027
15360
|
if ((def.type ?? "command") !== "command") continue;
|
|
15028
15361
|
if (!def.command) continue;
|
|
15029
|
-
|
|
15030
|
-
|
|
15031
|
-
command:
|
|
15032
|
-
|
|
15033
|
-
|
|
15034
|
-
|
|
15035
|
-
|
|
15036
|
-
|
|
15362
|
+
const handler = {
|
|
15363
|
+
type: "command",
|
|
15364
|
+
command: def.command
|
|
15365
|
+
};
|
|
15366
|
+
if (def.timeout !== void 0 && def.timeout !== null && def.timeout > 0) handler.timeout = def.timeout;
|
|
15367
|
+
if (def.statusMessage !== void 0 && def.statusMessage !== null) handler.statusMessage = def.statusMessage;
|
|
15368
|
+
const matcher = def.matcher !== void 0 && def.matcher !== null && def.matcher !== "" ? def.matcher : void 0;
|
|
15369
|
+
const groups = hooks[deepagentsEvent] ??= [];
|
|
15370
|
+
const group = groups.find((candidate) => candidate.matcher === matcher);
|
|
15371
|
+
if (group) group.hooks.push(handler);
|
|
15372
|
+
else groups.push({
|
|
15373
|
+
...matcher !== void 0 && { matcher },
|
|
15374
|
+
hooks: [handler]
|
|
15037
15375
|
});
|
|
15038
15376
|
}
|
|
15039
15377
|
}
|
|
15040
|
-
return
|
|
15378
|
+
return hooks;
|
|
15041
15379
|
}
|
|
15042
15380
|
/**
|
|
15043
|
-
* Convert
|
|
15381
|
+
* Convert the Hooks v2 document back to the canonical hooks record.
|
|
15044
15382
|
*/
|
|
15045
|
-
function deepagentsToCanonicalHooks(
|
|
15383
|
+
function deepagentsToCanonicalHooks(hooks) {
|
|
15046
15384
|
const canonical = {};
|
|
15047
|
-
for (const
|
|
15048
|
-
|
|
15049
|
-
if (!Array.isArray(
|
|
15050
|
-
|
|
15051
|
-
|
|
15052
|
-
|
|
15053
|
-
|
|
15054
|
-
|
|
15055
|
-
|
|
15385
|
+
for (const [deepagentsEvent, groups] of Object.entries(hooks)) {
|
|
15386
|
+
const canonicalEvent = DEEPAGENTS_TO_CANONICAL_EVENT_NAMES[deepagentsEvent];
|
|
15387
|
+
if (!canonicalEvent || !Array.isArray(groups)) continue;
|
|
15388
|
+
for (const group of groups) {
|
|
15389
|
+
if (!isRecord(group) || !Array.isArray(group.hooks)) continue;
|
|
15390
|
+
for (const handler of group.hooks) {
|
|
15391
|
+
if (!isRecord(handler) || typeof handler.command !== "string") continue;
|
|
15392
|
+
const def = {
|
|
15393
|
+
type: "command",
|
|
15394
|
+
command: handler.command
|
|
15395
|
+
};
|
|
15396
|
+
if (typeof group.matcher === "string" && group.matcher !== "") def.matcher = group.matcher;
|
|
15397
|
+
if (typeof handler.timeout === "number") def.timeout = handler.timeout;
|
|
15398
|
+
if (typeof handler.statusMessage === "string") def.statusMessage = handler.statusMessage;
|
|
15399
|
+
(canonical[canonicalEvent] ??= []).push(def);
|
|
15400
|
+
}
|
|
15401
|
+
}
|
|
15402
|
+
}
|
|
15403
|
+
return canonical;
|
|
15404
|
+
}
|
|
15405
|
+
/**
|
|
15406
|
+
* Read the pre-v2 flat list. deepagents still loads it until 2026-09-01, so a
|
|
15407
|
+
* `hooks.json` a user has not migrated yet is imported rather than discarded —
|
|
15408
|
+
* but rulesync only ever writes the v2 shape, so regenerating migrates it.
|
|
15409
|
+
*/
|
|
15410
|
+
function deepagentsLegacyToCanonicalHooks(entries) {
|
|
15411
|
+
const canonical = {};
|
|
15412
|
+
for (const entry of entries) {
|
|
15413
|
+
if (!isRecord(entry)) continue;
|
|
15414
|
+
const argv = entry.command;
|
|
15415
|
+
if (!Array.isArray(argv) || argv.length === 0) continue;
|
|
15416
|
+
const command = argv.length === 3 && argv[0] === "bash" && argv[1] === "-c" ? String(argv[2] ?? "") : argv.join(" ");
|
|
15417
|
+
const events = Array.isArray(entry.events) ? entry.events : [];
|
|
15418
|
+
for (const legacyEvent of events) {
|
|
15419
|
+
const canonicalEvent = typeof legacyEvent === "string" ? DEEPAGENTS_LEGACY_TO_CANONICAL_EVENT_NAMES[legacyEvent] : void 0;
|
|
15056
15420
|
if (!canonicalEvent) continue;
|
|
15057
|
-
|
|
15058
|
-
if (existing) existing.push({
|
|
15421
|
+
(canonical[canonicalEvent] ??= []).push({
|
|
15059
15422
|
type: "command",
|
|
15060
15423
|
command
|
|
15061
15424
|
});
|
|
15062
|
-
else canonical[canonicalEvent] = [{
|
|
15063
|
-
type: "command",
|
|
15064
|
-
command
|
|
15065
|
-
}];
|
|
15066
15425
|
}
|
|
15067
15426
|
}
|
|
15068
15427
|
return canonical;
|
|
@@ -15071,7 +15430,7 @@ var DeepagentsHooks = class DeepagentsHooks extends ToolHooks {
|
|
|
15071
15430
|
constructor(params) {
|
|
15072
15431
|
super({
|
|
15073
15432
|
...params,
|
|
15074
|
-
fileContent: params.fileContent ?? JSON.stringify({ hooks:
|
|
15433
|
+
fileContent: params.fileContent ?? JSON.stringify({ hooks: {} }, null, 2)
|
|
15075
15434
|
});
|
|
15076
15435
|
}
|
|
15077
15436
|
isDeletable() {
|
|
@@ -15085,7 +15444,7 @@ var DeepagentsHooks = class DeepagentsHooks extends ToolHooks {
|
|
|
15085
15444
|
}
|
|
15086
15445
|
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
15087
15446
|
const paths = DeepagentsHooks.getSettablePaths({ global });
|
|
15088
|
-
const fileContent = await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? JSON.stringify({ hooks:
|
|
15447
|
+
const fileContent = await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? JSON.stringify({ hooks: {} }, null, 2);
|
|
15089
15448
|
return new DeepagentsHooks({
|
|
15090
15449
|
outputRoot,
|
|
15091
15450
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -15113,7 +15472,8 @@ var DeepagentsHooks = class DeepagentsHooks extends ToolHooks {
|
|
|
15113
15472
|
} catch (error) {
|
|
15114
15473
|
throw new Error(`Failed to parse deepagents hooks content in ${(0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
15115
15474
|
}
|
|
15116
|
-
const
|
|
15475
|
+
const rawHooks = isRecord(parsed) ? parsed.hooks : void 0;
|
|
15476
|
+
const hooks = Array.isArray(rawHooks) ? deepagentsLegacyToCanonicalHooks(rawHooks) : isRecord(rawHooks) ? deepagentsToCanonicalHooks(rawHooks) : {};
|
|
15117
15477
|
return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
|
|
15118
15478
|
hooks,
|
|
15119
15479
|
overrideKey: "deepagents"
|
|
@@ -15130,7 +15490,7 @@ var DeepagentsHooks = class DeepagentsHooks extends ToolHooks {
|
|
|
15130
15490
|
outputRoot,
|
|
15131
15491
|
relativeDirPath,
|
|
15132
15492
|
relativeFilePath,
|
|
15133
|
-
fileContent: JSON.stringify({ hooks:
|
|
15493
|
+
fileContent: JSON.stringify({ hooks: {} }, null, 2),
|
|
15134
15494
|
validate: false
|
|
15135
15495
|
});
|
|
15136
15496
|
}
|
|
@@ -15232,7 +15592,7 @@ var DevinHooks = class DevinHooks extends ToolHooks {
|
|
|
15232
15592
|
validate
|
|
15233
15593
|
});
|
|
15234
15594
|
}
|
|
15235
|
-
toRulesyncHooks() {
|
|
15595
|
+
toRulesyncHooks({ logger } = {}) {
|
|
15236
15596
|
let parsed;
|
|
15237
15597
|
try {
|
|
15238
15598
|
parsed = JSON.parse(this.getFileContent());
|
|
@@ -15240,8 +15600,9 @@ var DevinHooks = class DevinHooks extends ToolHooks {
|
|
|
15240
15600
|
throw new Error(`Failed to parse Devin hooks content in ${(0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
15241
15601
|
}
|
|
15242
15602
|
const hooks = toolHooksToCanonical({
|
|
15243
|
-
hooks: this.getRelativeFilePath() === "config.json" ? isRecord(parsed) && isRecord(parsed.hooks) ? parsed.hooks : {} : parsed,
|
|
15244
|
-
converterConfig: DEVIN_CONVERTER_CONFIG
|
|
15603
|
+
hooks: this.getRelativeFilePath() === "config.json" ? isRecord$1(parsed) && isRecord$1(parsed.hooks) ? parsed.hooks : {} : parsed,
|
|
15604
|
+
converterConfig: DEVIN_CONVERTER_CONFIG,
|
|
15605
|
+
logger
|
|
15245
15606
|
});
|
|
15246
15607
|
return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
|
|
15247
15608
|
hooks,
|
|
@@ -15335,7 +15696,7 @@ var FactorydroidHooks = class FactorydroidHooks extends ToolHooks {
|
|
|
15335
15696
|
validate
|
|
15336
15697
|
});
|
|
15337
15698
|
}
|
|
15338
|
-
toRulesyncHooks() {
|
|
15699
|
+
toRulesyncHooks({ logger } = {}) {
|
|
15339
15700
|
let settings;
|
|
15340
15701
|
try {
|
|
15341
15702
|
settings = JSON.parse(this.getFileContent());
|
|
@@ -15344,7 +15705,8 @@ var FactorydroidHooks = class FactorydroidHooks extends ToolHooks {
|
|
|
15344
15705
|
}
|
|
15345
15706
|
const hooks = toolHooksToCanonical({
|
|
15346
15707
|
hooks: settings.hooks,
|
|
15347
|
-
converterConfig: FACTORYDROID_CONVERTER_CONFIG
|
|
15708
|
+
converterConfig: FACTORYDROID_CONVERTER_CONFIG,
|
|
15709
|
+
logger
|
|
15348
15710
|
});
|
|
15349
15711
|
return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
|
|
15350
15712
|
hooks,
|
|
@@ -15413,13 +15775,14 @@ var GooseHooks = class GooseHooks extends ToolHooks {
|
|
|
15413
15775
|
validate
|
|
15414
15776
|
});
|
|
15415
15777
|
}
|
|
15416
|
-
static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false }) {
|
|
15778
|
+
static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false, logger }) {
|
|
15417
15779
|
const paths = GooseHooks.getSettablePaths({ global });
|
|
15418
15780
|
const config = rulesyncHooks.getJson();
|
|
15419
15781
|
const gooseHooks = canonicalToToolHooks({
|
|
15420
15782
|
config,
|
|
15421
15783
|
toolOverrideHooks: config.goose?.hooks,
|
|
15422
|
-
converterConfig: GOOSE_CONVERTER_CONFIG
|
|
15784
|
+
converterConfig: GOOSE_CONVERTER_CONFIG,
|
|
15785
|
+
logger
|
|
15423
15786
|
});
|
|
15424
15787
|
const fileContent = JSON.stringify({ hooks: gooseHooks }, null, 2);
|
|
15425
15788
|
return new GooseHooks({
|
|
@@ -15430,7 +15793,7 @@ var GooseHooks = class GooseHooks extends ToolHooks {
|
|
|
15430
15793
|
validate
|
|
15431
15794
|
});
|
|
15432
15795
|
}
|
|
15433
|
-
toRulesyncHooks() {
|
|
15796
|
+
toRulesyncHooks({ logger } = {}) {
|
|
15434
15797
|
let parsed;
|
|
15435
15798
|
try {
|
|
15436
15799
|
parsed = JSON.parse(this.getFileContent());
|
|
@@ -15439,7 +15802,8 @@ var GooseHooks = class GooseHooks extends ToolHooks {
|
|
|
15439
15802
|
}
|
|
15440
15803
|
const hooks = toolHooksToCanonical({
|
|
15441
15804
|
hooks: parsed.hooks && typeof parsed.hooks === "object" && !Array.isArray(parsed.hooks) ? Object.fromEntries(Object.entries(parsed.hooks).filter(([eventName]) => Object.hasOwn(GOOSE_TO_CANONICAL_EVENT_NAMES, eventName))) : parsed.hooks,
|
|
15442
|
-
converterConfig: GOOSE_CONVERTER_CONFIG
|
|
15805
|
+
converterConfig: GOOSE_CONVERTER_CONFIG,
|
|
15806
|
+
logger
|
|
15443
15807
|
});
|
|
15444
15808
|
return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
|
|
15445
15809
|
hooks,
|
|
@@ -15543,7 +15907,7 @@ var GrokcliHooks = class GrokcliHooks extends ToolHooks {
|
|
|
15543
15907
|
validate
|
|
15544
15908
|
});
|
|
15545
15909
|
}
|
|
15546
|
-
toRulesyncHooks() {
|
|
15910
|
+
toRulesyncHooks({ logger } = {}) {
|
|
15547
15911
|
let parsed;
|
|
15548
15912
|
try {
|
|
15549
15913
|
parsed = JSON.parse(this.getFileContent());
|
|
@@ -15551,8 +15915,9 @@ var GrokcliHooks = class GrokcliHooks extends ToolHooks {
|
|
|
15551
15915
|
throw new Error(`Failed to parse Grok hooks content in ${(0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
15552
15916
|
}
|
|
15553
15917
|
const hooks = toolHooksToCanonical({
|
|
15554
|
-
hooks: isRecord(parsed) && isRecord(parsed.hooks) ? parsed.hooks : {},
|
|
15555
|
-
converterConfig: GROKCLI_CONVERTER_CONFIG
|
|
15918
|
+
hooks: isRecord$1(parsed) && isRecord$1(parsed.hooks) ? parsed.hooks : {},
|
|
15919
|
+
converterConfig: GROKCLI_CONVERTER_CONFIG,
|
|
15920
|
+
logger
|
|
15556
15921
|
});
|
|
15557
15922
|
return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
|
|
15558
15923
|
hooks,
|
|
@@ -15873,7 +16238,7 @@ var JunieHooks = class JunieHooks extends ToolHooks {
|
|
|
15873
16238
|
validate
|
|
15874
16239
|
});
|
|
15875
16240
|
}
|
|
15876
|
-
toRulesyncHooks() {
|
|
16241
|
+
toRulesyncHooks({ logger } = {}) {
|
|
15877
16242
|
let settings;
|
|
15878
16243
|
try {
|
|
15879
16244
|
settings = JSON.parse(this.getFileContent());
|
|
@@ -15882,7 +16247,8 @@ var JunieHooks = class JunieHooks extends ToolHooks {
|
|
|
15882
16247
|
}
|
|
15883
16248
|
const hooks = toolHooksToCanonical({
|
|
15884
16249
|
hooks: settings.hooks,
|
|
15885
|
-
converterConfig: JUNIE_CONVERTER_CONFIG
|
|
16250
|
+
converterConfig: JUNIE_CONVERTER_CONFIG,
|
|
16251
|
+
logger
|
|
15886
16252
|
});
|
|
15887
16253
|
return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
|
|
15888
16254
|
hooks,
|
|
@@ -16620,7 +16986,7 @@ function buildKiroIdeEntriesForEvent(trigger, definitions) {
|
|
|
16620
16986
|
...def.matcher !== void 0 && def.matcher !== null && def.matcher !== "" && { matcher: def.matcher },
|
|
16621
16987
|
action,
|
|
16622
16988
|
...def.timeout !== void 0 && def.timeout !== null && def.timeout >= 0 && { timeout: def.timeout },
|
|
16623
|
-
enabled: true
|
|
16989
|
+
enabled: def.enabled ?? true
|
|
16624
16990
|
});
|
|
16625
16991
|
}
|
|
16626
16992
|
return entries;
|
|
@@ -16660,6 +17026,7 @@ function kiroIdeHooksToCanonical(entries) {
|
|
|
16660
17026
|
if (entry.description !== void 0 && entry.description !== null) def.description = entry.description;
|
|
16661
17027
|
if (entry.matcher !== void 0 && entry.matcher !== null && entry.matcher !== "") def.matcher = entry.matcher;
|
|
16662
17028
|
if (entry.timeout !== void 0 && entry.timeout !== null) def.timeout = entry.timeout;
|
|
17029
|
+
if (entry.enabled === false) def.enabled = false;
|
|
16663
17030
|
(canonical[eventName] ??= []).push(def);
|
|
16664
17031
|
}
|
|
16665
17032
|
return canonical;
|
|
@@ -17604,6 +17971,37 @@ function unsupportedEventNames(params) {
|
|
|
17604
17971
|
const eventNames = factory.passthroughOverrideEvents ? Object.keys(sharedHooks) : Object.keys(effectiveHooks);
|
|
17605
17972
|
return [...new Set(eventNames)].filter((e) => !supportedEvents.has(e));
|
|
17606
17973
|
}
|
|
17974
|
+
/**
|
|
17975
|
+
* A logger whose warnings name the tool being converted, matching the rest of
|
|
17976
|
+
* this processor's warnings: the shared hooks converter says what is wrong but
|
|
17977
|
+
* not which tool's file it is reading or writing, and one run walks them all.
|
|
17978
|
+
*
|
|
17979
|
+
* Every method delegates explicitly rather than through a prototype, so the
|
|
17980
|
+
* real logger keeps owning its state — a wrapper that inherited it would
|
|
17981
|
+
* absorb the writes `configure` and `outputJson` make.
|
|
17982
|
+
*/
|
|
17983
|
+
function withToolTargetPrefix({ logger, toolTarget }) {
|
|
17984
|
+
return {
|
|
17985
|
+
configure: (options) => logger.configure(options),
|
|
17986
|
+
get verbose() {
|
|
17987
|
+
return logger.verbose;
|
|
17988
|
+
},
|
|
17989
|
+
get silent() {
|
|
17990
|
+
return logger.silent;
|
|
17991
|
+
},
|
|
17992
|
+
get jsonMode() {
|
|
17993
|
+
return logger.jsonMode;
|
|
17994
|
+
},
|
|
17995
|
+
captureData: (key, value) => logger.captureData(key, value),
|
|
17996
|
+
getJsonData: () => logger.getJsonData(),
|
|
17997
|
+
outputJson: (success, error) => logger.outputJson(success, error),
|
|
17998
|
+
info: (message, ...args) => logger.info(message, ...args),
|
|
17999
|
+
success: (message, ...args) => logger.success(message, ...args),
|
|
18000
|
+
warn: (message, ...args) => logger.warn(`For ${toolTarget}: ${message}`, ...args),
|
|
18001
|
+
error: (message, code, ...args) => logger.error(message, code, ...args),
|
|
18002
|
+
debug: (message, ...args) => logger.debug(message, ...args)
|
|
18003
|
+
};
|
|
18004
|
+
}
|
|
17607
18005
|
function unsupportedMatcherEventNames({ factory, effectiveHooks }) {
|
|
17608
18006
|
if (factory.supportsMatcher && !factory.matcherEvents) return [];
|
|
17609
18007
|
const matcherEvents = factory.matcherEvents ? new Set(factory.matcherEvents) : void 0;
|
|
@@ -17826,13 +18224,13 @@ const toolHooksFactories = /* @__PURE__ */ new Map([
|
|
|
17826
18224
|
["deepagents", {
|
|
17827
18225
|
class: DeepagentsHooks,
|
|
17828
18226
|
meta: {
|
|
17829
|
-
supportsProject:
|
|
18227
|
+
supportsProject: true,
|
|
17830
18228
|
supportsGlobal: true,
|
|
17831
18229
|
supportsImport: true
|
|
17832
18230
|
},
|
|
17833
18231
|
supportedEvents: DEEPAGENTS_HOOK_EVENTS,
|
|
17834
18232
|
supportedHookTypes: ["command"],
|
|
17835
|
-
supportsMatcher:
|
|
18233
|
+
supportsMatcher: true
|
|
17836
18234
|
}],
|
|
17837
18235
|
["kiro", {
|
|
17838
18236
|
class: KiroHooks,
|
|
@@ -18044,6 +18442,15 @@ var HooksProcessor = class extends FeatureProcessor {
|
|
|
18044
18442
|
}
|
|
18045
18443
|
for (const [hookType, events] of unsupportedTypeToEvents) this.logger.warn(`Skipped ${hookType}-type hook(s) for ${this.toolTarget} (not supported): ${Array.from(events).join(", ")}`);
|
|
18046
18444
|
}
|
|
18445
|
+
if (this.toolTarget !== "kiro-ide") {
|
|
18446
|
+
const skippedEvents = new Set(unsupportedEventNames({
|
|
18447
|
+
factory,
|
|
18448
|
+
sharedHooks,
|
|
18449
|
+
effectiveHooks
|
|
18450
|
+
}));
|
|
18451
|
+
const eventsWithDisabledHooks = Object.entries(sharedHooks).filter(([event, defs]) => !skippedEvents.has(event) && defs.some((def) => def.enabled === false)).map(([event]) => event);
|
|
18452
|
+
if (eventsWithDisabledHooks.length > 0) this.logger.warn(`Emitting "enabled: false" hook(s) as active for ${this.toolTarget} (only kiro-ide supports the flag): ${eventsWithDisabledHooks.join(", ")}`);
|
|
18453
|
+
}
|
|
18047
18454
|
const eventsWithUnsupportedMatcher = unsupportedMatcherEventNames({
|
|
18048
18455
|
factory,
|
|
18049
18456
|
effectiveHooks
|
|
@@ -18054,7 +18461,10 @@ var HooksProcessor = class extends FeatureProcessor {
|
|
|
18054
18461
|
rulesyncHooks,
|
|
18055
18462
|
validate: true,
|
|
18056
18463
|
global: this.global,
|
|
18057
|
-
logger:
|
|
18464
|
+
logger: withToolTargetPrefix({
|
|
18465
|
+
logger: this.logger,
|
|
18466
|
+
toolTarget: this.toolTarget
|
|
18467
|
+
})
|
|
18058
18468
|
})];
|
|
18059
18469
|
const auxiliaryFiles = await factory.class.getAuxiliaryFiles?.({
|
|
18060
18470
|
outputRoot: this.outputRoot,
|
|
@@ -18064,7 +18474,12 @@ var HooksProcessor = class extends FeatureProcessor {
|
|
|
18064
18474
|
return result;
|
|
18065
18475
|
}
|
|
18066
18476
|
async convertToolFilesToRulesyncFiles(toolFiles) {
|
|
18067
|
-
|
|
18477
|
+
const hooks = toolFiles.filter((f) => f instanceof ToolHooks);
|
|
18478
|
+
const logger = withToolTargetPrefix({
|
|
18479
|
+
logger: this.logger,
|
|
18480
|
+
toolTarget: this.toolTarget
|
|
18481
|
+
});
|
|
18482
|
+
return hooks.map((h) => h.toRulesyncHooks({ logger }));
|
|
18068
18483
|
}
|
|
18069
18484
|
static getToolTargets({ global = false, importOnly = false } = {}) {
|
|
18070
18485
|
if (global) return importOnly ? hooksProcessorToolTargetsGlobalImportable : hooksProcessorToolTargetsGlobal;
|
|
@@ -19707,9 +20122,9 @@ function parseAmpSettingsJsonc(fileContent) {
|
|
|
19707
20122
|
}
|
|
19708
20123
|
function filterMcpServers(mcpServers) {
|
|
19709
20124
|
const filtered = {};
|
|
19710
|
-
if (!isRecord(mcpServers)) return filtered;
|
|
20125
|
+
if (!isRecord$1(mcpServers)) return filtered;
|
|
19711
20126
|
for (const [name, config] of Object.entries(mcpServers)) {
|
|
19712
|
-
if (isPrototypePollutionKey(name) || !isRecord(config)) continue;
|
|
20127
|
+
if (isPrototypePollutionKey(name) || !isRecord$1(config)) continue;
|
|
19713
20128
|
const filteredConfig = {};
|
|
19714
20129
|
for (const [key, value] of Object.entries(config)) {
|
|
19715
20130
|
if (isPrototypePollutionKey(key)) continue;
|
|
@@ -19824,7 +20239,7 @@ var AmpMcp = class AmpMcp extends ToolMcp {
|
|
|
19824
20239
|
success: true,
|
|
19825
20240
|
error: null
|
|
19826
20241
|
};
|
|
19827
|
-
if (!isRecord(mcpServers)) return {
|
|
20242
|
+
if (!isRecord$1(mcpServers)) return {
|
|
19828
20243
|
success: false,
|
|
19829
20244
|
error: /* @__PURE__ */ new Error(`${AMP_MCP_SERVERS_KEY} must be a JSON object`)
|
|
19830
20245
|
};
|
|
@@ -19833,7 +20248,7 @@ var AmpMcp = class AmpMcp extends ToolMcp {
|
|
|
19833
20248
|
success: false,
|
|
19834
20249
|
error: /* @__PURE__ */ new Error(`Server name "${serverName}" is a prototype pollution key and is not allowed`)
|
|
19835
20250
|
};
|
|
19836
|
-
if (!isRecord(serverConfig)) return {
|
|
20251
|
+
if (!isRecord$1(serverConfig)) return {
|
|
19837
20252
|
success: false,
|
|
19838
20253
|
error: /* @__PURE__ */ new Error(`MCP server "${serverName}" must be a JSON object`)
|
|
19839
20254
|
};
|
|
@@ -20433,13 +20848,13 @@ function normalizeCodexMcpServerName(name) {
|
|
|
20433
20848
|
function convertFromCodexFormat(codexMcp) {
|
|
20434
20849
|
const result = {};
|
|
20435
20850
|
for (const [name, config] of Object.entries(codexMcp)) {
|
|
20436
|
-
if (PROTOTYPE_POLLUTION_KEYS.has(name) || !isRecord(config)) continue;
|
|
20851
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(name) || !isRecord$1(config)) continue;
|
|
20437
20852
|
const converted = {};
|
|
20438
20853
|
for (const [key, value] of Object.entries(config)) {
|
|
20439
20854
|
if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
20440
20855
|
if (key === "enabled") {
|
|
20441
20856
|
if (value === false) converted["disabled"] = true;
|
|
20442
|
-
} else if (key === "oauth" && isRecord(value)) converted[key] = mapOauthFromCodex(value);
|
|
20857
|
+
} else if (key === "oauth" && isRecord$1(value)) converted[key] = mapOauthFromCodex(value);
|
|
20443
20858
|
else if (Object.hasOwn(CODEX_TO_RULESYNC_FIELD_MAP, key)) {
|
|
20444
20859
|
const mappedKey = CODEX_TO_RULESYNC_FIELD_MAP[key];
|
|
20445
20860
|
if (mappedKey) if (isValidRenamedArray(key, value)) converted[mappedKey] = value;
|
|
@@ -20458,7 +20873,7 @@ function convertToCodexFormat(mcpServers) {
|
|
|
20458
20873
|
const result = {};
|
|
20459
20874
|
const originalNames = /* @__PURE__ */ new Map();
|
|
20460
20875
|
for (const [name, config] of Object.entries(mcpServers)) {
|
|
20461
|
-
if (!isRecord(config)) continue;
|
|
20876
|
+
if (!isRecord$1(config)) continue;
|
|
20462
20877
|
const { codexName, usedFallback } = normalizeCodexMcpServerName(name);
|
|
20463
20878
|
if (usedFallback) warnWithFallback(void 0, `MCP server "${name}" cannot be represented as a Codex MCP server name (ASCII [a-zA-Z0-9_-] only), so the stable fallback name "${codexName}" was used. Rename the server in .rulesync/mcp.jsonc to choose a readable Codex name.`);
|
|
20464
20879
|
const converted = {};
|
|
@@ -20466,7 +20881,7 @@ function convertToCodexFormat(mcpServers) {
|
|
|
20466
20881
|
if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
20467
20882
|
if (key === "disabled") {
|
|
20468
20883
|
if (value === true) converted["enabled"] = false;
|
|
20469
|
-
} else if (key === "oauth" && isRecord(value)) converted[key] = mapOauthToCodex(value);
|
|
20884
|
+
} else if (key === "oauth" && isRecord$1(value)) converted[key] = mapOauthToCodex(value);
|
|
20470
20885
|
else if (Object.hasOwn(RULESYNC_TO_CODEX_FIELD_MAP, key)) {
|
|
20471
20886
|
const mappedKey = RULESYNC_TO_CODEX_FIELD_MAP[key];
|
|
20472
20887
|
if (mappedKey) if (isValidRenamedArray(key, value)) converted[mappedKey] = value;
|
|
@@ -20542,21 +20957,21 @@ var CodexcliMcp = class CodexcliMcp extends ToolMcp {
|
|
|
20542
20957
|
const strippedMcpServers = rulesyncMcp.getMcpServers();
|
|
20543
20958
|
const rawMcpServers = rulesyncMcp.getJson().mcpServers;
|
|
20544
20959
|
const converted = convertToCodexFormat(Object.fromEntries(Object.entries(strippedMcpServers).map(([serverName, serverConfig]) => {
|
|
20545
|
-
const rawServer = isRecord(rawMcpServers) ? rawMcpServers[serverName] : void 0;
|
|
20960
|
+
const rawServer = isRecord$1(rawMcpServers) ? rawMcpServers[serverName] : void 0;
|
|
20546
20961
|
return [serverName, {
|
|
20547
20962
|
...serverConfig,
|
|
20548
|
-
...isRecord(rawServer) && isEnvVarEntryArray(rawServer.envVars) ? { envVars: rawServer.envVars } : {},
|
|
20549
|
-
...isRecord(rawServer) && typeof rawServer.experimental_environment === "string" ? { experimentalEnvironment: rawServer.experimental_environment } : {},
|
|
20550
|
-
...isRecord(rawServer) && typeof rawServer.experimentalEnvironment === "string" ? { experimentalEnvironment: rawServer.experimentalEnvironment } : {}
|
|
20963
|
+
...isRecord$1(rawServer) && isEnvVarEntryArray(rawServer.envVars) ? { envVars: rawServer.envVars } : {},
|
|
20964
|
+
...isRecord$1(rawServer) && typeof rawServer.experimental_environment === "string" ? { experimentalEnvironment: rawServer.experimental_environment } : {},
|
|
20965
|
+
...isRecord$1(rawServer) && typeof rawServer.experimentalEnvironment === "string" ? { experimentalEnvironment: rawServer.experimentalEnvironment } : {}
|
|
20551
20966
|
}];
|
|
20552
20967
|
})));
|
|
20553
20968
|
const filteredMcpServers = this.removeEmptyEntries(converted);
|
|
20554
20969
|
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`);
|
|
20555
|
-
const existingMcpServers = isRecord(configToml["mcp_servers"]) ? configToml["mcp_servers"] : {};
|
|
20970
|
+
const existingMcpServers = isRecord$1(configToml["mcp_servers"]) ? configToml["mcp_servers"] : {};
|
|
20556
20971
|
const mergedMcpServers = Object.fromEntries(Object.entries(filteredMcpServers).map(([name, serverConfig]) => {
|
|
20557
|
-
const existingServer = isRecord(existingMcpServers[name]) ? existingMcpServers[name] : void 0;
|
|
20972
|
+
const existingServer = isRecord$1(existingMcpServers[name]) ? existingMcpServers[name] : void 0;
|
|
20558
20973
|
const serverRecord = serverConfig;
|
|
20559
|
-
if (existingServer && isRecord(existingServer["tools"]) && !("tools" in serverRecord)) return [name, {
|
|
20974
|
+
if (existingServer && isRecord$1(existingServer["tools"]) && !("tools" in serverRecord)) return [name, {
|
|
20560
20975
|
...serverRecord,
|
|
20561
20976
|
tools: existingServer["tools"]
|
|
20562
20977
|
}];
|
|
@@ -21513,7 +21928,7 @@ function convertServerToGooseExtension(name, config, logger) {
|
|
|
21513
21928
|
function convertToGooseFormat({ mcpServers, existingExtensions, logger }) {
|
|
21514
21929
|
const generated = {};
|
|
21515
21930
|
for (const [name, config] of Object.entries(mcpServers)) {
|
|
21516
|
-
if (PROTOTYPE_POLLUTION_KEYS.has(name) || !isRecord(config)) continue;
|
|
21931
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(name) || !isRecord$1(config)) continue;
|
|
21517
21932
|
const ext = convertServerToGooseExtension(name, config, logger);
|
|
21518
21933
|
if (ext !== void 0) generated[name] = ext;
|
|
21519
21934
|
}
|
|
@@ -21521,7 +21936,7 @@ function convertToGooseFormat({ mcpServers, existingExtensions, logger }) {
|
|
|
21521
21936
|
const retracted = [];
|
|
21522
21937
|
for (const [name, ext] of Object.entries(existingExtensions)) {
|
|
21523
21938
|
if (PROTOTYPE_POLLUTION_KEYS.has(name)) continue;
|
|
21524
|
-
const type = isRecord(ext) ? existingExtensionType(ext) : void 0;
|
|
21939
|
+
const type = isRecord$1(ext) ? existingExtensionType(ext) : void 0;
|
|
21525
21940
|
if (type !== void 0 && GOOSE_MCP_EXTENSION_TYPES.has(type)) {
|
|
21526
21941
|
if (!Object.hasOwn(generated, name)) retracted.push(name);
|
|
21527
21942
|
continue;
|
|
@@ -21555,7 +21970,7 @@ function convertFromGooseFormat(extensions) {
|
|
|
21555
21970
|
const result = {};
|
|
21556
21971
|
const skipped = [];
|
|
21557
21972
|
for (const [name, ext] of Object.entries(extensions)) {
|
|
21558
|
-
if (PROTOTYPE_POLLUTION_KEYS.has(name) || !isRecord(ext)) continue;
|
|
21973
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(name) || !isRecord$1(ext)) continue;
|
|
21559
21974
|
const type = existingExtensionType(ext);
|
|
21560
21975
|
if (type === void 0 || !GOOSE_MCP_EXTENSION_TYPES.has(type)) {
|
|
21561
21976
|
skipped.push(name);
|
|
@@ -21609,7 +22024,7 @@ function buildGoosePluginStdioServer(config) {
|
|
|
21609
22024
|
function convertToGoosePluginMcpServers(mcpServers, logger) {
|
|
21610
22025
|
const result = {};
|
|
21611
22026
|
for (const [name, config] of Object.entries(mcpServers)) {
|
|
21612
|
-
if (PROTOTYPE_POLLUTION_KEYS.has(name) || !isRecord(config)) continue;
|
|
22027
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(name) || !isRecord$1(config)) continue;
|
|
21613
22028
|
const gooseType = resolveGooseType(config, resolveGooseUrl(config));
|
|
21614
22029
|
if (gooseType !== "stdio") {
|
|
21615
22030
|
warnWithFallback(logger, `Goose open-plugin MCP manifest (${GOOSE_PLUGIN_MCP_RELATIVE_PATH}) is stdio-only; skipping "${name}" (${gooseType}). Sync it with --global to ~/.config/goose/config.yaml instead.`);
|
|
@@ -21650,7 +22065,7 @@ var GooseMcp = class GooseMcp extends ToolMcp {
|
|
|
21650
22065
|
parsePluginManifest(fileContent) {
|
|
21651
22066
|
try {
|
|
21652
22067
|
const parsed = JSON.parse(fileContent);
|
|
21653
|
-
return isRecord(parsed) ? parsed : {};
|
|
22068
|
+
return isRecord$1(parsed) ? parsed : {};
|
|
21654
22069
|
} catch (error) {
|
|
21655
22070
|
throw new Error(`Failed to parse Goose MCP manifest at ${(0, node_path.join)(this.relativeDirPath, this.relativeFilePath)}: ${formatError(error)}`, { cause: error });
|
|
21656
22071
|
}
|
|
@@ -21699,7 +22114,7 @@ var GooseMcp = class GooseMcp extends ToolMcp {
|
|
|
21699
22114
|
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
21700
22115
|
const existingContent = await readFileContentOrNull(filePath) ?? "";
|
|
21701
22116
|
const config = parseGooseConfig(existingContent, paths.relativeDirPath, paths.relativeFilePath);
|
|
21702
|
-
const existingExtensions = isRecord(config.extensions) ? config.extensions : {};
|
|
22117
|
+
const existingExtensions = isRecord$1(config.extensions) ? config.extensions : {};
|
|
21703
22118
|
return new GooseMcp({
|
|
21704
22119
|
outputRoot,
|
|
21705
22120
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -21721,10 +22136,10 @@ var GooseMcp = class GooseMcp extends ToolMcp {
|
|
|
21721
22136
|
}
|
|
21722
22137
|
toRulesyncMcp() {
|
|
21723
22138
|
if (!this.global) {
|
|
21724
|
-
const mcpServers = isRecord(this.config.mcpServers) ? this.config.mcpServers : {};
|
|
22139
|
+
const mcpServers = isRecord$1(this.config.mcpServers) ? this.config.mcpServers : {};
|
|
21725
22140
|
return this.toRulesyncMcpDefault({ fileContent: JSON.stringify({ mcpServers }, null, 2) });
|
|
21726
22141
|
}
|
|
21727
|
-
const mcpServers = convertFromGooseFormat(isRecord(this.config.extensions) ? this.config.extensions : {});
|
|
22142
|
+
const mcpServers = convertFromGooseFormat(isRecord$1(this.config.extensions) ? this.config.extensions : {});
|
|
21728
22143
|
return this.toRulesyncMcpDefault({ fileContent: JSON.stringify({ mcpServers }, null, 2) });
|
|
21729
22144
|
}
|
|
21730
22145
|
validate() {
|
|
@@ -21760,7 +22175,7 @@ function convertToGrokFormat(mcpServers) {
|
|
|
21760
22175
|
const result = {};
|
|
21761
22176
|
for (const [name, config] of Object.entries(mcpServers)) {
|
|
21762
22177
|
if (PROTOTYPE_POLLUTION_KEYS.has(name)) continue;
|
|
21763
|
-
if (!isRecord(config)) continue;
|
|
22178
|
+
if (!isRecord$1(config)) continue;
|
|
21764
22179
|
const converted = {};
|
|
21765
22180
|
for (const [key, value] of Object.entries(config)) {
|
|
21766
22181
|
if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
@@ -21775,7 +22190,7 @@ function convertToGrokFormat(mcpServers) {
|
|
|
21775
22190
|
function convertFromGrokFormat(grokMcp) {
|
|
21776
22191
|
const result = {};
|
|
21777
22192
|
for (const [name, config] of Object.entries(grokMcp)) {
|
|
21778
|
-
if (PROTOTYPE_POLLUTION_KEYS.has(name) || !isRecord(config)) continue;
|
|
22193
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(name) || !isRecord$1(config)) continue;
|
|
21779
22194
|
const converted = {};
|
|
21780
22195
|
for (const [key, value] of Object.entries(config)) {
|
|
21781
22196
|
if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
@@ -21913,7 +22328,7 @@ function resolveHermesTimeout(config) {
|
|
|
21913
22328
|
* import alike. See the Hermes mcp-config-reference.
|
|
21914
22329
|
*/
|
|
21915
22330
|
function copyHermesOauth(source) {
|
|
21916
|
-
if (!isRecord(source)) return;
|
|
22331
|
+
if (!isRecord$1(source)) return;
|
|
21917
22332
|
const oauth = {};
|
|
21918
22333
|
for (const key of [
|
|
21919
22334
|
"redirect_uri",
|
|
@@ -22047,13 +22462,13 @@ function convertServerToHermes(config) {
|
|
|
22047
22462
|
function convertToHermesFormat(mcpServers) {
|
|
22048
22463
|
const result = {};
|
|
22049
22464
|
for (const [name, config] of Object.entries(mcpServers)) {
|
|
22050
|
-
if (PROTOTYPE_POLLUTION_KEYS.has(name) || !isRecord(config)) continue;
|
|
22465
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(name) || !isRecord$1(config)) continue;
|
|
22051
22466
|
result[name] = convertServerToHermes(config);
|
|
22052
22467
|
}
|
|
22053
22468
|
return result;
|
|
22054
22469
|
}
|
|
22055
22470
|
function mergeHermesMcpServers(config, mcpServers) {
|
|
22056
|
-
const existingMcpServers = isRecord(config.mcp_servers) ? config.mcp_servers : {};
|
|
22471
|
+
const existingMcpServers = isRecord$1(config.mcp_servers) ? config.mcp_servers : {};
|
|
22057
22472
|
return {
|
|
22058
22473
|
...config,
|
|
22059
22474
|
mcp_servers: {
|
|
@@ -22072,7 +22487,7 @@ function convertFromHermesFormat(mcpServers) {
|
|
|
22072
22487
|
const result = {};
|
|
22073
22488
|
const hermesOverrides = {};
|
|
22074
22489
|
for (const [name, config] of Object.entries(mcpServers)) {
|
|
22075
|
-
if (PROTOTYPE_POLLUTION_KEYS.has(name) || !isRecord(config)) continue;
|
|
22490
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(name) || !isRecord$1(config)) continue;
|
|
22076
22491
|
const server = {};
|
|
22077
22492
|
if (typeof config.command === "string") server.command = config.command;
|
|
22078
22493
|
if (isStringArray$1(config.args)) server.args = config.args;
|
|
@@ -22081,7 +22496,7 @@ function convertFromHermesFormat(mcpServers) {
|
|
|
22081
22496
|
if (isPlainObject$1(config.headers)) server.headers = omitPrototypePollutionKeys(config.headers);
|
|
22082
22497
|
if (config.enabled === false) server.disabled = true;
|
|
22083
22498
|
if (typeof config.timeout === "number") server.networkTimeout = config.timeout;
|
|
22084
|
-
if (isRecord(config.tools)) applyHermesToolsBlock(config.tools, server);
|
|
22499
|
+
if (isRecord$1(config.tools)) applyHermesToolsBlock(config.tools, server);
|
|
22085
22500
|
result[name] = server;
|
|
22086
22501
|
const hermesServer = { ...server };
|
|
22087
22502
|
if (copyHermesAdvancedFields(config, hermesServer)) hermesOverrides[name] = hermesServer;
|
|
@@ -22120,7 +22535,7 @@ var HermesagentMcp = class HermesagentMcp extends ToolMcp {
|
|
|
22120
22535
|
const merged = mergeHermesMcpServers(parseSharedConfig({
|
|
22121
22536
|
format: "yaml",
|
|
22122
22537
|
fileContent
|
|
22123
|
-
}), isRecord(this.config.mcp_servers) ? this.config.mcp_servers : {});
|
|
22538
|
+
}), isRecord$1(this.config.mcp_servers) ? this.config.mcp_servers : {});
|
|
22124
22539
|
this.config = merged;
|
|
22125
22540
|
super.setFileContent(applySharedConfigPatch({
|
|
22126
22541
|
fileKey: getHermesagentConfigSharedFileKey({ global: this.global }),
|
|
@@ -22184,7 +22599,7 @@ var HermesagentMcp = class HermesagentMcp extends ToolMcp {
|
|
|
22184
22599
|
});
|
|
22185
22600
|
}
|
|
22186
22601
|
toRulesyncMcp() {
|
|
22187
|
-
const { mcpServers: servers, hermesOverrides } = convertFromHermesFormat(isRecord(this.config.mcp_servers) ? this.config.mcp_servers : {});
|
|
22602
|
+
const { mcpServers: servers, hermesOverrides } = convertFromHermesFormat(isRecord$1(this.config.mcp_servers) ? this.config.mcp_servers : {});
|
|
22188
22603
|
return this.toRulesyncMcpDefault({
|
|
22189
22604
|
outputRoot: getHermesagentRulesyncOutputRoot({
|
|
22190
22605
|
nativeOutputRoot: this.outputRoot,
|
|
@@ -22496,7 +22911,7 @@ function convertServerToKiloFormat(serverName, serverConfig, existingEntry, logg
|
|
|
22496
22911
|
*/
|
|
22497
22912
|
function readExistingKiloMcpEntries(fileContent) {
|
|
22498
22913
|
const mcp = (0, jsonc_parser.parse)(fileContent || "{}")?.mcp;
|
|
22499
|
-
if (!isRecord(mcp)) return {};
|
|
22914
|
+
if (!isRecord$1(mcp)) return {};
|
|
22500
22915
|
const entries = {};
|
|
22501
22916
|
for (const [serverName, entry] of Object.entries(mcp)) {
|
|
22502
22917
|
const result = KiloMcpServerSchema.safeParse(entry);
|
|
@@ -22800,7 +23215,7 @@ async function readKimiCodeConfig({ outputRoot }) {
|
|
|
22800
23215
|
return {
|
|
22801
23216
|
parsed: true,
|
|
22802
23217
|
content,
|
|
22803
|
-
mcp: isRecord(mcp) ? mcp : {}
|
|
23218
|
+
mcp: isRecord$1(mcp) ? mcp : {}
|
|
22804
23219
|
};
|
|
22805
23220
|
} catch {
|
|
22806
23221
|
return {
|
|
@@ -22954,7 +23369,7 @@ var KimiCodeMcp = class KimiCodeMcp extends ToolMcp {
|
|
|
22954
23369
|
static async getAuxiliaryFiles({ outputRoot = process.cwd(), global = false, rulesyncMcp, logger }) {
|
|
22955
23370
|
if (!global) return [];
|
|
22956
23371
|
const block = rulesyncMcp.getJson()["kimi-code"];
|
|
22957
|
-
if (!isRecord(block)) return [];
|
|
23372
|
+
if (!isRecord$1(block)) return [];
|
|
22958
23373
|
const startupTimeoutMs = typeof block.startupTimeoutMs === "number" ? block.startupTimeoutMs : void 0;
|
|
22959
23374
|
const toolTimeoutMs = typeof block.toolTimeoutMs === "number" ? block.toolTimeoutMs : void 0;
|
|
22960
23375
|
if (startupTimeoutMs === void 0 && toolTimeoutMs === void 0) return [];
|
|
@@ -23007,6 +23422,68 @@ var KimiCodeMcp = class KimiCodeMcp extends ToolMcp {
|
|
|
23007
23422
|
};
|
|
23008
23423
|
//#endregion
|
|
23009
23424
|
//#region src/features/mcp/kiro-mcp.ts
|
|
23425
|
+
/**
|
|
23426
|
+
* Union of two optional string lists, preserving order and dropping duplicates.
|
|
23427
|
+
* Returns `undefined` only when neither side was authored at all, so the caller
|
|
23428
|
+
* omits the key entirely rather than writing an empty array — but an explicitly
|
|
23429
|
+
* authored `[]` is kept, which keeps import → generate idempotent.
|
|
23430
|
+
*/
|
|
23431
|
+
function mergeToolLists(...lists) {
|
|
23432
|
+
if (lists.every((list) => list === void 0)) return void 0;
|
|
23433
|
+
const merged = [];
|
|
23434
|
+
for (const list of lists) for (const tool of list ?? []) if (!merged.includes(tool)) merged.push(tool);
|
|
23435
|
+
return merged;
|
|
23436
|
+
}
|
|
23437
|
+
/**
|
|
23438
|
+
* Translate rulesync's Kiro-only authoring keys onto the field names Kiro
|
|
23439
|
+
* actually reads in `mcp.json`.
|
|
23440
|
+
*
|
|
23441
|
+
* - `kiroAutoApprove` → `autoApprove` (tools run without a confirmation prompt)
|
|
23442
|
+
* - `kiroAutoBlock` → `disabledTools` (tools hidden from the agent)
|
|
23443
|
+
*
|
|
23444
|
+
* `disabledTools` is the only block list Kiro reads, and it is also a canonical
|
|
23445
|
+
* rulesync field, so `kiroAutoBlock` is a redundant spelling of it. Prefer the
|
|
23446
|
+
* canonical field; see the note on `kiroAutoBlock` in `src/types/mcp.ts`.
|
|
23447
|
+
*
|
|
23448
|
+
* Both native names are documented per-server fields, so a config that already
|
|
23449
|
+
* spells them natively keeps working: the two lists are merged rather than
|
|
23450
|
+
* one overwriting the other.
|
|
23451
|
+
* @see https://kiro.dev/docs/mcp/configuration/
|
|
23452
|
+
*/
|
|
23453
|
+
function toKiroMcpServers(servers) {
|
|
23454
|
+
return Object.fromEntries(Object.entries(servers).map(([name, server]) => {
|
|
23455
|
+
const { kiroAutoApprove, kiroAutoBlock, disabledTools, ...rest } = server;
|
|
23456
|
+
const autoApprove = mergeToolLists(isStringArray$1(rest.autoApprove) ? rest.autoApprove : void 0, kiroAutoApprove);
|
|
23457
|
+
const disabled = mergeToolLists(disabledTools, kiroAutoBlock);
|
|
23458
|
+
return [name, {
|
|
23459
|
+
...rest,
|
|
23460
|
+
...autoApprove !== void 0 && { autoApprove },
|
|
23461
|
+
...disabled !== void 0 && { disabledTools: disabled }
|
|
23462
|
+
}];
|
|
23463
|
+
}));
|
|
23464
|
+
}
|
|
23465
|
+
/**
|
|
23466
|
+
* Import direction of {@link toKiroMcpServers}: Kiro's `autoApprove` becomes the
|
|
23467
|
+
* rulesync-only `kiroAutoApprove` so a regenerate reproduces it. `disabledTools`
|
|
23468
|
+
* is left alone — it is already a canonical rulesync key with the same meaning,
|
|
23469
|
+
* so `kiroAutoBlock` deliberately has no import counterpart.
|
|
23470
|
+
*
|
|
23471
|
+
* Only a genuine string array is renamed. `kiroAutoApprove` is typed as one, so
|
|
23472
|
+
* moving a hand-written `"autoApprove": "all"` there would produce a
|
|
23473
|
+
* `.rulesync/mcp.jsonc` the next generate refuses to parse; such a value stays
|
|
23474
|
+
* under its original key and passes through untouched instead.
|
|
23475
|
+
*/
|
|
23476
|
+
function fromKiroMcpServers(servers) {
|
|
23477
|
+
return Object.fromEntries(Object.entries(servers).map(([name, server]) => {
|
|
23478
|
+
if (server === null || typeof server !== "object" || Array.isArray(server)) return [name, server];
|
|
23479
|
+
const { autoApprove, ...rest } = server;
|
|
23480
|
+
if (!isStringArray$1(autoApprove)) return [name, server];
|
|
23481
|
+
return [name, {
|
|
23482
|
+
...rest,
|
|
23483
|
+
kiroAutoApprove: autoApprove
|
|
23484
|
+
}];
|
|
23485
|
+
}));
|
|
23486
|
+
}
|
|
23010
23487
|
var KiroMcp = class KiroMcp extends ToolMcp {
|
|
23011
23488
|
json;
|
|
23012
23489
|
constructor(params) {
|
|
@@ -23035,7 +23512,7 @@ var KiroMcp = class KiroMcp extends ToolMcp {
|
|
|
23035
23512
|
}
|
|
23036
23513
|
static fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true }) {
|
|
23037
23514
|
const paths = this.getSettablePaths();
|
|
23038
|
-
const fileContent = JSON.stringify({ mcpServers: rulesyncMcp.getMcpServers() }, null, 2);
|
|
23515
|
+
const fileContent = JSON.stringify({ mcpServers: toKiroMcpServers(rulesyncMcp.getMcpServers()) }, null, 2);
|
|
23039
23516
|
return new KiroMcp({
|
|
23040
23517
|
outputRoot,
|
|
23041
23518
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -23045,7 +23522,9 @@ var KiroMcp = class KiroMcp extends ToolMcp {
|
|
|
23045
23522
|
});
|
|
23046
23523
|
}
|
|
23047
23524
|
toRulesyncMcp() {
|
|
23048
|
-
|
|
23525
|
+
const mcpServers = this.json.mcpServers;
|
|
23526
|
+
const translated = isMcpServers(mcpServers) ? fromKiroMcpServers(mcpServers) : {};
|
|
23527
|
+
return this.toRulesyncMcpDefault({ fileContent: JSON.stringify({ mcpServers: translated }, null, 2) });
|
|
23049
23528
|
}
|
|
23050
23529
|
validate() {
|
|
23051
23530
|
return {
|
|
@@ -23931,7 +24410,7 @@ async function readRovodevConfigYaml({ outputRoot }) {
|
|
|
23931
24410
|
});
|
|
23932
24411
|
}
|
|
23933
24412
|
function disabledNamesOf(config) {
|
|
23934
|
-
const mcpBlock = config && isRecord(config.mcp) ? config.mcp : {};
|
|
24413
|
+
const mcpBlock = config && isRecord$1(config.mcp) ? config.mcp : {};
|
|
23935
24414
|
return isStringArray$1(mcpBlock.disabledMcpServers) ? mcpBlock.disabledMcpServers : [];
|
|
23936
24415
|
}
|
|
23937
24416
|
/**
|
|
@@ -24050,7 +24529,7 @@ var RovodevMcp = class RovodevMcp extends ToolMcp {
|
|
|
24050
24529
|
const managedNames = Object.keys(servers).filter((name) => toRovodevServer(name, servers[name]) !== null);
|
|
24051
24530
|
const disabledNames = managedNames.filter((name) => {
|
|
24052
24531
|
const server = servers[name];
|
|
24053
|
-
return isRecord(server) && server.disabled === true;
|
|
24532
|
+
return isRecord$1(server) && server.disabled === true;
|
|
24054
24533
|
});
|
|
24055
24534
|
const existingContent = await readFileContentOrNull((0, node_path.join)(outputRoot, ROVODEV_DIR, ROVODEV_CONFIG_FILE_NAME)) ?? "";
|
|
24056
24535
|
let existingParsed;
|
|
@@ -24064,7 +24543,7 @@ var RovodevMcp = class RovodevMcp extends ToolMcp {
|
|
|
24064
24543
|
logger?.warn(`Skipping the Rovo Dev mcp.disabledMcpServers update: ${formatError(error)}`);
|
|
24065
24544
|
return [];
|
|
24066
24545
|
}
|
|
24067
|
-
const existingMcp = isRecord(existingParsed.mcp) ? { ...existingParsed.mcp } : {};
|
|
24546
|
+
const existingMcp = isRecord$1(existingParsed.mcp) ? { ...existingParsed.mcp } : {};
|
|
24068
24547
|
const existingDisabled = isStringArray$1(existingMcp.disabledMcpServers) ? existingMcp.disabledMcpServers : [];
|
|
24069
24548
|
const managedNameSet = new Set(managedNames);
|
|
24070
24549
|
const reEnabled = existingDisabled.filter((name) => managedNameSet.has(name) && !disabledNames.includes(name));
|
|
@@ -24256,7 +24735,7 @@ function deriveTransportAllowlist(servers) {
|
|
|
24256
24735
|
http: false
|
|
24257
24736
|
};
|
|
24258
24737
|
for (const server of Object.values(servers)) {
|
|
24259
|
-
if (!isRecord(server)) continue;
|
|
24738
|
+
if (!isRecord$1(server)) continue;
|
|
24260
24739
|
const transport = transportOf(server);
|
|
24261
24740
|
if (transport) allowlist[transport] = true;
|
|
24262
24741
|
}
|
|
@@ -27891,13 +28370,13 @@ const CURSOR_TYPE_TO_CANONICAL = {
|
|
|
27891
28370
|
WebFetch: "webfetch",
|
|
27892
28371
|
Mcp: "mcp"
|
|
27893
28372
|
};
|
|
27894
|
-
const MCP_CANONICAL_PREFIX$
|
|
28373
|
+
const MCP_CANONICAL_PREFIX$2 = "mcp__";
|
|
27895
28374
|
/**
|
|
27896
28375
|
* Returns true if the canonical category is the per-tool MCP form
|
|
27897
28376
|
* `mcp__<server>__<tool>`.
|
|
27898
28377
|
*/
|
|
27899
28378
|
function isMcpScopedCategory(canonical) {
|
|
27900
|
-
return canonical.startsWith(MCP_CANONICAL_PREFIX$
|
|
28379
|
+
return canonical.startsWith(MCP_CANONICAL_PREFIX$2) && canonical.length > 5;
|
|
27901
28380
|
}
|
|
27902
28381
|
function toCursorType(canonical) {
|
|
27903
28382
|
if (isMcpScopedCategory(canonical)) return "Mcp";
|
|
@@ -27931,7 +28410,7 @@ function toCanonicalCategory$1(cursorType, pattern) {
|
|
|
27931
28410
|
if (match) {
|
|
27932
28411
|
const server = match[1] ?? "*";
|
|
27933
28412
|
const tool = match[2] ?? "*";
|
|
27934
|
-
return `${MCP_CANONICAL_PREFIX$
|
|
28413
|
+
return `${MCP_CANONICAL_PREFIX$2}${server}__${tool}`;
|
|
27935
28414
|
}
|
|
27936
28415
|
return CURSOR_TYPE_TO_CANONICAL[cursorType] ?? cursorType.toLowerCase();
|
|
27937
28416
|
}
|
|
@@ -28160,7 +28639,7 @@ function convertCursorToRulesyncPermissions(params) {
|
|
|
28160
28639
|
const { type, pattern } = parseCursorPermissionEntry(entry);
|
|
28161
28640
|
const canonical = toCanonicalCategory$1(type, pattern);
|
|
28162
28641
|
if (!permission[canonical]) permission[canonical] = {};
|
|
28163
|
-
const canonicalPattern = type === "Mcp" && canonical.startsWith(MCP_CANONICAL_PREFIX$
|
|
28642
|
+
const canonicalPattern = type === "Mcp" && canonical.startsWith(MCP_CANONICAL_PREFIX$2) ? "*" : pattern;
|
|
28164
28643
|
permission[canonical][canonicalPattern] = action;
|
|
28165
28644
|
}
|
|
28166
28645
|
};
|
|
@@ -28295,14 +28774,14 @@ var DevinPermissions = class DevinPermissions extends ToolPermissions {
|
|
|
28295
28774
|
let settings;
|
|
28296
28775
|
try {
|
|
28297
28776
|
const parsed = JSON.parse(existingContent);
|
|
28298
|
-
settings = isRecord(parsed) ? parsed : {};
|
|
28777
|
+
settings = isRecord$1(parsed) ? parsed : {};
|
|
28299
28778
|
} catch (error) {
|
|
28300
28779
|
throw new Error(`Failed to parse existing Devin config at ${filePath}: ${formatError(error)}`, { cause: error });
|
|
28301
28780
|
}
|
|
28302
28781
|
const config = rulesyncPermissions.getJson();
|
|
28303
28782
|
const { allow, ask, deny } = convertRulesyncToDevinPermissions(config);
|
|
28304
28783
|
const managedScopes = new Set(Object.keys(config.permission).map((category) => toDevinScope(category)));
|
|
28305
|
-
const existingPermissions = isRecord(settings.permissions) ? settings.permissions : {};
|
|
28784
|
+
const existingPermissions = isRecord$1(settings.permissions) ? settings.permissions : {};
|
|
28306
28785
|
const preserve = (entries) => (entries ?? []).filter((entry) => !managedScopes.has(parseDevinPermissionEntry(entry).scope));
|
|
28307
28786
|
const mergedAllow = (0, es_toolkit.uniq)([...preserve(existingPermissions.allow), ...allow].toSorted());
|
|
28308
28787
|
const mergedAsk = (0, es_toolkit.uniq)([...preserve(existingPermissions.ask), ...ask].toSorted());
|
|
@@ -28332,11 +28811,11 @@ var DevinPermissions = class DevinPermissions extends ToolPermissions {
|
|
|
28332
28811
|
let settings;
|
|
28333
28812
|
try {
|
|
28334
28813
|
const parsed = JSON.parse(this.getFileContent());
|
|
28335
|
-
settings = isRecord(parsed) ? parsed : {};
|
|
28814
|
+
settings = isRecord$1(parsed) ? parsed : {};
|
|
28336
28815
|
} catch (error) {
|
|
28337
28816
|
throw new Error(`Failed to parse Devin permissions content in ${(0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
28338
28817
|
}
|
|
28339
|
-
const permissions = isRecord(settings.permissions) ? settings.permissions : {};
|
|
28818
|
+
const permissions = isRecord$1(settings.permissions) ? settings.permissions : {};
|
|
28340
28819
|
const config = convertDevinToRulesyncPermissions({
|
|
28341
28820
|
allow: Array.isArray(permissions.allow) ? permissions.allow : [],
|
|
28342
28821
|
ask: Array.isArray(permissions.ask) ? permissions.ask : [],
|
|
@@ -28685,7 +29164,7 @@ var GoosePermissions = class GoosePermissions extends ToolPermissions {
|
|
|
28685
29164
|
} catch (error) {
|
|
28686
29165
|
throw new Error(`Failed to parse existing Goose permission.yaml at ${filePath}: ${formatError(error)}`, { cause: error });
|
|
28687
29166
|
}
|
|
28688
|
-
const config = isRecord(parsed) ? { ...parsed } : {};
|
|
29167
|
+
const config = isRecord$1(parsed) ? { ...parsed } : {};
|
|
28689
29168
|
const userPermission = convertRulesyncToGoosePermissionConfig({
|
|
28690
29169
|
config: rulesyncPermissions.getJson(),
|
|
28691
29170
|
logger
|
|
@@ -28708,8 +29187,8 @@ var GoosePermissions = class GoosePermissions extends ToolPermissions {
|
|
|
28708
29187
|
} catch (error) {
|
|
28709
29188
|
throw new Error(`Failed to parse Goose permissions content in ${(0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
28710
29189
|
}
|
|
28711
|
-
const config = isRecord(parsed) ? parsed : {};
|
|
28712
|
-
const rulesyncConfig = convertGoosePermissionConfigToRulesync(isRecord(config[GOOSE_USER_KEY]) ? config[GOOSE_USER_KEY] : {});
|
|
29190
|
+
const config = isRecord$1(parsed) ? parsed : {};
|
|
29191
|
+
const rulesyncConfig = convertGoosePermissionConfigToRulesync(isRecord$1(config[GOOSE_USER_KEY]) ? config[GOOSE_USER_KEY] : {});
|
|
28713
29192
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(rulesyncConfig, null, 2) });
|
|
28714
29193
|
}
|
|
28715
29194
|
validate() {
|
|
@@ -28783,7 +29262,7 @@ const GROKCLI_UI_KEY = "ui";
|
|
|
28783
29262
|
const GROKCLI_PERMISSION_MODE_KEY = "permission_mode";
|
|
28784
29263
|
const GROKCLI_PERMISSION_KEY = "permission";
|
|
28785
29264
|
const CATCH_ALL_PATTERN$2 = "*";
|
|
28786
|
-
const MCP_CANONICAL_PREFIX = "mcp__";
|
|
29265
|
+
const MCP_CANONICAL_PREFIX$1 = "mcp__";
|
|
28787
29266
|
const CATEGORY_TO_GROK_TOOL = {
|
|
28788
29267
|
bash: "Bash",
|
|
28789
29268
|
read: "Read",
|
|
@@ -28811,7 +29290,7 @@ const GROK_MCP_TOOL = "MCPTool";
|
|
|
28811
29290
|
* concrete pattern emits `Tool(pattern)`.
|
|
28812
29291
|
*/
|
|
28813
29292
|
function buildGrokEntry(category, pattern) {
|
|
28814
|
-
if (category.startsWith(MCP_CANONICAL_PREFIX)) {
|
|
29293
|
+
if (category.startsWith(MCP_CANONICAL_PREFIX$1)) {
|
|
28815
29294
|
const remainder = category.slice(5);
|
|
28816
29295
|
return remainder.length > 0 ? `${GROK_MCP_TOOL}(${remainder})` : GROK_MCP_TOOL;
|
|
28817
29296
|
}
|
|
@@ -28838,7 +29317,7 @@ function parseGrokEntry(entry) {
|
|
|
28838
29317
|
inner = trimmed.slice(parenIndex + 1, -1).trim();
|
|
28839
29318
|
}
|
|
28840
29319
|
if (tool === GROK_MCP_TOOL) return inner.length > 0 ? {
|
|
28841
|
-
category: `${MCP_CANONICAL_PREFIX}${inner}`,
|
|
29320
|
+
category: `${MCP_CANONICAL_PREFIX$1}${inner}`,
|
|
28842
29321
|
pattern: CATCH_ALL_PATTERN$2
|
|
28843
29322
|
} : {
|
|
28844
29323
|
category: "mcp",
|
|
@@ -28936,7 +29415,7 @@ var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
|
|
|
28936
29415
|
throw new Error(`Failed to parse existing Grok config.toml at ${filePath}: ${formatError(error)}`, { cause: error });
|
|
28937
29416
|
}
|
|
28938
29417
|
const config = rulesyncPermissions.getJson();
|
|
28939
|
-
const existingPermission = isRecord(parsed[GROKCLI_PERMISSION_KEY]) ? parsed[GROKCLI_PERMISSION_KEY] : {};
|
|
29418
|
+
const existingPermission = isRecord$1(parsed[GROKCLI_PERMISSION_KEY]) ? parsed[GROKCLI_PERMISSION_KEY] : {};
|
|
28940
29419
|
const buckets = buildGrokPermissionArrays(config, existingPermission, logger);
|
|
28941
29420
|
const permission = {
|
|
28942
29421
|
...existingPermission,
|
|
@@ -28945,7 +29424,7 @@ var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
|
|
|
28945
29424
|
ask: buckets.ask
|
|
28946
29425
|
};
|
|
28947
29426
|
const uiPatch = global ? { [GROKCLI_UI_KEY]: {
|
|
28948
|
-
...isRecord(parsed[GROKCLI_UI_KEY]) ? parsed[GROKCLI_UI_KEY] : {},
|
|
29427
|
+
...isRecord$1(parsed[GROKCLI_UI_KEY]) ? parsed[GROKCLI_UI_KEY] : {},
|
|
28949
29428
|
[GROKCLI_PERMISSION_MODE_KEY]: deriveGrokPermissionMode(config)
|
|
28950
29429
|
} } : {};
|
|
28951
29430
|
return new GrokcliPermissions({
|
|
@@ -28974,7 +29453,7 @@ var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
|
|
|
28974
29453
|
} catch (error) {
|
|
28975
29454
|
throw new Error(`Failed to parse Grok config.toml content in ${(0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
28976
29455
|
}
|
|
28977
|
-
const fineGrained = parseGrokPermissionArrays(isRecord(parsed[GROKCLI_PERMISSION_KEY]) ? parsed[GROKCLI_PERMISSION_KEY] : {});
|
|
29456
|
+
const fineGrained = parseGrokPermissionArrays(isRecord$1(parsed[GROKCLI_PERMISSION_KEY]) ? parsed[GROKCLI_PERMISSION_KEY] : {});
|
|
28978
29457
|
const rulesyncConfig = fineGrained ? { permission: fineGrained } : { permission: { bash: { [CATCH_ALL_PATTERN$2]: legacyModeAction(parsed) } } };
|
|
28979
29458
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(rulesyncConfig, null, 2) });
|
|
28980
29459
|
}
|
|
@@ -29071,7 +29550,7 @@ function parseGrokPermissionArrays(permission) {
|
|
|
29071
29550
|
* `always-approve` ⇒ `allow`; anything else (including a missing mode) ⇒ `ask`.
|
|
29072
29551
|
*/
|
|
29073
29552
|
function legacyModeAction(parsed) {
|
|
29074
|
-
return (isRecord(parsed[GROKCLI_UI_KEY]) ? parsed[GROKCLI_UI_KEY] : {})[GROKCLI_PERMISSION_MODE_KEY] === "always-approve" ? "allow" : "ask";
|
|
29553
|
+
return (isRecord$1(parsed[GROKCLI_UI_KEY]) ? parsed[GROKCLI_UI_KEY] : {})[GROKCLI_PERMISSION_MODE_KEY] === "always-approve" ? "allow" : "ask";
|
|
29075
29554
|
}
|
|
29076
29555
|
/**
|
|
29077
29556
|
* Collapse a rulesync permissions config into Grok's single coarse mode.
|
|
@@ -29127,12 +29606,12 @@ function withoutKey(record, key) {
|
|
|
29127
29606
|
return Object.fromEntries(Object.entries(record).filter(([entryKey]) => entryKey !== key));
|
|
29128
29607
|
}
|
|
29129
29608
|
function buildHermesOverride(config, provenance) {
|
|
29130
|
-
const base = isRecord(provenance.hermes) ? { ...provenance.hermes } : {};
|
|
29131
|
-
const approvalsOverride = withoutKey(isRecord(config.approvals) ? config.approvals : {}, "deny");
|
|
29609
|
+
const base = isRecord$1(provenance.hermes) ? { ...provenance.hermes } : {};
|
|
29610
|
+
const approvalsOverride = withoutKey(isRecord$1(config.approvals) ? config.approvals : {}, "deny");
|
|
29132
29611
|
if (Object.keys(approvalsOverride).length > 0) base.approvals = approvalsOverride;
|
|
29133
29612
|
else delete base.approvals;
|
|
29134
|
-
const security = isRecord(config.security) ? { ...config.security } : {};
|
|
29135
|
-
const blocklist = isRecord(security.website_blocklist) ? { ...security.website_blocklist } : void 0;
|
|
29613
|
+
const security = isRecord$1(config.security) ? { ...config.security } : {};
|
|
29614
|
+
const blocklist = isRecord$1(security.website_blocklist) ? { ...security.website_blocklist } : void 0;
|
|
29136
29615
|
if (blocklist?.enabled === true) {
|
|
29137
29616
|
delete blocklist.domains;
|
|
29138
29617
|
delete blocklist.enabled;
|
|
@@ -29141,7 +29620,7 @@ function buildHermesOverride(config, provenance) {
|
|
|
29141
29620
|
else delete security.website_blocklist;
|
|
29142
29621
|
if (Object.keys(security).length > 0) base.security = security;
|
|
29143
29622
|
else delete base.security;
|
|
29144
|
-
for (const key of ["skills", "memory"]) if (isRecord(config[key])) base[key] = config[key];
|
|
29623
|
+
for (const key of ["skills", "memory"]) if (isRecord$1(config[key])) base[key] = config[key];
|
|
29145
29624
|
else delete base[key];
|
|
29146
29625
|
return base;
|
|
29147
29626
|
}
|
|
@@ -29213,7 +29692,7 @@ var HermesagentPermissions = class HermesagentPermissions extends ToolPermission
|
|
|
29213
29692
|
format: "yaml",
|
|
29214
29693
|
fileContent: this.getFileContent()
|
|
29215
29694
|
});
|
|
29216
|
-
const permissionsRoot = isRecord(config.permissions) ? config.permissions : {};
|
|
29695
|
+
const permissionsRoot = isRecord$1(config.permissions) ? config.permissions : {};
|
|
29217
29696
|
const parsedProvenance = RulesyncPermissionsFileSchema.safeParse(permissionsRoot.rulesync);
|
|
29218
29697
|
const provenance = parsedProvenance.success ? parsedProvenance.data : { permission: {} };
|
|
29219
29698
|
const permission = clonePermissionBlock(provenance.permission);
|
|
@@ -29221,14 +29700,14 @@ var HermesagentPermissions = class HermesagentPermissions extends ToolPermission
|
|
|
29221
29700
|
permission,
|
|
29222
29701
|
commandAllowlist: isStringArray$1(config.command_allowlist) ? config.command_allowlist : []
|
|
29223
29702
|
});
|
|
29224
|
-
const approvals = isRecord(config.approvals) ? config.approvals : {};
|
|
29703
|
+
const approvals = isRecord$1(config.approvals) ? config.approvals : {};
|
|
29225
29704
|
reconcileNativeDenies({
|
|
29226
29705
|
permission,
|
|
29227
29706
|
category: "bash",
|
|
29228
29707
|
patterns: isStringArray$1(approvals.deny) ? approvals.deny : []
|
|
29229
29708
|
});
|
|
29230
|
-
const security = isRecord(config.security) ? config.security : {};
|
|
29231
|
-
const websiteBlocklist = isRecord(security.website_blocklist) ? security.website_blocklist : {};
|
|
29709
|
+
const security = isRecord$1(config.security) ? config.security : {};
|
|
29710
|
+
const websiteBlocklist = isRecord$1(security.website_blocklist) ? security.website_blocklist : {};
|
|
29232
29711
|
reconcileNativeDenies({
|
|
29233
29712
|
permission,
|
|
29234
29713
|
category: "webfetch",
|
|
@@ -29903,8 +30382,8 @@ function mergeKimiCodeToolsSection({ existingContent, patch }) {
|
|
|
29903
30382
|
existing = void 0;
|
|
29904
30383
|
}
|
|
29905
30384
|
const merged = {
|
|
29906
|
-
...isRecord(existing) ? existing : {},
|
|
29907
|
-
...isRecord(patch.tools) ? patch.tools : {}
|
|
30385
|
+
...isRecord$1(existing) ? existing : {},
|
|
30386
|
+
...isRecord$1(patch.tools) ? patch.tools : {}
|
|
29908
30387
|
};
|
|
29909
30388
|
if (Object.keys(merged).length === 0) return;
|
|
29910
30389
|
warnAboutMistypedToolLists(merged);
|
|
@@ -29928,7 +30407,7 @@ function mergeKimiCodeToolsSection({ existingContent, patch }) {
|
|
|
29928
30407
|
* @see https://moonshotai.github.io/kimi-code/en/configuration/config-files.html#tools
|
|
29929
30408
|
*/
|
|
29930
30409
|
function buildKimiCodeToolsSection(tools) {
|
|
29931
|
-
if (!isRecord(tools)) return;
|
|
30410
|
+
if (!isRecord$1(tools)) return;
|
|
29932
30411
|
const section = Object.fromEntries(Object.entries(tools).filter(([key, value]) => key === "enabled" || key === "disabled" ? isStringList(value) : true));
|
|
29933
30412
|
return Object.keys(section).length > 0 ? section : void 0;
|
|
29934
30413
|
}
|
|
@@ -30027,7 +30506,7 @@ function preserveKimiCodeRules(rules) {
|
|
|
30027
30506
|
nativeRules
|
|
30028
30507
|
};
|
|
30029
30508
|
for (const raw of rules) {
|
|
30030
|
-
if (!isRecord(raw)) continue;
|
|
30509
|
+
if (!isRecord$1(raw)) continue;
|
|
30031
30510
|
const decision = raw.decision;
|
|
30032
30511
|
const pattern = raw.pattern;
|
|
30033
30512
|
if (decision !== "allow" && decision !== "ask" && decision !== "deny" || typeof pattern !== "string") continue;
|
|
@@ -30142,7 +30621,7 @@ var KimiCodePermissions = class KimiCodePermissions extends ToolPermissions {
|
|
|
30142
30621
|
format: "toml",
|
|
30143
30622
|
fileContent: this.getFileContent()
|
|
30144
30623
|
});
|
|
30145
|
-
const { permission, nativeRules } = preserveKimiCodeRules((isRecord(config.permission) ? config.permission : {}).rules);
|
|
30624
|
+
const { permission, nativeRules } = preserveKimiCodeRules((isRecord$1(config.permission) ? config.permission : {}).rules);
|
|
30146
30625
|
const defaultPermissionMode = config.default_permission_mode;
|
|
30147
30626
|
const tools = buildKimiCodeToolsSection(config.tools);
|
|
30148
30627
|
const toolOverride = {
|
|
@@ -31419,7 +31898,7 @@ var RovodevPermissions = class RovodevPermissions extends ToolPermissions {
|
|
|
31419
31898
|
} catch (error) {
|
|
31420
31899
|
throw new Error(`Failed to parse existing Rovodev config at ${filePath}: ${formatError(error)}`, { cause: error });
|
|
31421
31900
|
}
|
|
31422
|
-
const config = isRecord(parsed) ? { ...parsed } : {};
|
|
31901
|
+
const config = isRecord$1(parsed) ? { ...parsed } : {};
|
|
31423
31902
|
const rulesyncConfig = rulesyncPermissions.getJson();
|
|
31424
31903
|
const toolPermissions = convertRulesyncToRovodevToolPermissions({
|
|
31425
31904
|
config: rulesyncConfig,
|
|
@@ -31456,8 +31935,8 @@ var RovodevPermissions = class RovodevPermissions extends ToolPermissions {
|
|
|
31456
31935
|
} catch (error) {
|
|
31457
31936
|
throw new Error(`Failed to parse Rovodev permissions content in ${(0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
31458
31937
|
}
|
|
31459
|
-
const config = isRecord(parsed) ? parsed : {};
|
|
31460
|
-
const rulesyncConfig = convertRovodevToolPermissionsToRulesync(isRecord(config.toolPermissions) ? config.toolPermissions : {});
|
|
31938
|
+
const config = isRecord$1(parsed) ? parsed : {};
|
|
31939
|
+
const rulesyncConfig = convertRovodevToolPermissionsToRulesync(isRecord$1(config.toolPermissions) ? config.toolPermissions : {});
|
|
31461
31940
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(rulesyncConfig, null, 2) });
|
|
31462
31941
|
}
|
|
31463
31942
|
validate() {
|
|
@@ -31484,14 +31963,14 @@ var RovodevPermissions = class RovodevPermissions extends ToolPermissions {
|
|
|
31484
31963
|
* keys it does not are kept as-is.
|
|
31485
31964
|
*/
|
|
31486
31965
|
function resolveToolPermissionsBlock({ existing, generated, sourceStatesRules, filePath, logger }) {
|
|
31487
|
-
const existingToolPermissions = isRecord(existing) ? { ...existing } : {};
|
|
31966
|
+
const existingToolPermissions = isRecord$1(existing) ? { ...existing } : {};
|
|
31488
31967
|
if (Object.keys(generated).length === 0 && sourceStatesRules) {
|
|
31489
|
-
if (!isRecord(existing)) return;
|
|
31968
|
+
if (!isRecord$1(existing)) return;
|
|
31490
31969
|
const strippedKeys = stripPermissiveOwnedValues(existingToolPermissions);
|
|
31491
31970
|
logger?.warn(`Rovo Dev permissions: the rulesync source produced no rule Rovo Dev can express, so the toolPermissions block in ${filePath} keeps its current levels` + (strippedKeys.length > 0 ? `, minus the grants ${strippedKeys.map((key) => `"${key}"`).join(", ")}.` : `.`));
|
|
31492
31971
|
return existingToolPermissions;
|
|
31493
31972
|
}
|
|
31494
|
-
const hasExistingToolsRecord = isRecord(existingToolPermissions.tools);
|
|
31973
|
+
const hasExistingToolsRecord = isRecord$1(existingToolPermissions.tools);
|
|
31495
31974
|
const existingTools = hasExistingToolsRecord ? { ...existingToolPermissions.tools } : {};
|
|
31496
31975
|
warnAboutDroppedOwnedKeys({
|
|
31497
31976
|
existingToolPermissions,
|
|
@@ -31535,7 +32014,7 @@ function warnAboutDroppedOwnedKeys({ existingToolPermissions, existingTools, gen
|
|
|
31535
32014
|
*/
|
|
31536
32015
|
function stripPermissiveOwnedValues(toolPermissions) {
|
|
31537
32016
|
const strippedKeys = [];
|
|
31538
|
-
if (isRecord(toolPermissions.tools)) {
|
|
32017
|
+
if (isRecord$1(toolPermissions.tools)) {
|
|
31539
32018
|
const tools = { ...toolPermissions.tools };
|
|
31540
32019
|
for (const toolKey of MANAGED_TOOL_KEYS) if (tools[toolKey] === "allow") {
|
|
31541
32020
|
delete tools[toolKey];
|
|
@@ -31557,14 +32036,14 @@ function stripPermissiveOwnedValues(toolPermissions) {
|
|
|
31557
32036
|
strippedKeys.push("default");
|
|
31558
32037
|
}
|
|
31559
32038
|
const bash = toolPermissions.bash;
|
|
31560
|
-
if (isRecord(bash)) {
|
|
32039
|
+
if (isRecord$1(bash)) {
|
|
31561
32040
|
const stripped = { ...bash };
|
|
31562
32041
|
if (stripped.default === "allow") {
|
|
31563
32042
|
delete stripped.default;
|
|
31564
32043
|
strippedKeys.push("bash.default");
|
|
31565
32044
|
}
|
|
31566
32045
|
if (Array.isArray(stripped.commands)) {
|
|
31567
|
-
const kept = stripped.commands.filter((entry) => !(isRecord(entry) && entry.permission === "allow"));
|
|
32046
|
+
const kept = stripped.commands.filter((entry) => !(isRecord$1(entry) && entry.permission === "allow"));
|
|
31568
32047
|
if (kept.length !== stripped.commands.length) strippedKeys.push("bash.commands");
|
|
31569
32048
|
if (kept.length > 0) stripped.commands = kept;
|
|
31570
32049
|
else delete stripped.commands;
|
|
@@ -31672,15 +32151,15 @@ function convertRovodevToolPermissionsToRulesync(toolPermissions) {
|
|
|
31672
32151
|
const permission = {};
|
|
31673
32152
|
if (isPermissionAction(toolPermissions.default)) permission[CATCH_ALL_PATTERN$1] = { [CATCH_ALL_PATTERN$1]: toolPermissions.default };
|
|
31674
32153
|
const bash = toolPermissions.bash;
|
|
31675
|
-
if (isRecord(bash)) {
|
|
32154
|
+
if (isRecord$1(bash)) {
|
|
31676
32155
|
const bashRules = {};
|
|
31677
32156
|
if (isPermissionAction(bash.default)) bashRules[CATCH_ALL_PATTERN$1] = bash.default;
|
|
31678
32157
|
if (Array.isArray(bash.commands)) {
|
|
31679
|
-
for (const entry of bash.commands) if (isRecord(entry) && typeof entry.command === "string" && isPermissionAction(entry.permission)) bashRules[entry.command] = entry.permission;
|
|
32158
|
+
for (const entry of bash.commands) if (isRecord$1(entry) && typeof entry.command === "string" && isPermissionAction(entry.permission)) bashRules[entry.command] = entry.permission;
|
|
31680
32159
|
}
|
|
31681
32160
|
if (Object.keys(bashRules).length > 0) permission.bash = bashRules;
|
|
31682
32161
|
}
|
|
31683
|
-
const nestedTools = isRecord(toolPermissions.tools) ? toolPermissions.tools : {};
|
|
32162
|
+
const nestedTools = isRecord$1(toolPermissions.tools) ? toolPermissions.tools : {};
|
|
31684
32163
|
const implicitLevel = isPermissionAction(toolPermissions.default) ? toolPermissions.default : "ask";
|
|
31685
32164
|
for (const category of new Set(Object.values(TOOL_KEY_TO_CATEGORY))) {
|
|
31686
32165
|
const levels = Object.entries(TOOL_KEY_TO_CATEGORY).filter(([, mapped]) => mapped === category).map(([toolKey]) => {
|
|
@@ -32395,11 +32874,11 @@ var WarpPermissions = class WarpPermissions extends ToolPermissions {
|
|
|
32395
32874
|
config,
|
|
32396
32875
|
logger
|
|
32397
32876
|
});
|
|
32398
|
-
const agents = isRecord(settings.agents) ? { ...settings.agents } : {};
|
|
32399
|
-
const profiles = isRecord(agents.profiles) ? { ...agents.profiles } : {};
|
|
32877
|
+
const agents = isRecord$1(settings.agents) ? { ...settings.agents } : {};
|
|
32878
|
+
const profiles = isRecord$1(agents.profiles) ? { ...agents.profiles } : {};
|
|
32400
32879
|
const override = config.warp;
|
|
32401
|
-
const executionProfileOverride = isRecord(override) && isRecord(override[WARP_EXECUTION_PROFILE_OVERRIDE_KEY]) ? override[WARP_EXECUTION_PROFILE_OVERRIDE_KEY] : void 0;
|
|
32402
|
-
if (isRecord(override)) {
|
|
32880
|
+
const executionProfileOverride = isRecord$1(override) && isRecord$1(override[WARP_EXECUTION_PROFILE_OVERRIDE_KEY]) ? override[WARP_EXECUTION_PROFILE_OVERRIDE_KEY] : void 0;
|
|
32881
|
+
if (isRecord$1(override)) {
|
|
32403
32882
|
const { [WARP_EXECUTION_PROFILE_OVERRIDE_KEY]: _executionProfile, ...legacyOverride } = override;
|
|
32404
32883
|
Object.assign(profiles, legacyOverride);
|
|
32405
32884
|
}
|
|
@@ -32434,10 +32913,10 @@ var WarpPermissions = class WarpPermissions extends ToolPermissions {
|
|
|
32434
32913
|
} catch (error) {
|
|
32435
32914
|
throw new Error(`Failed to parse Warp permissions content in ${(0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
32436
32915
|
}
|
|
32437
|
-
const agents = isRecord(settings.agents) ? settings.agents : {};
|
|
32438
|
-
const profiles = isRecord(agents.profiles) ? agents.profiles : {};
|
|
32439
|
-
const executionProfiles = isRecord(agents[EXECUTION_PROFILES_KEY]) ? agents[EXECUTION_PROFILES_KEY] : void 0;
|
|
32440
|
-
const defaultProfile = executionProfiles && isRecord(executionProfiles[DEFAULT_PROFILE_KEY]) ? executionProfiles[DEFAULT_PROFILE_KEY] : void 0;
|
|
32916
|
+
const agents = isRecord$1(settings.agents) ? settings.agents : {};
|
|
32917
|
+
const profiles = isRecord$1(agents.profiles) ? agents.profiles : {};
|
|
32918
|
+
const executionProfiles = isRecord$1(agents[EXECUTION_PROFILES_KEY]) ? agents[EXECUTION_PROFILES_KEY] : void 0;
|
|
32919
|
+
const defaultProfile = executionProfiles && isRecord$1(executionProfiles[DEFAULT_PROFILE_KEY]) ? executionProfiles[DEFAULT_PROFILE_KEY] : void 0;
|
|
32441
32920
|
const config = convertWarpToRulesyncPermissions({
|
|
32442
32921
|
allow: defaultProfile ? isStringArray$1(defaultProfile[PROFILE_ALLOWLIST_KEY]) ? defaultProfile[PROFILE_ALLOWLIST_KEY] : [] : isStringArray$1(profiles[ALLOWLIST_KEY]) ? profiles[ALLOWLIST_KEY] : [],
|
|
32443
32922
|
deny: defaultProfile ? isStringArray$1(defaultProfile[PROFILE_DENYLIST_KEY]) ? defaultProfile[PROFILE_DENYLIST_KEY] : [] : isStringArray$1(profiles[DENYLIST_KEY]) ? profiles[DENYLIST_KEY] : []
|
|
@@ -32484,12 +32963,12 @@ var WarpPermissions = class WarpPermissions extends ToolPermissions {
|
|
|
32484
32963
|
*/
|
|
32485
32964
|
function mergeIntoDefaultExecutionProfile({ agents, mergedAllow, mergedDeny, executionProfileOverride, logger }) {
|
|
32486
32965
|
const hasOverrideKeys = executionProfileOverride !== void 0 && Object.keys(executionProfileOverride).length > 0;
|
|
32487
|
-
if (!isRecord(agents[EXECUTION_PROFILES_KEY])) {
|
|
32966
|
+
if (!isRecord$1(agents[EXECUTION_PROFILES_KEY])) {
|
|
32488
32967
|
if (hasOverrideKeys && logger) logger.warn("The warp.execution_profile permissions override was skipped: settings.toml has no [agents.execution_profiles] collection yet (un-migrated install). Open Warp once to run its settings migration, then re-run rulesync generate.");
|
|
32489
32968
|
return;
|
|
32490
32969
|
}
|
|
32491
32970
|
const executionProfiles = { ...agents[EXECUTION_PROFILES_KEY] };
|
|
32492
|
-
const defaultProfile = isRecord(executionProfiles[DEFAULT_PROFILE_KEY]) ? { ...executionProfiles[DEFAULT_PROFILE_KEY] } : {};
|
|
32971
|
+
const defaultProfile = isRecord$1(executionProfiles[DEFAULT_PROFILE_KEY]) ? { ...executionProfiles[DEFAULT_PROFILE_KEY] } : {};
|
|
32493
32972
|
if (executionProfileOverride) Object.assign(defaultProfile, executionProfileOverride);
|
|
32494
32973
|
if (mergedAllow.length > 0) defaultProfile[PROFILE_ALLOWLIST_KEY] = mergedAllow;
|
|
32495
32974
|
else delete defaultProfile[PROFILE_ALLOWLIST_KEY];
|
|
@@ -32563,9 +33042,13 @@ const ZedToolPermissionsSchema = zod_mini.z.looseObject({
|
|
|
32563
33042
|
default: zod_mini.z.optional(ZedPermissionActionSchema),
|
|
32564
33043
|
tools: zod_mini.z.optional(zod_mini.z.record(zod_mini.z.string(), ZedToolPermissionSchema))
|
|
32565
33044
|
});
|
|
33045
|
+
/** Canonical per-tool MCP category prefix: `mcp__<server>__<tool>`. */
|
|
33046
|
+
const MCP_CANONICAL_PREFIX = "mcp__";
|
|
33047
|
+
/** Zed's per-tool MCP name prefix: `mcp:<server>:<tool>`. */
|
|
33048
|
+
const MCP_ZED_PREFIX = "mcp:";
|
|
32566
33049
|
/**
|
|
32567
33050
|
* Mapping from rulesync canonical tool category names to Zed agent tool names.
|
|
32568
|
-
* Unknown names are passed through as-is
|
|
33051
|
+
* Unknown names are passed through as-is.
|
|
32569
33052
|
*/
|
|
32570
33053
|
const CANONICAL_TO_ZED_TOOL_NAMES = {
|
|
32571
33054
|
bash: "terminal",
|
|
@@ -32600,12 +33083,68 @@ const ZED_EXCLUDED_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
|
32600
33083
|
]);
|
|
32601
33084
|
const isZedExcludedCategory = (category) => ZED_EXCLUDED_CANONICAL_CATEGORIES.has(category) || ZED_EXCLUDED_TOOL_NAMES.has(toZedToolName(category));
|
|
32602
33085
|
const ZED_TO_CANONICAL_TOOL_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_ZED_TOOL_NAMES).map(([k, v]) => [v, k]));
|
|
33086
|
+
/**
|
|
33087
|
+
* Zed addresses an MCP tool as `mcp:<server>:<tool>`, where rulesync's canonical
|
|
33088
|
+
* category is `mcp__<server>__<tool>`. Without this translation the canonical
|
|
33089
|
+
* spelling lands under a key Zed never looks up.
|
|
33090
|
+
*
|
|
33091
|
+
* Only the FIRST separator is split, matching the `cursor-permissions.ts`
|
|
33092
|
+
* precedent: upstream's `mcp_tool_id` concatenates the two names without
|
|
33093
|
+
* escaping either, so a tool called `create__issue` is legitimately
|
|
33094
|
+
* `mcp:github:create__issue`. Splitting every separator would rewrite that into
|
|
33095
|
+
* a third key on the next generate.
|
|
33096
|
+
*
|
|
33097
|
+
* @see https://zed.dev/docs/ai/tool-permissions
|
|
33098
|
+
*/
|
|
32603
33099
|
function toZedToolName(canonical) {
|
|
33100
|
+
if (canonical.startsWith(MCP_CANONICAL_PREFIX)) {
|
|
33101
|
+
const [server, ...toolParts] = canonical.slice(5).split("__");
|
|
33102
|
+
const address = toolParts.length > 0 ? `${server}:${toolParts.join("__")}` : server ?? "";
|
|
33103
|
+
return `${MCP_ZED_PREFIX}${address}`;
|
|
33104
|
+
}
|
|
32604
33105
|
return CANONICAL_TO_ZED_TOOL_NAMES[canonical] ?? canonical;
|
|
32605
33106
|
}
|
|
32606
33107
|
function toCanonicalToolName(zedName) {
|
|
33108
|
+
if (zedName.startsWith(MCP_ZED_PREFIX)) {
|
|
33109
|
+
const [server, ...toolParts] = zedName.slice(4).split(":");
|
|
33110
|
+
const address = toolParts.length > 0 ? `${server}__${toolParts.join(":")}` : server ?? "";
|
|
33111
|
+
return `${MCP_CANONICAL_PREFIX}${address}`;
|
|
33112
|
+
}
|
|
32607
33113
|
return ZED_TO_CANONICAL_TOOL_NAMES[zedName] ?? zedName;
|
|
32608
33114
|
}
|
|
33115
|
+
/**
|
|
33116
|
+
* Zed matches `always_allow`/`always_deny`/`always_confirm` regexes against the
|
|
33117
|
+
* tool's text input, and it dispatches every MCP tool with a single empty input
|
|
33118
|
+
* (`&[String::new()]`, commented upstream as "MCP tools are gated only by tool
|
|
33119
|
+
* id (no per-input pattern matching)"). The regexes still run, but against `""`,
|
|
33120
|
+
* so a pattern-scoped rule silently does something other than what its author
|
|
33121
|
+
* meant — and a non-matching `always_allow` downgrades the outcome to confirm.
|
|
33122
|
+
* Only the category's `*` rule (Zed's per-tool `default`) is therefore emitted.
|
|
33123
|
+
*/
|
|
33124
|
+
const isMcpZedToolName = (zedName) => zedName.startsWith(MCP_ZED_PREFIX);
|
|
33125
|
+
/**
|
|
33126
|
+
* Zed looks a tool up by exact key on the full `mcp:<server>:<tool>` triple, so
|
|
33127
|
+
* an address is inert unless it names both a concrete server and a concrete
|
|
33128
|
+
* tool: a missing half matches nothing, and so does a wildcard, since Zed does
|
|
33129
|
+
* no glob or prefix matching on the key.
|
|
33130
|
+
*/
|
|
33131
|
+
function isInertMcpAddress(zedToolName) {
|
|
33132
|
+
if (!isMcpZedToolName(zedToolName)) return false;
|
|
33133
|
+
const [server, ...toolParts] = zedToolName.slice(4).split(":");
|
|
33134
|
+
const tool = toolParts.join(":");
|
|
33135
|
+
return !server || server === "*" || !tool || tool === "*";
|
|
33136
|
+
}
|
|
33137
|
+
/**
|
|
33138
|
+
* Strip pattern-scoped rules from an MCP category, warning once about the ones
|
|
33139
|
+
* dropped. Non-MCP categories are returned untouched.
|
|
33140
|
+
*/
|
|
33141
|
+
function withoutInertMcpPatterns({ category, zedToolName, rules, logger }) {
|
|
33142
|
+
if (!isMcpZedToolName(zedToolName)) return rules;
|
|
33143
|
+
const scopedPatterns = Object.keys(rules).filter((pattern) => pattern !== "*");
|
|
33144
|
+
if (scopedPatterns.length === 0) return rules;
|
|
33145
|
+
logger?.warn(`Zed permissions: dropping the pattern-scoped rule(s) ${scopedPatterns.map((pattern) => `"${pattern}"`).join(", ")} in the "${category}" category — Zed dispatches an MCP tool with an empty input, so a permission pattern is matched against "" rather than against anything meaningful. Only the catch-all "*" rule is emitted, as the tool's default.`);
|
|
33146
|
+
return Object.fromEntries(Object.entries(rules).filter(([pattern]) => pattern === "*"));
|
|
33147
|
+
}
|
|
32609
33148
|
const CANONICAL_TO_ZED_ACTION = {
|
|
32610
33149
|
allow: "allow",
|
|
32611
33150
|
ask: "confirm",
|
|
@@ -32648,6 +33187,53 @@ function buildZedToolPermission(rules) {
|
|
|
32648
33187
|
if (alwaysConfirm.length > 0) tool.always_confirm = alwaysConfirm;
|
|
32649
33188
|
return Object.keys(tool).length > 0 ? tool : null;
|
|
32650
33189
|
}
|
|
33190
|
+
/**
|
|
33191
|
+
* Split a canonical permission block into the Zed shapes it maps onto, plus the
|
|
33192
|
+
* categories the caller should report as dropped.
|
|
33193
|
+
*
|
|
33194
|
+
* The canonical `*` category is the all-tools catch-all. Zed's counterpart is
|
|
33195
|
+
* `agent.tool_permissions.default` (rung 6 of its precedence ladder), not a
|
|
33196
|
+
* `tools["*"]` entry — `*` is not a Zed tool name, so writing one produces a
|
|
33197
|
+
* rule Zed silently ignores. Only the category's own `*` pattern can be
|
|
33198
|
+
* expressed there: Zed's global default carries no pattern list, so
|
|
33199
|
+
* pattern-scoped rules in the `*` category are dropped with a warning instead of
|
|
33200
|
+
* being emitted as inert config.
|
|
33201
|
+
*/
|
|
33202
|
+
function buildZedToolPermissions({ permission, logger }) {
|
|
33203
|
+
let managedDefault;
|
|
33204
|
+
const managedTools = {};
|
|
33205
|
+
const excludedCategories = [];
|
|
33206
|
+
const inertMcpCategories = [];
|
|
33207
|
+
for (const [category, rules] of Object.entries(permission)) {
|
|
33208
|
+
if (category === "*") {
|
|
33209
|
+
for (const [pattern, action] of Object.entries(rules)) if (pattern === "*") managedDefault = CANONICAL_TO_ZED_ACTION[action];
|
|
33210
|
+
else logger?.warn(`Zed permissions: dropping the "*" category rule for pattern "${pattern}" — Zed's global tool-permission default takes no patterns; scope the rule to a tool category instead.`);
|
|
33211
|
+
continue;
|
|
33212
|
+
}
|
|
33213
|
+
if (isZedExcludedCategory(category)) {
|
|
33214
|
+
if (Object.values(rules).some((action) => action === "deny" || action === "ask")) excludedCategories.push(category);
|
|
33215
|
+
continue;
|
|
33216
|
+
}
|
|
33217
|
+
const zedToolName = toZedToolName(category);
|
|
33218
|
+
if (isInertMcpAddress(zedToolName)) {
|
|
33219
|
+
inertMcpCategories.push(category);
|
|
33220
|
+
continue;
|
|
33221
|
+
}
|
|
33222
|
+
const tool = buildZedToolPermission(withoutInertMcpPatterns({
|
|
33223
|
+
category,
|
|
33224
|
+
zedToolName,
|
|
33225
|
+
rules,
|
|
33226
|
+
logger
|
|
33227
|
+
}));
|
|
33228
|
+
if (tool) managedTools[zedToolName] = tool;
|
|
33229
|
+
}
|
|
33230
|
+
return {
|
|
33231
|
+
managedDefault,
|
|
33232
|
+
managedTools,
|
|
33233
|
+
excludedCategories,
|
|
33234
|
+
inertMcpCategories
|
|
33235
|
+
};
|
|
33236
|
+
}
|
|
32651
33237
|
function asRecord(value) {
|
|
32652
33238
|
if (value === null || typeof value !== "object" || Array.isArray(value)) return {};
|
|
32653
33239
|
return Object.fromEntries(Object.entries(value));
|
|
@@ -32718,25 +33304,15 @@ var ZedPermissions = class ZedPermissions extends ToolPermissions {
|
|
|
32718
33304
|
const agent = asRecord(settings.agent);
|
|
32719
33305
|
const toolPermissions = asRecord(agent.tool_permissions);
|
|
32720
33306
|
const existingTools = asRecord(toolPermissions.tools);
|
|
32721
|
-
|
|
32722
|
-
|
|
32723
|
-
|
|
32724
|
-
|
|
32725
|
-
if (category === "*") {
|
|
32726
|
-
for (const [pattern, action] of Object.entries(rules)) if (pattern === "*") managedDefault = CANONICAL_TO_ZED_ACTION[action];
|
|
32727
|
-
else logger?.warn(`Zed permissions: dropping the "*" category rule for pattern "${pattern}" — Zed's global tool-permission default takes no patterns; scope the rule to a tool category instead.`);
|
|
32728
|
-
continue;
|
|
32729
|
-
}
|
|
32730
|
-
if (isZedExcludedCategory(category)) {
|
|
32731
|
-
if (Object.values(rules).some((action) => action === "deny" || action === "ask")) excludedCategories.push(category);
|
|
32732
|
-
continue;
|
|
32733
|
-
}
|
|
32734
|
-
const tool = buildZedToolPermission(rules);
|
|
32735
|
-
if (tool) managedTools[toZedToolName(category)] = tool;
|
|
32736
|
-
}
|
|
33307
|
+
const { managedDefault, managedTools, excludedCategories, inertMcpCategories } = buildZedToolPermissions({
|
|
33308
|
+
permission: config.permission,
|
|
33309
|
+
logger
|
|
33310
|
+
});
|
|
32737
33311
|
if (excludedCategories.length > 0) logger?.warn(`Zed permissions: dropping the ${excludedCategories.map((category) => `"${category}"`).join(", ")} ${excludedCategories.length === 1 ? "category" : "categories"} — Zed does not gate its read-only tools, so the entries would never be consulted. Zed's read-denial surface is \`private_files\`, which the ignore feature writes from \`.rulesync/.aiignore\`.`);
|
|
33312
|
+
if (inertMcpCategories.length > 0) logger?.warn(`Zed permissions: dropping the ${inertMcpCategories.map((category) => `"${category}"`).join(", ")} ${inertMcpCategories.length === 1 ? "category" : "categories"} — Zed looks an MCP tool up by exact key on the full \`mcp:<server>:<tool>\` triple, so an address that omits or wildcards either half matches nothing. Name the individual server and tool instead.`);
|
|
32738
33313
|
const managedToolNames = new Set(Object.keys(managedTools));
|
|
32739
33314
|
if ("*" in config.permission) managedToolNames.add("*");
|
|
33315
|
+
for (const toolName of Object.keys(existingTools)) if (toolName.startsWith(MCP_CANONICAL_PREFIX)) managedToolNames.add(toolName);
|
|
32740
33316
|
const preservedTools = Object.fromEntries(Object.entries(existingTools).filter(([toolName]) => !managedToolNames.has(toolName)));
|
|
32741
33317
|
return new ZedPermissions({
|
|
32742
33318
|
outputRoot,
|
|
@@ -34937,18 +35513,10 @@ var CopilotSkill = class CopilotSkill extends ToolSkill {
|
|
|
34937
35513
|
};
|
|
34938
35514
|
}
|
|
34939
35515
|
toRulesyncSkill() {
|
|
34940
|
-
const
|
|
34941
|
-
const copilotSection = {
|
|
34942
|
-
...frontmatter.license !== void 0 && { license: frontmatter.license },
|
|
34943
|
-
...frontmatter["allowed-tools"] !== void 0 && { "allowed-tools": frontmatter["allowed-tools"] },
|
|
34944
|
-
...frontmatter["argument-hint"] !== void 0 && { "argument-hint": frontmatter["argument-hint"] },
|
|
34945
|
-
...frontmatter["user-invocable"] !== void 0 && { "user-invocable": frontmatter["user-invocable"] },
|
|
34946
|
-
...frontmatter["disable-model-invocation"] !== void 0 && { "disable-model-invocation": frontmatter["disable-model-invocation"] },
|
|
34947
|
-
...frontmatter.context !== void 0 && { context: frontmatter.context }
|
|
34948
|
-
};
|
|
35516
|
+
const { name, description, ...copilotSection } = this.getFrontmatter();
|
|
34949
35517
|
const rulesyncFrontmatter = {
|
|
34950
|
-
name
|
|
34951
|
-
description
|
|
35518
|
+
name,
|
|
35519
|
+
description,
|
|
34952
35520
|
targets: ["*"],
|
|
34953
35521
|
...Object.keys(copilotSection).length > 0 && { copilot: copilotSection }
|
|
34954
35522
|
};
|
|
@@ -34975,15 +35543,13 @@ var CopilotSkill = class CopilotSkill extends ToolSkill {
|
|
|
34975
35543
|
rootFrontmatter: rulesyncFrontmatter,
|
|
34976
35544
|
section: copilotSection
|
|
34977
35545
|
});
|
|
35546
|
+
const { "user-invocable": _userInvocable, "disable-model-invocation": _disableModelInvocation, ...copilotFields } = copilotSection ?? {};
|
|
34978
35547
|
const copilotFrontmatter = {
|
|
35548
|
+
...copilotFields,
|
|
34979
35549
|
name: rulesyncFrontmatter.name,
|
|
34980
35550
|
description: rulesyncFrontmatter.description,
|
|
34981
|
-
...copilotSection?.license !== void 0 && { license: copilotSection.license },
|
|
34982
|
-
...copilotSection?.["allowed-tools"] !== void 0 && { "allowed-tools": copilotSection["allowed-tools"] },
|
|
34983
|
-
...copilotSection?.["argument-hint"] !== void 0 && { "argument-hint": copilotSection["argument-hint"] },
|
|
34984
35551
|
...resolvedUserInvocable !== void 0 && { "user-invocable": resolvedUserInvocable },
|
|
34985
|
-
...resolvedDisableModelInvocation !== void 0 && { "disable-model-invocation": resolvedDisableModelInvocation }
|
|
34986
|
-
...copilotSection?.context !== void 0 && { context: copilotSection.context }
|
|
35552
|
+
...resolvedDisableModelInvocation !== void 0 && { "disable-model-invocation": resolvedDisableModelInvocation }
|
|
34987
35553
|
};
|
|
34988
35554
|
return new CopilotSkill({
|
|
34989
35555
|
outputRoot,
|
|
@@ -35102,17 +35668,10 @@ var CopilotcliSkill = class CopilotcliSkill extends ToolSkill {
|
|
|
35102
35668
|
};
|
|
35103
35669
|
}
|
|
35104
35670
|
toRulesyncSkill() {
|
|
35105
|
-
const
|
|
35106
|
-
const copilotcliSection = {
|
|
35107
|
-
...frontmatter.license !== void 0 && { license: frontmatter.license },
|
|
35108
|
-
...frontmatter["allowed-tools"] !== void 0 && { "allowed-tools": frontmatter["allowed-tools"] },
|
|
35109
|
-
...frontmatter["argument-hint"] !== void 0 && { "argument-hint": frontmatter["argument-hint"] },
|
|
35110
|
-
...frontmatter["user-invocable"] !== void 0 && { "user-invocable": frontmatter["user-invocable"] },
|
|
35111
|
-
...frontmatter["disable-model-invocation"] !== void 0 && { "disable-model-invocation": frontmatter["disable-model-invocation"] }
|
|
35112
|
-
};
|
|
35671
|
+
const { name, description, ...copilotcliSection } = this.getFrontmatter();
|
|
35113
35672
|
const rulesyncFrontmatter = {
|
|
35114
|
-
name
|
|
35115
|
-
description
|
|
35673
|
+
name,
|
|
35674
|
+
description,
|
|
35116
35675
|
targets: ["*"],
|
|
35117
35676
|
...Object.keys(copilotcliSection).length > 0 && { copilotcli: copilotcliSection }
|
|
35118
35677
|
};
|
|
@@ -35139,12 +35698,11 @@ var CopilotcliSkill = class CopilotcliSkill extends ToolSkill {
|
|
|
35139
35698
|
rootFrontmatter: rulesyncFrontmatter,
|
|
35140
35699
|
section: copilotcliSection
|
|
35141
35700
|
});
|
|
35701
|
+
const { "user-invocable": _userInvocable, "disable-model-invocation": _disableModelInvocation, ...copilotcliFields } = copilotcliSection ?? {};
|
|
35142
35702
|
const copilotcliFrontmatter = {
|
|
35703
|
+
...copilotcliFields,
|
|
35143
35704
|
name: rulesyncFrontmatter.name,
|
|
35144
35705
|
description: rulesyncFrontmatter.description,
|
|
35145
|
-
...copilotcliSection?.license !== void 0 && { license: copilotcliSection.license },
|
|
35146
|
-
...copilotcliSection?.["allowed-tools"] !== void 0 && { "allowed-tools": copilotcliSection["allowed-tools"] },
|
|
35147
|
-
...copilotcliSection?.["argument-hint"] !== void 0 && { "argument-hint": copilotcliSection["argument-hint"] },
|
|
35148
35706
|
...resolvedUserInvocable !== void 0 && { "user-invocable": resolvedUserInvocable },
|
|
35149
35707
|
...resolvedDisableModelInvocation !== void 0 && { "disable-model-invocation": resolvedDisableModelInvocation }
|
|
35150
35708
|
};
|
|
@@ -36618,7 +37176,10 @@ var KimiCodeSkill = class KimiCodeSkill extends ToolSkill {
|
|
|
36618
37176
|
//#region src/features/skills/kiro-skill.ts
|
|
36619
37177
|
const KiroSkillFrontmatterSchema = zod_mini.z.looseObject({
|
|
36620
37178
|
name: zod_mini.z.string(),
|
|
36621
|
-
description: zod_mini.z.string()
|
|
37179
|
+
description: zod_mini.z.string(),
|
|
37180
|
+
license: zod_mini.z.optional(zod_mini.z.string()),
|
|
37181
|
+
compatibility: zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.looseObject({})])),
|
|
37182
|
+
metadata: zod_mini.z.optional(zod_mini.z.looseObject({}))
|
|
36622
37183
|
});
|
|
36623
37184
|
/**
|
|
36624
37185
|
* Represents a Kiro skill directory.
|
|
@@ -36672,11 +37233,12 @@ var KiroSkill = class KiroSkill extends ToolSkill {
|
|
|
36672
37233
|
};
|
|
36673
37234
|
}
|
|
36674
37235
|
toRulesyncSkill() {
|
|
36675
|
-
const
|
|
37236
|
+
const { name, description, ...kiroSection } = this.getFrontmatter();
|
|
36676
37237
|
const rulesyncFrontmatter = {
|
|
36677
|
-
name
|
|
36678
|
-
description
|
|
36679
|
-
targets: ["*"]
|
|
37238
|
+
name,
|
|
37239
|
+
description,
|
|
37240
|
+
targets: ["*"],
|
|
37241
|
+
...Object.keys(kiroSection).length > 0 && { kiro: kiroSection }
|
|
36680
37242
|
};
|
|
36681
37243
|
return new RulesyncSkill({
|
|
36682
37244
|
outputRoot: this.outputRoot,
|
|
@@ -36693,6 +37255,7 @@ var KiroSkill = class KiroSkill extends ToolSkill {
|
|
|
36693
37255
|
const settablePaths = KiroSkill.getSettablePaths({ global });
|
|
36694
37256
|
const rulesyncFrontmatter = rulesyncSkill.getFrontmatter();
|
|
36695
37257
|
const kiroFrontmatter = {
|
|
37258
|
+
...rulesyncFrontmatter.kiro,
|
|
36696
37259
|
name: rulesyncFrontmatter.name,
|
|
36697
37260
|
description: rulesyncFrontmatter.description
|
|
36698
37261
|
};
|
|
@@ -43261,14 +43824,14 @@ var ZoocodeSubagent = class extends RooSubagent {
|
|
|
43261
43824
|
static toRooMode(rulesyncSubagent) {
|
|
43262
43825
|
const mode = super.toRooMode(rulesyncSubagent);
|
|
43263
43826
|
const frontmatter = rulesyncSubagent.getFrontmatter();
|
|
43264
|
-
const zoocodeSection = isRecord(frontmatter.zoocode) ? frontmatter.zoocode : {};
|
|
43827
|
+
const zoocodeSection = isRecord$1(frontmatter.zoocode) ? frontmatter.zoocode : {};
|
|
43265
43828
|
if (isStringArray$1(zoocodeSection.allowedMcpServers)) mode.allowedMcpServers = zoocodeSection.allowedMcpServers;
|
|
43266
43829
|
return mode;
|
|
43267
43830
|
}
|
|
43268
43831
|
toRulesyncSubagents() {
|
|
43269
43832
|
return super.toRulesyncSubagents().map((subagent) => {
|
|
43270
43833
|
const frontmatter = subagent.getFrontmatter();
|
|
43271
|
-
const { allowedMcpServers, ...restRooSection } = isRecord(frontmatter.roo) ? { ...frontmatter.roo } : {};
|
|
43834
|
+
const { allowedMcpServers, ...restRooSection } = isRecord$1(frontmatter.roo) ? { ...frontmatter.roo } : {};
|
|
43272
43835
|
const rebuilt = {
|
|
43273
43836
|
...frontmatter,
|
|
43274
43837
|
targets: ["zoocode"],
|