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
|
@@ -1842,7 +1842,7 @@ var RulesyncFile = class extends AiFile {
|
|
|
1842
1842
|
* Type guard to check if a value is a plain object (Record<string, unknown>).
|
|
1843
1843
|
* This excludes arrays and null values.
|
|
1844
1844
|
*/
|
|
1845
|
-
function isRecord(value) {
|
|
1845
|
+
function isRecord$1(value) {
|
|
1846
1846
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1847
1847
|
}
|
|
1848
1848
|
/**
|
|
@@ -1857,7 +1857,7 @@ function isRecord(value) {
|
|
|
1857
1857
|
* malicious accessor descriptors.
|
|
1858
1858
|
*/
|
|
1859
1859
|
function isPlainObject$1(value) {
|
|
1860
|
-
if (!isRecord(value)) return false;
|
|
1860
|
+
if (!isRecord$1(value)) return false;
|
|
1861
1861
|
const proto = Object.getPrototypeOf(value);
|
|
1862
1862
|
return proto === null || proto === Object.prototype;
|
|
1863
1863
|
}
|
|
@@ -2163,6 +2163,7 @@ const HookDefinitionSchema = z.looseObject({
|
|
|
2163
2163
|
timeout: z.optional(z.number()),
|
|
2164
2164
|
cacheTtl: z.optional(z.number().check(nonnegative())),
|
|
2165
2165
|
matcher: z.optional(safeString),
|
|
2166
|
+
enabled: z.optional(z.boolean()),
|
|
2166
2167
|
prompt: z.optional(safeString),
|
|
2167
2168
|
loop_limit: z.optional(z.nullable(z.number())),
|
|
2168
2169
|
name: z.optional(safeString),
|
|
@@ -2481,8 +2482,9 @@ const FACTORYDROID_HOOK_EVENTS = [
|
|
|
2481
2482
|
/**
|
|
2482
2483
|
* Hook events supported by deepagents-cli (`deepagents-code` / `dcode`).
|
|
2483
2484
|
*
|
|
2484
|
-
*
|
|
2485
|
-
*
|
|
2485
|
+
* These are the twelve Hooks v2 `HookEvent` members, GA since deepagents-code
|
|
2486
|
+
* 0.1.52. Canonical `contextOffload` is deliberately absent — see
|
|
2487
|
+
* {@link CANONICAL_TO_DEEPAGENTS_EVENT_NAMES}.
|
|
2486
2488
|
* https://docs.langchain.com/oss/python/deepagents/cli/configuration
|
|
2487
2489
|
*/
|
|
2488
2490
|
const DEEPAGENTS_HOOK_EVENTS = [
|
|
@@ -2495,8 +2497,9 @@ const DEEPAGENTS_HOOK_EVENTS = [
|
|
|
2495
2497
|
"postToolUseFailure",
|
|
2496
2498
|
"stop",
|
|
2497
2499
|
"preCompact",
|
|
2498
|
-
"
|
|
2499
|
-
"
|
|
2500
|
+
"notification",
|
|
2501
|
+
"subagentStart",
|
|
2502
|
+
"subagentStop"
|
|
2500
2503
|
];
|
|
2501
2504
|
/** Hook events supported by Codex CLI. */
|
|
2502
2505
|
const CODEXCLI_HOOK_EVENTS = [
|
|
@@ -3184,26 +3187,59 @@ const CANONICAL_TO_GOOSE_EVENT_NAMES = {
|
|
|
3184
3187
|
*/
|
|
3185
3188
|
const GOOSE_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_GOOSE_EVENT_NAMES).map(([k, v]) => [v, k]));
|
|
3186
3189
|
/**
|
|
3187
|
-
* Map canonical camelCase event names to deepagents-cli
|
|
3190
|
+
* Map canonical camelCase event names to the deepagents-cli Hooks v2
|
|
3191
|
+
* `HookEvent` values.
|
|
3192
|
+
*
|
|
3193
|
+
* Hooks v2 went GA in deepagents-code 0.1.52 (2026-08-04) and replaced the
|
|
3194
|
+
* legacy dot-notation names (`session.start`, `tool.use`, …) with these twelve
|
|
3195
|
+
* PascalCase members. The legacy list format is still read, but is scheduled for
|
|
3196
|
+
* removal on 2026-09-01 (`_LEGACY_HOOKS_REMOVAL_DATE` in `hooks/loading.py`).
|
|
3197
|
+
*
|
|
3198
|
+
* Canonical `contextOffload` has no v2 counterpart. Its legacy event
|
|
3199
|
+
* (`context.offload`) is gone, and folding it onto `PreCompact` would silently
|
|
3200
|
+
* merge two distinct canonical events into one — so it is dropped for
|
|
3201
|
+
* deepagents instead, and reported by the hooks processor as an unsupported
|
|
3202
|
+
* event like any other.
|
|
3203
|
+
*
|
|
3204
|
+
* @see https://github.com/langchain-ai/deepagents `libs/code/deepagents_code/hooks/models/domain.py`
|
|
3188
3205
|
*/
|
|
3189
3206
|
const CANONICAL_TO_DEEPAGENTS_EVENT_NAMES = {
|
|
3190
|
-
sessionStart: "
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
permissionRequest: "
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
preCompact: "
|
|
3199
|
-
|
|
3200
|
-
|
|
3207
|
+
sessionStart: "SessionStart",
|
|
3208
|
+
beforeSubmitPrompt: "UserPromptSubmit",
|
|
3209
|
+
sessionEnd: "SessionEnd",
|
|
3210
|
+
permissionRequest: "PermissionRequest",
|
|
3211
|
+
notification: "Notification",
|
|
3212
|
+
preToolUse: "PreToolUse",
|
|
3213
|
+
postToolUse: "PostToolUse",
|
|
3214
|
+
postToolUseFailure: "PostToolUseFailure",
|
|
3215
|
+
preCompact: "PreCompact",
|
|
3216
|
+
stop: "Stop",
|
|
3217
|
+
subagentStart: "SubagentStart",
|
|
3218
|
+
subagentStop: "SubagentStop"
|
|
3201
3219
|
};
|
|
3202
3220
|
/**
|
|
3203
|
-
* Map deepagents-cli
|
|
3221
|
+
* Map deepagents-cli `HookEvent` values to canonical camelCase.
|
|
3204
3222
|
*/
|
|
3205
3223
|
const DEEPAGENTS_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_DEEPAGENTS_EVENT_NAMES).map(([k, v]) => [v, k]));
|
|
3206
3224
|
/**
|
|
3225
|
+
* The legacy dot-notation event names deepagents-cli used before Hooks v2.
|
|
3226
|
+
* Kept for the read-only import path so a `hooks.json` still in the old format
|
|
3227
|
+
* round-trips into canonical events instead of being silently discarded.
|
|
3228
|
+
*/
|
|
3229
|
+
const DEEPAGENTS_LEGACY_TO_CANONICAL_EVENT_NAMES = {
|
|
3230
|
+
"session.start": "sessionStart",
|
|
3231
|
+
"session.end": "sessionEnd",
|
|
3232
|
+
"user.prompt": "beforeSubmitPrompt",
|
|
3233
|
+
"permission.request": "permissionRequest",
|
|
3234
|
+
"tool.use": "preToolUse",
|
|
3235
|
+
"tool.result": "postToolUse",
|
|
3236
|
+
"tool.error": "postToolUseFailure",
|
|
3237
|
+
"task.complete": "stop",
|
|
3238
|
+
"context.compact": "preCompact",
|
|
3239
|
+
"context.offload": "contextOffload",
|
|
3240
|
+
"input.required": "notification"
|
|
3241
|
+
};
|
|
3242
|
+
/**
|
|
3207
3243
|
* Map canonical camelCase event names to Kiro CLI camelCase.
|
|
3208
3244
|
* Kiro CLI uses its own event naming: agentSpawn, userPromptSubmit, preToolUse,
|
|
3209
3245
|
* postToolUse, stop. Both `sessionEnd` and `stop` canonical events map to
|
|
@@ -3821,7 +3857,7 @@ var RulesyncMcp = class RulesyncMcp extends RulesyncFile {
|
|
|
3821
3857
|
logger
|
|
3822
3858
|
});
|
|
3823
3859
|
for (const ignoredKey of MCP_IGNORED_ALIAS_SOURCE_KEYS) {
|
|
3824
|
-
if (!isRecord(json[ignoredKey])) continue;
|
|
3860
|
+
if (!isRecord$1(json[ignoredKey])) continue;
|
|
3825
3861
|
this.warnOncePerFile(`alias:${ignoredKey}`, `The "${ignoredKey}" block in ${join(this.relativeDirPath, this.relativeFilePath)} is ignored. Author it under the "${MCP_BLOCK_KEY_ALIASES[ignoredKey]}" key instead.`, logger);
|
|
3826
3862
|
}
|
|
3827
3863
|
const toolBlockKeys = Object.keys(json).filter((key) => MCP_TOOL_BLOCK_KEYS.has(key));
|
|
@@ -3833,7 +3869,7 @@ var RulesyncMcp = class RulesyncMcp extends RulesyncFile {
|
|
|
3833
3869
|
}));
|
|
3834
3870
|
for (const blockKey of blockKeys) {
|
|
3835
3871
|
const toolBlock = json[blockKey];
|
|
3836
|
-
const toolServers = isRecord(toolBlock) && isRecord(toolBlock.mcpServers) ? toolBlock.mcpServers : void 0;
|
|
3872
|
+
const toolServers = isRecord$1(toolBlock) && isRecord$1(toolBlock.mcpServers) ? toolBlock.mcpServers : void 0;
|
|
3837
3873
|
for (const [serverName, serverConfig] of Object.entries(toolServers ?? {})) {
|
|
3838
3874
|
if (isPrototypePollutionKey(serverName)) continue;
|
|
3839
3875
|
if (serverConfig === null) delete effectiveServers[serverName];
|
|
@@ -4850,9 +4886,9 @@ var RulesyncPermissions = class RulesyncPermissions extends RulesyncFile {
|
|
|
4850
4886
|
if (NATIVE_PERMISSION_OVERRIDE_TARGETS.has(toolTarget)) return this;
|
|
4851
4887
|
const overrideKey = PERMISSION_OVERRIDE_KEY_ALIASES[toolTarget] ?? toolTarget;
|
|
4852
4888
|
const json = this.json;
|
|
4853
|
-
if (overrideKey !== toolTarget && isRecord(json[toolTarget])) logger?.warn(`The "${toolTarget}" block in ${join(this.relativeDirPath, this.relativeFilePath)} is ignored. Author it under the "${overrideKey}" key instead (the ${toolTarget} target reads that block).`);
|
|
4889
|
+
if (overrideKey !== toolTarget && isRecord$1(json[toolTarget])) logger?.warn(`The "${toolTarget}" block in ${join(this.relativeDirPath, this.relativeFilePath)} is ignored. Author it under the "${overrideKey}" key instead (the ${toolTarget} target reads that block).`);
|
|
4854
4890
|
const overrideBlock = json[overrideKey];
|
|
4855
|
-
if (!isRecord(overrideBlock) || !isRecord(overrideBlock.permission)) return this;
|
|
4891
|
+
if (!isRecord$1(overrideBlock) || !isRecord$1(overrideBlock.permission)) return this;
|
|
4856
4892
|
const { permission: toolScopedPermission, ...restOverride } = overrideBlock;
|
|
4857
4893
|
const merged = {
|
|
4858
4894
|
...json,
|
|
@@ -5162,6 +5198,11 @@ const RulesyncSkillFrontmatterSchema = z.looseObject({
|
|
|
5162
5198
|
compatibility: z.optional(z.looseObject({})),
|
|
5163
5199
|
metadata: z.optional(z.looseObject({}))
|
|
5164
5200
|
})),
|
|
5201
|
+
kiro: z.optional(z.looseObject({
|
|
5202
|
+
license: z.optional(z.string()),
|
|
5203
|
+
compatibility: z.optional(z.union([z.string(), z.looseObject({})])),
|
|
5204
|
+
metadata: z.optional(z.looseObject({}))
|
|
5205
|
+
})),
|
|
5165
5206
|
deepagents: z.optional(z.looseObject({
|
|
5166
5207
|
"allowed-tools": z.optional(z.array(z.string())),
|
|
5167
5208
|
license: z.optional(z.string()),
|
|
@@ -8256,7 +8297,7 @@ var AntigravitySharedCommand = class extends ToolCommand {
|
|
|
8256
8297
|
}
|
|
8257
8298
|
static extractAntigravityConfig(rulesyncCommand) {
|
|
8258
8299
|
const antigravity = rulesyncCommand.getFrontmatter().antigravity;
|
|
8259
|
-
return isRecord(antigravity) ? antigravity : void 0;
|
|
8300
|
+
return isRecord$1(antigravity) ? antigravity : void 0;
|
|
8260
8301
|
}
|
|
8261
8302
|
static resolveTrigger(rulesyncCommand, antigravityConfig) {
|
|
8262
8303
|
const rulesyncFrontmatter = rulesyncCommand.getFrontmatter();
|
|
@@ -12046,8 +12087,8 @@ async function lookupPromptDescription({ outputRoot, relativeFilePath, name }) {
|
|
|
12046
12087
|
}
|
|
12047
12088
|
if (!isPlainObject$1(parsed) || !Array.isArray(parsed.prompts)) return "";
|
|
12048
12089
|
const expectedContentFile = toPosixPath(join("prompts", relativeFilePath));
|
|
12049
|
-
const entry = parsed.prompts.find((candidate) => isRecord(candidate) && (candidate.content_file === expectedContentFile || candidate.name === name));
|
|
12050
|
-
return isRecord(entry) && typeof entry.description === "string" ? entry.description : "";
|
|
12090
|
+
const entry = parsed.prompts.find((candidate) => isRecord$1(candidate) && (candidate.content_file === expectedContentFile || candidate.name === name));
|
|
12091
|
+
return isRecord$1(entry) && typeof entry.description === "string" ? entry.description : "";
|
|
12051
12092
|
}
|
|
12052
12093
|
/**
|
|
12053
12094
|
* The shared `.rovodev/prompts.yml` manifest that indexes every saved prompt.
|
|
@@ -13160,97 +13201,140 @@ function applyCommandPrefix({ def, converterConfig }) {
|
|
|
13160
13201
|
return `"${converterConfig.projectDirVar}"/${relativeCommand}`;
|
|
13161
13202
|
}
|
|
13162
13203
|
/**
|
|
13163
|
-
*
|
|
13164
|
-
*
|
|
13165
|
-
*
|
|
13166
|
-
*/
|
|
13167
|
-
function emitBooleanPassthroughFields({ def, hookType, converterConfig }) {
|
|
13168
|
-
return Object.fromEntries((converterConfig.booleanPassthroughFields ?? []).filter(({ canonical, commandOnly }) => {
|
|
13169
|
-
if (commandOnly === true && hookType !== "command") return false;
|
|
13170
|
-
return typeof def[canonical] === "boolean";
|
|
13171
|
-
}).map(({ canonical, tool }) => [tool, def[canonical]]));
|
|
13172
|
-
}
|
|
13173
|
-
/**
|
|
13174
|
-
* Import the configured boolean passthrough fields back into canonical fields,
|
|
13175
|
-
* reversing {@link emitBooleanPassthroughFields}. Only boolean values are read.
|
|
13176
|
-
*/
|
|
13177
|
-
function importBooleanPassthroughFields({ h, converterConfig }) {
|
|
13178
|
-
return Object.fromEntries((converterConfig.booleanPassthroughFields ?? []).filter(({ tool }) => typeof h[tool] === "boolean").map(({ canonical, tool }) => [canonical, h[tool]]));
|
|
13179
|
-
}
|
|
13180
|
-
/**
|
|
13181
|
-
* Emit the configured number passthrough fields on the tool side, mapping each
|
|
13182
|
-
* canonical field name to its (possibly renamed) tool field name. Only finite
|
|
13183
|
-
* numbers are carried through.
|
|
13184
|
-
*/
|
|
13185
|
-
function emitNumberPassthroughFields({ def, hookType, converterConfig }) {
|
|
13186
|
-
return Object.fromEntries((converterConfig.numberPassthroughFields ?? []).filter(({ canonical, commandOnly }) => {
|
|
13187
|
-
if (commandOnly === true && hookType !== "command") return false;
|
|
13188
|
-
return Number.isFinite(def[canonical]);
|
|
13189
|
-
}).map(({ canonical, tool }) => [tool, def[canonical]]));
|
|
13190
|
-
}
|
|
13191
|
-
/**
|
|
13192
|
-
* Import the configured number passthrough fields back into canonical fields,
|
|
13193
|
-
* reversing {@link emitNumberPassthroughFields}. Only finite numbers are read.
|
|
13194
|
-
*/
|
|
13195
|
-
function importNumberPassthroughFields({ h, converterConfig }) {
|
|
13196
|
-
return Object.fromEntries((converterConfig.numberPassthroughFields ?? []).filter(({ tool }) => Number.isFinite(h[tool])).map(({ canonical, tool }) => [canonical, h[tool]]));
|
|
13197
|
-
}
|
|
13198
|
-
/**
|
|
13199
|
-
* Emit the configured string passthrough fields on the tool side, mapping each
|
|
13200
|
-
* canonical field name to its (possibly renamed) tool field name. Only non-empty
|
|
13201
|
-
* string values are carried through.
|
|
13202
|
-
*/
|
|
13203
|
-
function emitStringPassthroughFields({ def, hookType, converterConfig }) {
|
|
13204
|
-
return Object.fromEntries((converterConfig.stringPassthroughFields ?? []).filter(({ canonical, commandOnly }) => {
|
|
13205
|
-
if (commandOnly === true && hookType !== "command") return false;
|
|
13206
|
-
return typeof def[canonical] === "string" && def[canonical] !== "";
|
|
13207
|
-
}).map(({ canonical, tool }) => [tool, def[canonical]]));
|
|
13208
|
-
}
|
|
13209
|
-
/**
|
|
13210
|
-
* Import the configured string passthrough fields back into canonical fields,
|
|
13211
|
-
* reversing {@link emitStringPassthroughFields}. Only non-empty string values
|
|
13212
|
-
* are read.
|
|
13213
|
-
*/
|
|
13214
|
-
function importStringPassthroughFields({ h, converterConfig }) {
|
|
13215
|
-
return Object.fromEntries((converterConfig.stringPassthroughFields ?? []).filter(({ tool }) => typeof h[tool] === "string" && h[tool] !== "").map(({ canonical, tool }) => [canonical, h[tool]]));
|
|
13216
|
-
}
|
|
13217
|
-
/**
|
|
13218
|
-
* Emit the configured string-array passthrough fields on the tool side.
|
|
13219
|
-
*/
|
|
13220
|
-
function emitArrayPassthroughFields({ def, hookType, converterConfig }) {
|
|
13221
|
-
return Object.fromEntries((converterConfig.arrayPassthroughFields ?? []).filter(({ canonical, commandOnly }) => {
|
|
13222
|
-
if (commandOnly === true && hookType !== "command") return false;
|
|
13223
|
-
return isStringArray(def[canonical]);
|
|
13224
|
-
}).map(({ canonical, tool }) => [tool, def[canonical]]));
|
|
13225
|
-
}
|
|
13226
|
-
/**
|
|
13227
|
-
* Import the configured string-array passthrough fields, reversing
|
|
13228
|
-
* {@link emitArrayPassthroughFields}.
|
|
13204
|
+
* Whether a field registered for `command` hooks only applies to this hook.
|
|
13205
|
+
* Applied on both export and import: a value imported into a canonical field
|
|
13206
|
+
* the exporter would then drop is silently deleted on the next generate.
|
|
13229
13207
|
*/
|
|
13230
|
-
function
|
|
13231
|
-
|
|
13232
|
-
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.`);
|
|
13233
|
-
return Object.fromEntries(fields.filter(({ tool }) => isSafeStringArray(h[tool])).map(({ canonical, tool }) => [canonical, h[tool]]));
|
|
13208
|
+
function isFieldApplicable({ commandOnly, hookType }) {
|
|
13209
|
+
return commandOnly !== true || hookType === "command";
|
|
13234
13210
|
}
|
|
13235
13211
|
/**
|
|
13236
|
-
* Emit the configured
|
|
13237
|
-
*
|
|
13212
|
+
* Emit the configured passthrough fields on the tool side, mapping each
|
|
13213
|
+
* canonical field name to its (possibly renamed) tool field name. Only values
|
|
13214
|
+
* accepted by `isValid` are carried through, so a malformed field can't leak
|
|
13215
|
+
* into a config the tool would reject.
|
|
13216
|
+
*
|
|
13217
|
+
* A field the tool documents on `command` hooks only is dropped when authored
|
|
13218
|
+
* on another hook type. That is a value the user hand-wrote in
|
|
13219
|
+
* `.rulesync/hooks.*` and the canonical schema does not cross-validate it
|
|
13220
|
+
* against the hook's type, so it is warned about rather than deleted in
|
|
13221
|
+
* silence — the mirror of the import side.
|
|
13238
13222
|
*/
|
|
13239
|
-
function
|
|
13240
|
-
|
|
13241
|
-
|
|
13242
|
-
|
|
13243
|
-
|
|
13244
|
-
|
|
13223
|
+
function emitPassthroughFields({ def, hookType, eventName, fields, isValid, warn }) {
|
|
13224
|
+
for (const { canonical, tool, commandOnly } of fields) {
|
|
13225
|
+
const value = def[canonical];
|
|
13226
|
+
if (value === void 0) continue;
|
|
13227
|
+
if (!isFieldApplicable({
|
|
13228
|
+
commandOnly,
|
|
13229
|
+
hookType
|
|
13230
|
+
})) {
|
|
13231
|
+
warn?.(`Dropping "${canonical}" from a "${hookType}" hook on "${eventName}": this tool documents "${tool}" on "command" hooks only, so it is not generated.`);
|
|
13232
|
+
continue;
|
|
13233
|
+
}
|
|
13234
|
+
if (!isValid({
|
|
13235
|
+
value,
|
|
13236
|
+
canonical
|
|
13237
|
+
})) warn?.(`Dropping "${canonical}" from a "${hookType}" hook on "${eventName}": ${JSON.stringify(value)} is not a value this tool can express as "${tool}".`);
|
|
13238
|
+
}
|
|
13239
|
+
return Object.fromEntries(fields.filter(({ canonical, commandOnly }) => isFieldApplicable({
|
|
13240
|
+
commandOnly,
|
|
13241
|
+
hookType
|
|
13242
|
+
}) && isValid({
|
|
13243
|
+
value: def[canonical],
|
|
13244
|
+
canonical
|
|
13245
|
+
})).map(({ canonical, tool }) => [tool, def[canonical]]));
|
|
13246
|
+
}
|
|
13247
|
+
/**
|
|
13248
|
+
* Import the configured passthrough fields back into canonical fields,
|
|
13249
|
+
* reversing {@link emitPassthroughFields}. A field the tool documents on
|
|
13250
|
+
* `command` hooks only is skipped here too, and `describeInvalid` — when the
|
|
13251
|
+
* kind has a rule an authored file can plausibly violate — turns a rejected
|
|
13252
|
+
* value into a warning instead of a silent drop.
|
|
13253
|
+
*/
|
|
13254
|
+
function importPassthroughFields({ h, hookType, fields, isValid, describeInvalid, warn }) {
|
|
13255
|
+
const applicable = fields.filter(({ commandOnly }) => isFieldApplicable({
|
|
13256
|
+
commandOnly,
|
|
13257
|
+
hookType
|
|
13258
|
+
}));
|
|
13259
|
+
const skipped = fields.filter(({ commandOnly }) => !isFieldApplicable({
|
|
13260
|
+
commandOnly,
|
|
13261
|
+
hookType
|
|
13262
|
+
}));
|
|
13263
|
+
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.`);
|
|
13264
|
+
for (const { tool, canonical } of applicable) if (describeInvalid !== void 0 && h[tool] !== void 0 && !isValid({
|
|
13265
|
+
value: h[tool],
|
|
13266
|
+
canonical
|
|
13267
|
+
})) warn?.(describeInvalid({
|
|
13268
|
+
tool,
|
|
13269
|
+
canonical,
|
|
13270
|
+
value: h[tool]
|
|
13271
|
+
}));
|
|
13272
|
+
return Object.fromEntries(applicable.filter(({ tool, canonical }) => isValid({
|
|
13273
|
+
value: h[tool],
|
|
13274
|
+
canonical
|
|
13275
|
+
})).map(({ canonical, tool }) => [canonical, h[tool]]));
|
|
13276
|
+
}
|
|
13277
|
+
const isBooleanValue = ({ value }) => typeof value === "boolean";
|
|
13278
|
+
const isFiniteNumber = ({ value }) => Number.isFinite(value);
|
|
13279
|
+
const isNonEmptyString = (value) => typeof value === "string" && value !== "";
|
|
13280
|
+
const isEmittableString = ({ value }) => isNonEmptyString(value);
|
|
13281
|
+
const isEmittableArray = ({ value }) => isStringArray(value);
|
|
13282
|
+
const isEmittableRecord = ({ value }) => isSafeStringRecord(value);
|
|
13283
|
+
const isImportableArray = ({ value }) => isSafeStringArray(value);
|
|
13284
|
+
/**
|
|
13285
|
+
* The canonical schema of one hook field, looked up by name. Read off the
|
|
13286
|
+
* schema's own shape rather than by parsing a one-field object, so a `canonical`
|
|
13287
|
+
* name that no longer exists resolves to `undefined` (a `looseObject` would
|
|
13288
|
+
* accept an unknown key and silently validate nothing) and a typo is caught by
|
|
13289
|
+
* the tests instead of quietly disabling the check.
|
|
13290
|
+
*/
|
|
13291
|
+
const CANONICAL_FIELD_SCHEMAS = HookDefinitionSchema.def.shape;
|
|
13292
|
+
/**
|
|
13293
|
+
* Whether a value satisfies the constraints the canonical schema puts on the
|
|
13294
|
+
* field it would be imported into.
|
|
13295
|
+
*
|
|
13296
|
+
* Import needs this on top of the kind's shape check: a hand-written tool
|
|
13297
|
+
* settings file can hold `"shell": "zsh"`, which is a non-empty string but not
|
|
13298
|
+
* a member of the canonical `z.enum(["bash", "powershell"])`. Letting it in
|
|
13299
|
+
* would write a `.rulesync/hooks.jsonc` that fails validation on the *next*
|
|
13300
|
+
* run, taking the whole hooks feature down with it.
|
|
13301
|
+
*/
|
|
13302
|
+
function satisfiesCanonicalField({ value, canonical }) {
|
|
13303
|
+
const schema = canonicalFieldSchema(canonical);
|
|
13304
|
+
return schema !== void 0 && z.safeParse(schema, value).success;
|
|
13305
|
+
}
|
|
13306
|
+
/** Own properties only, so a name like `toString` resolves to nothing. */
|
|
13307
|
+
function canonicalFieldSchema(canonical) {
|
|
13308
|
+
return Object.hasOwn(CANONICAL_FIELD_SCHEMAS, canonical) ? CANONICAL_FIELD_SCHEMAS[canonical] : void 0;
|
|
13309
|
+
}
|
|
13310
|
+
const isImportableString = ({ value, canonical }) => isNonEmptyString(value) && satisfiesCanonicalField({
|
|
13311
|
+
value,
|
|
13312
|
+
canonical
|
|
13313
|
+
});
|
|
13314
|
+
const isImportableNumber = ({ value, canonical }) => Number.isFinite(value) && satisfiesCanonicalField({
|
|
13315
|
+
value,
|
|
13316
|
+
canonical
|
|
13317
|
+
});
|
|
13245
13318
|
/**
|
|
13246
|
-
*
|
|
13247
|
-
*
|
|
13248
|
-
|
|
13249
|
-
|
|
13250
|
-
|
|
13251
|
-
|
|
13252
|
-
|
|
13253
|
-
|
|
13319
|
+
* Say which rule the value broke, so the warning names the actual constraint
|
|
13320
|
+
* rather than asserting a canonical rejection that may not be the reason. A
|
|
13321
|
+
* closed enum lists its members; a rule carrying its own message (the
|
|
13322
|
+
* control-character check behind `safeString`) reuses it.
|
|
13323
|
+
*/
|
|
13324
|
+
function describeScalarConstraint({ canonical, value }) {
|
|
13325
|
+
const schema = canonicalFieldSchema(canonical);
|
|
13326
|
+
const result = schema === void 0 ? void 0 : z.safeParse(schema, value);
|
|
13327
|
+
if (result === void 0 || result.success) return `it is not a value this field carries through.`;
|
|
13328
|
+
const issue = result.error.issues[0];
|
|
13329
|
+
if (issue === void 0) return `it is not a value the canonical "${canonical}" field accepts.`;
|
|
13330
|
+
return `it does not satisfy the canonical "${canonical}" field: ${issue.message}.`;
|
|
13331
|
+
}
|
|
13332
|
+
const describeInvalidScalar = ({ tool, canonical, value }) => `Dropping "${tool}" (${JSON.stringify(value)}) while importing a hook: ${describeScalarConstraint({
|
|
13333
|
+
canonical,
|
|
13334
|
+
value
|
|
13335
|
+
})} Importing it would fail validation on the next run.`;
|
|
13336
|
+
const describeInvalidArray = ({ tool }) => `Dropping "${tool}" while importing a hook: it must be a list of strings without newline, carriage return or NUL characters.`;
|
|
13337
|
+
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.`;
|
|
13254
13338
|
/**
|
|
13255
13339
|
* Check a value against the shape its field documents. A string field also
|
|
13256
13340
|
* rejects control characters, matching the canonical `safeString` so an
|
|
@@ -13311,7 +13395,54 @@ function emitTypePayloadFields({ def, hookType, converterConfig }) {
|
|
|
13311
13395
|
function isSupportedHookType({ type, converterConfig }) {
|
|
13312
13396
|
return converterConfig.supportedHookTypes?.has(type ?? "command") ?? true;
|
|
13313
13397
|
}
|
|
13314
|
-
|
|
13398
|
+
/**
|
|
13399
|
+
* Emit every per-hook passthrough kind for one canonical definition.
|
|
13400
|
+
*/
|
|
13401
|
+
function emitAllPassthroughFields({ def, hookType, eventName, converterConfig, warn }) {
|
|
13402
|
+
return {
|
|
13403
|
+
...emitPassthroughFields({
|
|
13404
|
+
def,
|
|
13405
|
+
hookType,
|
|
13406
|
+
eventName,
|
|
13407
|
+
fields: converterConfig.booleanPassthroughFields ?? [],
|
|
13408
|
+
isValid: isBooleanValue,
|
|
13409
|
+
warn
|
|
13410
|
+
}),
|
|
13411
|
+
...emitPassthroughFields({
|
|
13412
|
+
def,
|
|
13413
|
+
hookType,
|
|
13414
|
+
eventName,
|
|
13415
|
+
fields: converterConfig.numberPassthroughFields ?? [],
|
|
13416
|
+
isValid: isFiniteNumber,
|
|
13417
|
+
warn
|
|
13418
|
+
}),
|
|
13419
|
+
...emitPassthroughFields({
|
|
13420
|
+
def,
|
|
13421
|
+
hookType,
|
|
13422
|
+
eventName,
|
|
13423
|
+
fields: converterConfig.stringPassthroughFields ?? [],
|
|
13424
|
+
isValid: isEmittableString,
|
|
13425
|
+
warn
|
|
13426
|
+
}),
|
|
13427
|
+
...emitPassthroughFields({
|
|
13428
|
+
def,
|
|
13429
|
+
hookType,
|
|
13430
|
+
eventName,
|
|
13431
|
+
fields: converterConfig.arrayPassthroughFields ?? [],
|
|
13432
|
+
isValid: isEmittableArray,
|
|
13433
|
+
warn
|
|
13434
|
+
}),
|
|
13435
|
+
...emitPassthroughFields({
|
|
13436
|
+
def,
|
|
13437
|
+
hookType,
|
|
13438
|
+
eventName,
|
|
13439
|
+
fields: converterConfig.recordPassthroughFields ?? [],
|
|
13440
|
+
isValid: isEmittableRecord,
|
|
13441
|
+
warn
|
|
13442
|
+
})
|
|
13443
|
+
};
|
|
13444
|
+
}
|
|
13445
|
+
function buildToolHooks({ defs, eventName, converterConfig, warn }) {
|
|
13315
13446
|
const hooks = [];
|
|
13316
13447
|
for (const def of defs) {
|
|
13317
13448
|
const hookType = def.type ?? "command";
|
|
@@ -13324,30 +13455,12 @@ function buildToolHooks({ defs, converterConfig }) {
|
|
|
13324
13455
|
converterConfig
|
|
13325
13456
|
});
|
|
13326
13457
|
hooks.push({
|
|
13327
|
-
...
|
|
13328
|
-
def,
|
|
13329
|
-
hookType,
|
|
13330
|
-
converterConfig
|
|
13331
|
-
}),
|
|
13332
|
-
...emitNumberPassthroughFields({
|
|
13333
|
-
def,
|
|
13334
|
-
hookType,
|
|
13335
|
-
converterConfig
|
|
13336
|
-
}),
|
|
13337
|
-
...emitStringPassthroughFields({
|
|
13338
|
-
def,
|
|
13339
|
-
hookType,
|
|
13340
|
-
converterConfig
|
|
13341
|
-
}),
|
|
13342
|
-
...emitArrayPassthroughFields({
|
|
13458
|
+
...emitAllPassthroughFields({
|
|
13343
13459
|
def,
|
|
13344
13460
|
hookType,
|
|
13345
|
-
|
|
13346
|
-
|
|
13347
|
-
|
|
13348
|
-
def,
|
|
13349
|
-
hookType,
|
|
13350
|
-
converterConfig
|
|
13461
|
+
eventName,
|
|
13462
|
+
converterConfig,
|
|
13463
|
+
warn
|
|
13351
13464
|
}),
|
|
13352
13465
|
type: hookType,
|
|
13353
13466
|
...command !== void 0 && command !== null && { command },
|
|
@@ -13365,6 +13478,17 @@ function buildToolHooks({ defs, converterConfig }) {
|
|
|
13365
13478
|
return hooks;
|
|
13366
13479
|
}
|
|
13367
13480
|
/**
|
|
13481
|
+
* A `warn` that says each distinct thing once per conversion.
|
|
13482
|
+
*/
|
|
13483
|
+
function warnOnce(logger) {
|
|
13484
|
+
const seen = /* @__PURE__ */ new Set();
|
|
13485
|
+
return (message) => {
|
|
13486
|
+
if (seen.has(message)) return;
|
|
13487
|
+
seen.add(message);
|
|
13488
|
+
logger?.warn(message);
|
|
13489
|
+
};
|
|
13490
|
+
}
|
|
13491
|
+
/**
|
|
13368
13492
|
* Convert canonical hooks config to tool-specific format (shared by Claude and Factory Droid).
|
|
13369
13493
|
* Uses explicit event name mapping tables rather than algorithmic case conversion,
|
|
13370
13494
|
* since tool event names may differ entirely from canonical names
|
|
@@ -13376,6 +13500,7 @@ function canonicalToToolHooks({ config, toolOverrideHooks, converterConfig, logg
|
|
|
13376
13500
|
toolOverrideHooks,
|
|
13377
13501
|
supportedEvents: converterConfig.supportedEvents
|
|
13378
13502
|
});
|
|
13503
|
+
const warn = warnOnce(logger);
|
|
13379
13504
|
const result = {};
|
|
13380
13505
|
for (const [eventName, definitions] of Object.entries(effectiveHooks)) {
|
|
13381
13506
|
const toolEventName = converterConfig.canonicalToToolEventNames[eventName] ?? eventName;
|
|
@@ -13389,7 +13514,9 @@ function canonicalToToolHooks({ config, toolOverrideHooks, converterConfig, logg
|
|
|
13389
13514
|
if (isNoMatcherEvent && matcherKey) logger?.warn(`matcher "${matcherKey}" on "${eventName}" hook will be ignored — this event does not support matchers`);
|
|
13390
13515
|
const hooks = buildToolHooks({
|
|
13391
13516
|
defs,
|
|
13392
|
-
|
|
13517
|
+
eventName,
|
|
13518
|
+
converterConfig,
|
|
13519
|
+
warn
|
|
13393
13520
|
});
|
|
13394
13521
|
if (hooks.length === 0) continue;
|
|
13395
13522
|
const includeMatcher = matcherKey && !isNoMatcherEvent;
|
|
@@ -13495,85 +13622,283 @@ function isSafeStringArray(value) {
|
|
|
13495
13622
|
return isStringArray(value) && value.every((entry) => !CONTROL_CHARS.some((char) => entry.includes(char)));
|
|
13496
13623
|
}
|
|
13497
13624
|
/**
|
|
13625
|
+
* A raw string kept only if the canonical field it would land in accepts it,
|
|
13626
|
+
* warning when it does not. The canonical string fields are guarded by
|
|
13627
|
+
* `safeString`, so an existing tool config carrying a control character would
|
|
13628
|
+
* otherwise be imported into a file the next generate refuses to read.
|
|
13629
|
+
*/
|
|
13630
|
+
function importCanonicalString({ value, canonical, warn }) {
|
|
13631
|
+
if (typeof value !== "string") return;
|
|
13632
|
+
if (satisfiesCanonicalField({
|
|
13633
|
+
value,
|
|
13634
|
+
canonical
|
|
13635
|
+
})) return value;
|
|
13636
|
+
warn?.(describeInvalidScalar({
|
|
13637
|
+
tool: canonical,
|
|
13638
|
+
canonical,
|
|
13639
|
+
value
|
|
13640
|
+
}));
|
|
13641
|
+
}
|
|
13642
|
+
/**
|
|
13498
13643
|
* Import the payload fields specific to a hook type, type-checking each raw
|
|
13499
13644
|
* value before it enters the canonical definition.
|
|
13500
13645
|
*/
|
|
13501
|
-
function importTypePayloadFields({ h, hookType }) {
|
|
13502
|
-
if (hookType === "http")
|
|
13503
|
-
|
|
13504
|
-
|
|
13505
|
-
|
|
13506
|
-
|
|
13507
|
-
|
|
13508
|
-
|
|
13509
|
-
|
|
13510
|
-
|
|
13511
|
-
|
|
13512
|
-
|
|
13646
|
+
function importTypePayloadFields({ h, hookType, warn }) {
|
|
13647
|
+
if (hookType === "http") {
|
|
13648
|
+
const url = importCanonicalString({
|
|
13649
|
+
value: h.url,
|
|
13650
|
+
canonical: "url",
|
|
13651
|
+
warn
|
|
13652
|
+
});
|
|
13653
|
+
const headers = isStringRecord(h.headers) && !satisfiesCanonicalField({
|
|
13654
|
+
value: h.headers,
|
|
13655
|
+
canonical: "headers"
|
|
13656
|
+
}) ? (warn?.(describeInvalidScalar({
|
|
13657
|
+
tool: "headers",
|
|
13658
|
+
canonical: "headers",
|
|
13659
|
+
value: h.headers
|
|
13660
|
+
})), void 0) : h.headers;
|
|
13661
|
+
return {
|
|
13662
|
+
...url !== void 0 && { url },
|
|
13663
|
+
...isStringRecord(headers) && { headers },
|
|
13664
|
+
...isStringArray(h.allowedEnvVars) && { allowedEnvVars: h.allowedEnvVars }
|
|
13665
|
+
};
|
|
13666
|
+
}
|
|
13667
|
+
if (hookType === "mcp_tool") {
|
|
13668
|
+
const server = importCanonicalString({
|
|
13669
|
+
value: h.server,
|
|
13670
|
+
canonical: "server",
|
|
13671
|
+
warn
|
|
13672
|
+
});
|
|
13673
|
+
const tool = importCanonicalString({
|
|
13674
|
+
value: h.tool,
|
|
13675
|
+
canonical: "tool",
|
|
13676
|
+
warn
|
|
13677
|
+
});
|
|
13678
|
+
return {
|
|
13679
|
+
...server !== void 0 && { server },
|
|
13680
|
+
...tool !== void 0 && { tool },
|
|
13681
|
+
...h.input !== null && typeof h.input === "object" && !Array.isArray(h.input) && { input: h.input }
|
|
13682
|
+
};
|
|
13683
|
+
}
|
|
13684
|
+
if (hookType === "prompt" || hookType === "agent") {
|
|
13685
|
+
const model = importCanonicalString({
|
|
13686
|
+
value: h.model,
|
|
13687
|
+
canonical: "model",
|
|
13688
|
+
warn
|
|
13689
|
+
});
|
|
13690
|
+
return model !== void 0 ? { model } : {};
|
|
13691
|
+
}
|
|
13513
13692
|
return {};
|
|
13514
13693
|
}
|
|
13515
13694
|
/**
|
|
13516
|
-
*
|
|
13695
|
+
* Import every per-hook passthrough kind for one tool hook record, reversing
|
|
13696
|
+
* the emit side in {@link buildToolHooks}.
|
|
13517
13697
|
*/
|
|
13518
|
-
function
|
|
13519
|
-
const command = stripCommandPrefix({
|
|
13520
|
-
command: h.command,
|
|
13521
|
-
converterConfig
|
|
13522
|
-
});
|
|
13523
|
-
const hookType = isImportedHookType(h.type) ? h.type : "command";
|
|
13524
|
-
const timeout = typeof h.timeout === "number" ? h.timeout : void 0;
|
|
13525
|
-
const prompt = typeof h.prompt === "string" ? h.prompt : void 0;
|
|
13698
|
+
function importAllPassthroughFields({ h, hookType, converterConfig, warn }) {
|
|
13526
13699
|
return {
|
|
13527
|
-
|
|
13528
|
-
...command !== void 0 && command !== null && { command },
|
|
13529
|
-
...timeout !== void 0 && timeout !== null && { timeout },
|
|
13530
|
-
...prompt !== void 0 && prompt !== null && { prompt },
|
|
13531
|
-
...importTypePayloadFields({
|
|
13700
|
+
...importPassthroughFields({
|
|
13532
13701
|
h,
|
|
13533
|
-
hookType
|
|
13702
|
+
hookType,
|
|
13703
|
+
fields: converterConfig.booleanPassthroughFields ?? [],
|
|
13704
|
+
isValid: isBooleanValue,
|
|
13705
|
+
warn
|
|
13534
13706
|
}),
|
|
13535
|
-
...
|
|
13536
|
-
...converterConfig.passthroughFields?.includes("description") && typeof h.description === "string" && { description: h.description },
|
|
13537
|
-
...importBooleanPassthroughFields({
|
|
13707
|
+
...importPassthroughFields({
|
|
13538
13708
|
h,
|
|
13539
|
-
|
|
13709
|
+
hookType,
|
|
13710
|
+
fields: converterConfig.numberPassthroughFields ?? [],
|
|
13711
|
+
isValid: isImportableNumber,
|
|
13712
|
+
describeInvalid: describeInvalidScalar,
|
|
13713
|
+
warn
|
|
13540
13714
|
}),
|
|
13541
|
-
...
|
|
13715
|
+
...importPassthroughFields({
|
|
13542
13716
|
h,
|
|
13543
|
-
|
|
13717
|
+
hookType,
|
|
13718
|
+
fields: converterConfig.stringPassthroughFields ?? [],
|
|
13719
|
+
isValid: isImportableString,
|
|
13720
|
+
describeInvalid: describeInvalidScalar,
|
|
13721
|
+
warn
|
|
13544
13722
|
}),
|
|
13545
|
-
...
|
|
13723
|
+
...importPassthroughFields({
|
|
13546
13724
|
h,
|
|
13725
|
+
hookType,
|
|
13726
|
+
fields: converterConfig.arrayPassthroughFields ?? [],
|
|
13727
|
+
isValid: isImportableArray,
|
|
13728
|
+
describeInvalid: describeInvalidArray,
|
|
13729
|
+
warn
|
|
13730
|
+
}),
|
|
13731
|
+
...importPassthroughFields({
|
|
13732
|
+
h,
|
|
13733
|
+
hookType,
|
|
13734
|
+
fields: converterConfig.recordPassthroughFields ?? [],
|
|
13735
|
+
isValid: isEmittableRecord,
|
|
13736
|
+
describeInvalid: describeInvalidRecord,
|
|
13737
|
+
warn
|
|
13738
|
+
})
|
|
13739
|
+
};
|
|
13740
|
+
}
|
|
13741
|
+
/**
|
|
13742
|
+
* Convert a single tool hook record into a canonical hook definition.
|
|
13743
|
+
*/
|
|
13744
|
+
function toolHookToCanonical({ h, rawEntry, converterConfig, warn }) {
|
|
13745
|
+
const hookType = isImportedHookType(h.type) ? h.type : "command";
|
|
13746
|
+
const command = importCanonicalString({
|
|
13747
|
+
value: stripCommandPrefix({
|
|
13748
|
+
command: h.command,
|
|
13547
13749
|
converterConfig
|
|
13548
13750
|
}),
|
|
13549
|
-
|
|
13751
|
+
canonical: "command",
|
|
13752
|
+
warn
|
|
13753
|
+
});
|
|
13754
|
+
const timeout = typeof h.timeout === "number" ? h.timeout : void 0;
|
|
13755
|
+
const prompt = importCanonicalString({
|
|
13756
|
+
value: h.prompt,
|
|
13757
|
+
canonical: "prompt",
|
|
13758
|
+
warn
|
|
13759
|
+
});
|
|
13760
|
+
const name = importCanonicalString({
|
|
13761
|
+
value: h.name,
|
|
13762
|
+
canonical: "name",
|
|
13763
|
+
warn
|
|
13764
|
+
});
|
|
13765
|
+
const description = importCanonicalString({
|
|
13766
|
+
value: h.description,
|
|
13767
|
+
canonical: "description",
|
|
13768
|
+
warn
|
|
13769
|
+
});
|
|
13770
|
+
const matcher = rawEntry.matcher;
|
|
13771
|
+
return {
|
|
13772
|
+
type: hookType,
|
|
13773
|
+
...command !== void 0 && command !== null && { command },
|
|
13774
|
+
...timeout !== void 0 && timeout !== null && { timeout },
|
|
13775
|
+
...prompt !== void 0 && prompt !== null && { prompt },
|
|
13776
|
+
...importTypePayloadFields({
|
|
13550
13777
|
h,
|
|
13551
|
-
|
|
13552
|
-
|
|
13778
|
+
hookType,
|
|
13779
|
+
warn
|
|
13553
13780
|
}),
|
|
13554
|
-
...
|
|
13781
|
+
...converterConfig.passthroughFields?.includes("name") && name !== void 0 && { name },
|
|
13782
|
+
...converterConfig.passthroughFields?.includes("description") && description !== void 0 && { description },
|
|
13783
|
+
...importAllPassthroughFields({
|
|
13555
13784
|
h,
|
|
13556
13785
|
hookType,
|
|
13557
13786
|
converterConfig,
|
|
13558
|
-
|
|
13787
|
+
warn
|
|
13559
13788
|
}),
|
|
13560
13789
|
...importGroupPassthroughFields({
|
|
13561
13790
|
rawEntry,
|
|
13562
13791
|
converterConfig
|
|
13563
13792
|
}),
|
|
13564
|
-
...
|
|
13793
|
+
...matcher !== void 0 && matcher !== null && matcher !== "" && { matcher }
|
|
13565
13794
|
};
|
|
13566
13795
|
}
|
|
13567
13796
|
/**
|
|
13568
|
-
*
|
|
13797
|
+
* The fields whose value decides what a hook *is*, listed per hook type. When
|
|
13798
|
+
* one of them cannot be imported, dropping just the field would leave
|
|
13799
|
+
* something worse than nothing: a hook that loses its body runs nothing, and
|
|
13800
|
+
* one that loses its `matcher` fires on *everything* — a silent widening of
|
|
13801
|
+
* what the imported rule does. So the whole definition is skipped instead,
|
|
13802
|
+
* with the reason named. A field that does not define *this* type (a `prompt`
|
|
13803
|
+
* left on a command hook) is not one of them: it is dropped on its own, the
|
|
13804
|
+
* way any other unusable field is.
|
|
13569
13805
|
*/
|
|
13570
|
-
function
|
|
13571
|
-
|
|
13806
|
+
function definingFields({ h, rawEntry, hookType, converterConfig }) {
|
|
13807
|
+
const fields = [{
|
|
13808
|
+
field: "matcher",
|
|
13809
|
+
value: rawEntry.matcher
|
|
13810
|
+
}];
|
|
13811
|
+
if (hookType === "command") fields.push({
|
|
13812
|
+
field: "command",
|
|
13813
|
+
value: typeof h.command === "string" ? stripCommandPrefix({
|
|
13814
|
+
command: h.command,
|
|
13815
|
+
converterConfig
|
|
13816
|
+
}) : h.command
|
|
13817
|
+
});
|
|
13818
|
+
if (hookType === "prompt" || hookType === "agent") fields.push({
|
|
13819
|
+
field: "prompt",
|
|
13820
|
+
value: h.prompt
|
|
13821
|
+
});
|
|
13822
|
+
if (hookType === "http") fields.push({
|
|
13823
|
+
field: "url",
|
|
13824
|
+
value: h.url
|
|
13825
|
+
});
|
|
13826
|
+
if (hookType === "mcp_tool") fields.push({
|
|
13827
|
+
field: "server",
|
|
13828
|
+
value: h.server
|
|
13829
|
+
}, {
|
|
13830
|
+
field: "tool",
|
|
13831
|
+
value: h.tool
|
|
13832
|
+
});
|
|
13833
|
+
return fields;
|
|
13834
|
+
}
|
|
13835
|
+
/**
|
|
13836
|
+
* Why no hook of this matcher group can be imported, or `undefined` when they
|
|
13837
|
+
* can. A group field that *restricts* when its hooks run (`subdividesGroup`)
|
|
13838
|
+
* is the group-level twin of `matcher`: importing the group without it would
|
|
13839
|
+
* widen every hook in it, so the group is skipped instead.
|
|
13840
|
+
*/
|
|
13841
|
+
function describeGroupSkipReason({ rawEntry, converterConfig }) {
|
|
13842
|
+
const entry = rawEntry;
|
|
13843
|
+
for (const { tool, valueType, subdividesGroup } of converterConfig.groupPassthroughFields ?? []) {
|
|
13844
|
+
const value = entry[tool];
|
|
13845
|
+
if (subdividesGroup !== true || value === void 0) continue;
|
|
13846
|
+
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.`;
|
|
13847
|
+
}
|
|
13848
|
+
}
|
|
13849
|
+
/**
|
|
13850
|
+
* Why this hook cannot be imported, or `undefined` when it can.
|
|
13851
|
+
*/
|
|
13852
|
+
function describeHookSkipReason({ h, rawEntry, hookType, converterConfig }) {
|
|
13853
|
+
for (const { field, value } of definingFields({
|
|
13572
13854
|
h,
|
|
13573
13855
|
rawEntry,
|
|
13574
|
-
|
|
13575
|
-
|
|
13576
|
-
}))
|
|
13856
|
+
hookType,
|
|
13857
|
+
converterConfig
|
|
13858
|
+
})) {
|
|
13859
|
+
if (value === void 0 || satisfiesCanonicalField({
|
|
13860
|
+
value,
|
|
13861
|
+
canonical: field
|
|
13862
|
+
})) continue;
|
|
13863
|
+
return `Skipping a hook while importing: its "${field}" (${JSON.stringify(value)}) is unusable — ${describeScalarConstraint({
|
|
13864
|
+
canonical: field,
|
|
13865
|
+
value
|
|
13866
|
+
})} Keeping the hook without it would change what it does, so the whole hook is skipped.`;
|
|
13867
|
+
}
|
|
13868
|
+
}
|
|
13869
|
+
/**
|
|
13870
|
+
* Convert a single tool matcher entry into canonical hook definitions.
|
|
13871
|
+
*/
|
|
13872
|
+
function toolMatcherEntryToCanonical({ rawEntry, converterConfig, warn }) {
|
|
13873
|
+
const hookDefs = rawEntry.hooks ?? [];
|
|
13874
|
+
const groupSkipReason = describeGroupSkipReason({
|
|
13875
|
+
rawEntry,
|
|
13876
|
+
converterConfig
|
|
13877
|
+
});
|
|
13878
|
+
if (groupSkipReason !== void 0) {
|
|
13879
|
+
warn?.(groupSkipReason);
|
|
13880
|
+
return [];
|
|
13881
|
+
}
|
|
13882
|
+
const definitions = [];
|
|
13883
|
+
for (const h of hookDefs) {
|
|
13884
|
+
const skipReason = describeHookSkipReason({
|
|
13885
|
+
h,
|
|
13886
|
+
rawEntry,
|
|
13887
|
+
hookType: isImportedHookType(h.type) ? h.type : "command",
|
|
13888
|
+
converterConfig
|
|
13889
|
+
});
|
|
13890
|
+
if (skipReason !== void 0) {
|
|
13891
|
+
warn?.(skipReason);
|
|
13892
|
+
continue;
|
|
13893
|
+
}
|
|
13894
|
+
definitions.push(toolHookToCanonical({
|
|
13895
|
+
h,
|
|
13896
|
+
rawEntry,
|
|
13897
|
+
converterConfig,
|
|
13898
|
+
warn
|
|
13899
|
+
}));
|
|
13900
|
+
}
|
|
13901
|
+
return definitions;
|
|
13577
13902
|
}
|
|
13578
13903
|
/**
|
|
13579
13904
|
* Assemble the canonical hooks config a tool importer writes to
|
|
@@ -13603,6 +13928,7 @@ function buildImportedHooksConfig({ hooks, overrideKey, version = 1, extraOverri
|
|
|
13603
13928
|
}
|
|
13604
13929
|
function toolHooksToCanonical({ hooks, converterConfig, logger }) {
|
|
13605
13930
|
if (hooks === null || hooks === void 0 || typeof hooks !== "object") return {};
|
|
13931
|
+
const warn = warnOnce(logger);
|
|
13606
13932
|
const canonical = {};
|
|
13607
13933
|
for (const [toolEventName, matcherEntries] of Object.entries(hooks)) {
|
|
13608
13934
|
const eventName = converterConfig.toolToCanonicalEventNames[toolEventName] ?? toolEventName;
|
|
@@ -13613,7 +13939,7 @@ function toolHooksToCanonical({ hooks, converterConfig, logger }) {
|
|
|
13613
13939
|
defs.push(...toolMatcherEntryToCanonical({
|
|
13614
13940
|
rawEntry,
|
|
13615
13941
|
converterConfig,
|
|
13616
|
-
|
|
13942
|
+
warn
|
|
13617
13943
|
}));
|
|
13618
13944
|
}
|
|
13619
13945
|
if (defs.length > 0) canonical[eventName] = defs;
|
|
@@ -13652,7 +13978,9 @@ const ANTIGRAVITY_HOOK_NAME = "rulesync";
|
|
|
13652
13978
|
* map for import. Accepts both the documented named-hook shape
|
|
13653
13979
|
* (`{ "<name>": { "<Event>": [...], "enabled"?: bool } }`) and a legacy flat
|
|
13654
13980
|
* shape (`{ "<Event>": [...] }`) so older or hand-written files still import.
|
|
13655
|
-
* The per-hook `enabled` flag is ignored
|
|
13981
|
+
* The per-hook `enabled` flag is ignored. The canonical `enabled` field is a
|
|
13982
|
+
* property of a single hook definition, whereas Antigravity's flag gates a whole
|
|
13983
|
+
* named group, so the two do not map onto each other.
|
|
13656
13984
|
*/
|
|
13657
13985
|
function flattenAntigravityHooks(parsed) {
|
|
13658
13986
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
@@ -13734,7 +14062,7 @@ var AntigravityHooks = class extends ToolHooks {
|
|
|
13734
14062
|
validate
|
|
13735
14063
|
});
|
|
13736
14064
|
}
|
|
13737
|
-
toRulesyncHooks() {
|
|
14065
|
+
toRulesyncHooks({ logger } = {}) {
|
|
13738
14066
|
let parsed;
|
|
13739
14067
|
try {
|
|
13740
14068
|
parsed = JSON.parse(this.getFileContent());
|
|
@@ -13743,7 +14071,8 @@ var AntigravityHooks = class extends ToolHooks {
|
|
|
13743
14071
|
}
|
|
13744
14072
|
const hooks = toolHooksToCanonical({
|
|
13745
14073
|
hooks: flattenAntigravityHooks(parsed),
|
|
13746
|
-
converterConfig: ANTIGRAVITY_CONVERTER_CONFIG
|
|
14074
|
+
converterConfig: ANTIGRAVITY_CONVERTER_CONFIG,
|
|
14075
|
+
logger
|
|
13747
14076
|
});
|
|
13748
14077
|
const overrideKey = this.constructor.getOverrideKey();
|
|
13749
14078
|
return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
|
|
@@ -14125,7 +14454,7 @@ var ClaudecodeHooks = class extends ToolHooks {
|
|
|
14125
14454
|
validate
|
|
14126
14455
|
});
|
|
14127
14456
|
}
|
|
14128
|
-
toRulesyncHooks() {
|
|
14457
|
+
toRulesyncHooks({ logger } = {}) {
|
|
14129
14458
|
let settings;
|
|
14130
14459
|
try {
|
|
14131
14460
|
settings = JSON.parse(this.getFileContent());
|
|
@@ -14134,7 +14463,8 @@ var ClaudecodeHooks = class extends ToolHooks {
|
|
|
14134
14463
|
}
|
|
14135
14464
|
const hooks = toolHooksToCanonical({
|
|
14136
14465
|
hooks: settings.hooks,
|
|
14137
|
-
converterConfig: this.constructor.getConverterConfig()
|
|
14466
|
+
converterConfig: this.constructor.getConverterConfig(),
|
|
14467
|
+
logger
|
|
14138
14468
|
});
|
|
14139
14469
|
return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
|
|
14140
14470
|
hooks,
|
|
@@ -14293,13 +14623,14 @@ var CodexcliHooks = class CodexcliHooks extends ToolHooks {
|
|
|
14293
14623
|
validate
|
|
14294
14624
|
});
|
|
14295
14625
|
}
|
|
14296
|
-
static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false }) {
|
|
14626
|
+
static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false, logger }) {
|
|
14297
14627
|
const paths = CodexcliHooks.getSettablePaths({ global });
|
|
14298
14628
|
const config = rulesyncHooks.getJson();
|
|
14299
14629
|
const codexHooks = canonicalToToolHooks({
|
|
14300
14630
|
config,
|
|
14301
14631
|
toolOverrideHooks: config.codexcli?.hooks,
|
|
14302
|
-
converterConfig: CODEXCLI_CONVERTER_CONFIG
|
|
14632
|
+
converterConfig: CODEXCLI_CONVERTER_CONFIG,
|
|
14633
|
+
logger
|
|
14303
14634
|
});
|
|
14304
14635
|
const fileContent = JSON.stringify({ hooks: codexHooks }, null, 2);
|
|
14305
14636
|
return new CodexcliHooks({
|
|
@@ -14310,7 +14641,7 @@ var CodexcliHooks = class CodexcliHooks extends ToolHooks {
|
|
|
14310
14641
|
validate
|
|
14311
14642
|
});
|
|
14312
14643
|
}
|
|
14313
|
-
toRulesyncHooks() {
|
|
14644
|
+
toRulesyncHooks({ logger } = {}) {
|
|
14314
14645
|
let parsed;
|
|
14315
14646
|
try {
|
|
14316
14647
|
parsed = JSON.parse(this.getFileContent());
|
|
@@ -14319,7 +14650,8 @@ var CodexcliHooks = class CodexcliHooks extends ToolHooks {
|
|
|
14319
14650
|
}
|
|
14320
14651
|
const hooks = toolHooksToCanonical({
|
|
14321
14652
|
hooks: parsed.hooks,
|
|
14322
|
-
converterConfig: CODEXCLI_CONVERTER_CONFIG
|
|
14653
|
+
converterConfig: CODEXCLI_CONVERTER_CONFIG,
|
|
14654
|
+
logger
|
|
14323
14655
|
});
|
|
14324
14656
|
return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
|
|
14325
14657
|
hooks,
|
|
@@ -14558,13 +14890,13 @@ var CopilotHooks = class CopilotHooks extends ToolHooks {
|
|
|
14558
14890
|
* under `.github/hooks/` is picked up automatically when the CLI is
|
|
14559
14891
|
* invoked from the project root.
|
|
14560
14892
|
*
|
|
14561
|
-
* - **Global scope**: `~/.copilot/hooks/copilot-hooks.json` —
|
|
14562
|
-
*
|
|
14563
|
-
*
|
|
14564
|
-
*
|
|
14565
|
-
*
|
|
14566
|
-
*
|
|
14567
|
-
*
|
|
14893
|
+
* - **Global scope**: `~/.copilot/hooks/copilot-hooks.json` — the directory is
|
|
14894
|
+
* the documented user-level hooks location ("`*.json` files in the
|
|
14895
|
+
* user-level hooks directory. By default this is `~/.copilot/hooks/` on
|
|
14896
|
+
* macOS and Linux, or `%USERPROFILE%\.copilot\hooks\` on Windows"). Every
|
|
14897
|
+
* `*.json` in it is loaded, so the filename remains rulesync's choice, as it
|
|
14898
|
+
* is for project scope. `COPILOT_HOME` relocates the directory upstream
|
|
14899
|
+
* (`$COPILOT_HOME/hooks/`); rulesync does not read that variable yet.
|
|
14568
14900
|
*
|
|
14569
14901
|
* Hook entries on the six matcher-aware events (see
|
|
14570
14902
|
* {@link COPILOTCLI_MATCHER_EVENTS}) may carry an optional `matcher` regex; it
|
|
@@ -14975,17 +15307,18 @@ const DEEPAGENTS_MCP_FILE_NAME = ".mcp.json";
|
|
|
14975
15307
|
const DEEPAGENTS_HOOKS_FILE_NAME = "hooks.json";
|
|
14976
15308
|
//#endregion
|
|
14977
15309
|
//#region src/features/hooks/deepagents-hooks.ts
|
|
14978
|
-
function
|
|
14979
|
-
|
|
14980
|
-
return Array.isArray(val.hooks);
|
|
15310
|
+
function isRecord(value) {
|
|
15311
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
14981
15312
|
}
|
|
14982
15313
|
/**
|
|
14983
|
-
* Convert canonical hooks config to deepagents
|
|
15314
|
+
* Convert the canonical hooks config to the deepagents Hooks v2 document.
|
|
14984
15315
|
*
|
|
14985
|
-
*
|
|
14986
|
-
* { "hooks":
|
|
15316
|
+
* ```json
|
|
15317
|
+
* { "hooks": { "PreToolUse": [{ "matcher": "Bash", "hooks": [{ "type": "command", "command": "…" }] }] } }
|
|
15318
|
+
* ```
|
|
14987
15319
|
*
|
|
14988
|
-
*
|
|
15320
|
+
* Definitions sharing an event and matcher land in one group, preserving their
|
|
15321
|
+
* authored order — upstream runs a group's handlers in sequence.
|
|
14989
15322
|
*/
|
|
14990
15323
|
function canonicalToDeepagentsHooks(config) {
|
|
14991
15324
|
const supported = new Set(DEEPAGENTS_HOOK_EVENTS);
|
|
@@ -14993,7 +15326,7 @@ function canonicalToDeepagentsHooks(config) {
|
|
|
14993
15326
|
...config.hooks,
|
|
14994
15327
|
...config.deepagents?.hooks
|
|
14995
15328
|
};
|
|
14996
|
-
const
|
|
15329
|
+
const hooks = {};
|
|
14997
15330
|
for (const [canonicalEvent, definitions] of Object.entries(effectiveHooks)) {
|
|
14998
15331
|
if (!supported.has(canonicalEvent)) continue;
|
|
14999
15332
|
const deepagentsEvent = CANONICAL_TO_DEEPAGENTS_EVENT_NAMES[canonicalEvent];
|
|
@@ -15001,43 +15334,69 @@ function canonicalToDeepagentsHooks(config) {
|
|
|
15001
15334
|
for (const def of definitions) {
|
|
15002
15335
|
if ((def.type ?? "command") !== "command") continue;
|
|
15003
15336
|
if (!def.command) continue;
|
|
15004
|
-
|
|
15005
|
-
|
|
15006
|
-
command:
|
|
15007
|
-
|
|
15008
|
-
|
|
15009
|
-
|
|
15010
|
-
|
|
15011
|
-
|
|
15337
|
+
const handler = {
|
|
15338
|
+
type: "command",
|
|
15339
|
+
command: def.command
|
|
15340
|
+
};
|
|
15341
|
+
if (def.timeout !== void 0 && def.timeout !== null && def.timeout > 0) handler.timeout = def.timeout;
|
|
15342
|
+
if (def.statusMessage !== void 0 && def.statusMessage !== null) handler.statusMessage = def.statusMessage;
|
|
15343
|
+
const matcher = def.matcher !== void 0 && def.matcher !== null && def.matcher !== "" ? def.matcher : void 0;
|
|
15344
|
+
const groups = hooks[deepagentsEvent] ??= [];
|
|
15345
|
+
const group = groups.find((candidate) => candidate.matcher === matcher);
|
|
15346
|
+
if (group) group.hooks.push(handler);
|
|
15347
|
+
else groups.push({
|
|
15348
|
+
...matcher !== void 0 && { matcher },
|
|
15349
|
+
hooks: [handler]
|
|
15012
15350
|
});
|
|
15013
15351
|
}
|
|
15014
15352
|
}
|
|
15015
|
-
return
|
|
15353
|
+
return hooks;
|
|
15016
15354
|
}
|
|
15017
15355
|
/**
|
|
15018
|
-
* Convert
|
|
15356
|
+
* Convert the Hooks v2 document back to the canonical hooks record.
|
|
15019
15357
|
*/
|
|
15020
|
-
function deepagentsToCanonicalHooks(
|
|
15358
|
+
function deepagentsToCanonicalHooks(hooks) {
|
|
15021
15359
|
const canonical = {};
|
|
15022
|
-
for (const
|
|
15023
|
-
|
|
15024
|
-
if (!Array.isArray(
|
|
15025
|
-
|
|
15026
|
-
|
|
15027
|
-
|
|
15028
|
-
|
|
15029
|
-
|
|
15030
|
-
|
|
15360
|
+
for (const [deepagentsEvent, groups] of Object.entries(hooks)) {
|
|
15361
|
+
const canonicalEvent = DEEPAGENTS_TO_CANONICAL_EVENT_NAMES[deepagentsEvent];
|
|
15362
|
+
if (!canonicalEvent || !Array.isArray(groups)) continue;
|
|
15363
|
+
for (const group of groups) {
|
|
15364
|
+
if (!isRecord(group) || !Array.isArray(group.hooks)) continue;
|
|
15365
|
+
for (const handler of group.hooks) {
|
|
15366
|
+
if (!isRecord(handler) || typeof handler.command !== "string") continue;
|
|
15367
|
+
const def = {
|
|
15368
|
+
type: "command",
|
|
15369
|
+
command: handler.command
|
|
15370
|
+
};
|
|
15371
|
+
if (typeof group.matcher === "string" && group.matcher !== "") def.matcher = group.matcher;
|
|
15372
|
+
if (typeof handler.timeout === "number") def.timeout = handler.timeout;
|
|
15373
|
+
if (typeof handler.statusMessage === "string") def.statusMessage = handler.statusMessage;
|
|
15374
|
+
(canonical[canonicalEvent] ??= []).push(def);
|
|
15375
|
+
}
|
|
15376
|
+
}
|
|
15377
|
+
}
|
|
15378
|
+
return canonical;
|
|
15379
|
+
}
|
|
15380
|
+
/**
|
|
15381
|
+
* Read the pre-v2 flat list. deepagents still loads it until 2026-09-01, so a
|
|
15382
|
+
* `hooks.json` a user has not migrated yet is imported rather than discarded —
|
|
15383
|
+
* but rulesync only ever writes the v2 shape, so regenerating migrates it.
|
|
15384
|
+
*/
|
|
15385
|
+
function deepagentsLegacyToCanonicalHooks(entries) {
|
|
15386
|
+
const canonical = {};
|
|
15387
|
+
for (const entry of entries) {
|
|
15388
|
+
if (!isRecord(entry)) continue;
|
|
15389
|
+
const argv = entry.command;
|
|
15390
|
+
if (!Array.isArray(argv) || argv.length === 0) continue;
|
|
15391
|
+
const command = argv.length === 3 && argv[0] === "bash" && argv[1] === "-c" ? String(argv[2] ?? "") : argv.join(" ");
|
|
15392
|
+
const events = Array.isArray(entry.events) ? entry.events : [];
|
|
15393
|
+
for (const legacyEvent of events) {
|
|
15394
|
+
const canonicalEvent = typeof legacyEvent === "string" ? DEEPAGENTS_LEGACY_TO_CANONICAL_EVENT_NAMES[legacyEvent] : void 0;
|
|
15031
15395
|
if (!canonicalEvent) continue;
|
|
15032
|
-
|
|
15033
|
-
if (existing) existing.push({
|
|
15396
|
+
(canonical[canonicalEvent] ??= []).push({
|
|
15034
15397
|
type: "command",
|
|
15035
15398
|
command
|
|
15036
15399
|
});
|
|
15037
|
-
else canonical[canonicalEvent] = [{
|
|
15038
|
-
type: "command",
|
|
15039
|
-
command
|
|
15040
|
-
}];
|
|
15041
15400
|
}
|
|
15042
15401
|
}
|
|
15043
15402
|
return canonical;
|
|
@@ -15046,7 +15405,7 @@ var DeepagentsHooks = class DeepagentsHooks extends ToolHooks {
|
|
|
15046
15405
|
constructor(params) {
|
|
15047
15406
|
super({
|
|
15048
15407
|
...params,
|
|
15049
|
-
fileContent: params.fileContent ?? JSON.stringify({ hooks:
|
|
15408
|
+
fileContent: params.fileContent ?? JSON.stringify({ hooks: {} }, null, 2)
|
|
15050
15409
|
});
|
|
15051
15410
|
}
|
|
15052
15411
|
isDeletable() {
|
|
@@ -15060,7 +15419,7 @@ var DeepagentsHooks = class DeepagentsHooks extends ToolHooks {
|
|
|
15060
15419
|
}
|
|
15061
15420
|
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
15062
15421
|
const paths = DeepagentsHooks.getSettablePaths({ global });
|
|
15063
|
-
const fileContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? JSON.stringify({ hooks:
|
|
15422
|
+
const fileContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? JSON.stringify({ hooks: {} }, null, 2);
|
|
15064
15423
|
return new DeepagentsHooks({
|
|
15065
15424
|
outputRoot,
|
|
15066
15425
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -15088,7 +15447,8 @@ var DeepagentsHooks = class DeepagentsHooks extends ToolHooks {
|
|
|
15088
15447
|
} catch (error) {
|
|
15089
15448
|
throw new Error(`Failed to parse deepagents hooks content in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
15090
15449
|
}
|
|
15091
|
-
const
|
|
15450
|
+
const rawHooks = isRecord(parsed) ? parsed.hooks : void 0;
|
|
15451
|
+
const hooks = Array.isArray(rawHooks) ? deepagentsLegacyToCanonicalHooks(rawHooks) : isRecord(rawHooks) ? deepagentsToCanonicalHooks(rawHooks) : {};
|
|
15092
15452
|
return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
|
|
15093
15453
|
hooks,
|
|
15094
15454
|
overrideKey: "deepagents"
|
|
@@ -15105,7 +15465,7 @@ var DeepagentsHooks = class DeepagentsHooks extends ToolHooks {
|
|
|
15105
15465
|
outputRoot,
|
|
15106
15466
|
relativeDirPath,
|
|
15107
15467
|
relativeFilePath,
|
|
15108
|
-
fileContent: JSON.stringify({ hooks:
|
|
15468
|
+
fileContent: JSON.stringify({ hooks: {} }, null, 2),
|
|
15109
15469
|
validate: false
|
|
15110
15470
|
});
|
|
15111
15471
|
}
|
|
@@ -15207,7 +15567,7 @@ var DevinHooks = class DevinHooks extends ToolHooks {
|
|
|
15207
15567
|
validate
|
|
15208
15568
|
});
|
|
15209
15569
|
}
|
|
15210
|
-
toRulesyncHooks() {
|
|
15570
|
+
toRulesyncHooks({ logger } = {}) {
|
|
15211
15571
|
let parsed;
|
|
15212
15572
|
try {
|
|
15213
15573
|
parsed = JSON.parse(this.getFileContent());
|
|
@@ -15215,8 +15575,9 @@ var DevinHooks = class DevinHooks extends ToolHooks {
|
|
|
15215
15575
|
throw new Error(`Failed to parse Devin hooks content in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
15216
15576
|
}
|
|
15217
15577
|
const hooks = toolHooksToCanonical({
|
|
15218
|
-
hooks: this.getRelativeFilePath() === "config.json" ? isRecord(parsed) && isRecord(parsed.hooks) ? parsed.hooks : {} : parsed,
|
|
15219
|
-
converterConfig: DEVIN_CONVERTER_CONFIG
|
|
15578
|
+
hooks: this.getRelativeFilePath() === "config.json" ? isRecord$1(parsed) && isRecord$1(parsed.hooks) ? parsed.hooks : {} : parsed,
|
|
15579
|
+
converterConfig: DEVIN_CONVERTER_CONFIG,
|
|
15580
|
+
logger
|
|
15220
15581
|
});
|
|
15221
15582
|
return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
|
|
15222
15583
|
hooks,
|
|
@@ -15310,7 +15671,7 @@ var FactorydroidHooks = class FactorydroidHooks extends ToolHooks {
|
|
|
15310
15671
|
validate
|
|
15311
15672
|
});
|
|
15312
15673
|
}
|
|
15313
|
-
toRulesyncHooks() {
|
|
15674
|
+
toRulesyncHooks({ logger } = {}) {
|
|
15314
15675
|
let settings;
|
|
15315
15676
|
try {
|
|
15316
15677
|
settings = JSON.parse(this.getFileContent());
|
|
@@ -15319,7 +15680,8 @@ var FactorydroidHooks = class FactorydroidHooks extends ToolHooks {
|
|
|
15319
15680
|
}
|
|
15320
15681
|
const hooks = toolHooksToCanonical({
|
|
15321
15682
|
hooks: settings.hooks,
|
|
15322
|
-
converterConfig: FACTORYDROID_CONVERTER_CONFIG
|
|
15683
|
+
converterConfig: FACTORYDROID_CONVERTER_CONFIG,
|
|
15684
|
+
logger
|
|
15323
15685
|
});
|
|
15324
15686
|
return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
|
|
15325
15687
|
hooks,
|
|
@@ -15388,13 +15750,14 @@ var GooseHooks = class GooseHooks extends ToolHooks {
|
|
|
15388
15750
|
validate
|
|
15389
15751
|
});
|
|
15390
15752
|
}
|
|
15391
|
-
static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false }) {
|
|
15753
|
+
static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false, logger }) {
|
|
15392
15754
|
const paths = GooseHooks.getSettablePaths({ global });
|
|
15393
15755
|
const config = rulesyncHooks.getJson();
|
|
15394
15756
|
const gooseHooks = canonicalToToolHooks({
|
|
15395
15757
|
config,
|
|
15396
15758
|
toolOverrideHooks: config.goose?.hooks,
|
|
15397
|
-
converterConfig: GOOSE_CONVERTER_CONFIG
|
|
15759
|
+
converterConfig: GOOSE_CONVERTER_CONFIG,
|
|
15760
|
+
logger
|
|
15398
15761
|
});
|
|
15399
15762
|
const fileContent = JSON.stringify({ hooks: gooseHooks }, null, 2);
|
|
15400
15763
|
return new GooseHooks({
|
|
@@ -15405,7 +15768,7 @@ var GooseHooks = class GooseHooks extends ToolHooks {
|
|
|
15405
15768
|
validate
|
|
15406
15769
|
});
|
|
15407
15770
|
}
|
|
15408
|
-
toRulesyncHooks() {
|
|
15771
|
+
toRulesyncHooks({ logger } = {}) {
|
|
15409
15772
|
let parsed;
|
|
15410
15773
|
try {
|
|
15411
15774
|
parsed = JSON.parse(this.getFileContent());
|
|
@@ -15414,7 +15777,8 @@ var GooseHooks = class GooseHooks extends ToolHooks {
|
|
|
15414
15777
|
}
|
|
15415
15778
|
const hooks = toolHooksToCanonical({
|
|
15416
15779
|
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,
|
|
15417
|
-
converterConfig: GOOSE_CONVERTER_CONFIG
|
|
15780
|
+
converterConfig: GOOSE_CONVERTER_CONFIG,
|
|
15781
|
+
logger
|
|
15418
15782
|
});
|
|
15419
15783
|
return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
|
|
15420
15784
|
hooks,
|
|
@@ -15518,7 +15882,7 @@ var GrokcliHooks = class GrokcliHooks extends ToolHooks {
|
|
|
15518
15882
|
validate
|
|
15519
15883
|
});
|
|
15520
15884
|
}
|
|
15521
|
-
toRulesyncHooks() {
|
|
15885
|
+
toRulesyncHooks({ logger } = {}) {
|
|
15522
15886
|
let parsed;
|
|
15523
15887
|
try {
|
|
15524
15888
|
parsed = JSON.parse(this.getFileContent());
|
|
@@ -15526,8 +15890,9 @@ var GrokcliHooks = class GrokcliHooks extends ToolHooks {
|
|
|
15526
15890
|
throw new Error(`Failed to parse Grok hooks content in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
15527
15891
|
}
|
|
15528
15892
|
const hooks = toolHooksToCanonical({
|
|
15529
|
-
hooks: isRecord(parsed) && isRecord(parsed.hooks) ? parsed.hooks : {},
|
|
15530
|
-
converterConfig: GROKCLI_CONVERTER_CONFIG
|
|
15893
|
+
hooks: isRecord$1(parsed) && isRecord$1(parsed.hooks) ? parsed.hooks : {},
|
|
15894
|
+
converterConfig: GROKCLI_CONVERTER_CONFIG,
|
|
15895
|
+
logger
|
|
15531
15896
|
});
|
|
15532
15897
|
return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
|
|
15533
15898
|
hooks,
|
|
@@ -15848,7 +16213,7 @@ var JunieHooks = class JunieHooks extends ToolHooks {
|
|
|
15848
16213
|
validate
|
|
15849
16214
|
});
|
|
15850
16215
|
}
|
|
15851
|
-
toRulesyncHooks() {
|
|
16216
|
+
toRulesyncHooks({ logger } = {}) {
|
|
15852
16217
|
let settings;
|
|
15853
16218
|
try {
|
|
15854
16219
|
settings = JSON.parse(this.getFileContent());
|
|
@@ -15857,7 +16222,8 @@ var JunieHooks = class JunieHooks extends ToolHooks {
|
|
|
15857
16222
|
}
|
|
15858
16223
|
const hooks = toolHooksToCanonical({
|
|
15859
16224
|
hooks: settings.hooks,
|
|
15860
|
-
converterConfig: JUNIE_CONVERTER_CONFIG
|
|
16225
|
+
converterConfig: JUNIE_CONVERTER_CONFIG,
|
|
16226
|
+
logger
|
|
15861
16227
|
});
|
|
15862
16228
|
return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
|
|
15863
16229
|
hooks,
|
|
@@ -16595,7 +16961,7 @@ function buildKiroIdeEntriesForEvent(trigger, definitions) {
|
|
|
16595
16961
|
...def.matcher !== void 0 && def.matcher !== null && def.matcher !== "" && { matcher: def.matcher },
|
|
16596
16962
|
action,
|
|
16597
16963
|
...def.timeout !== void 0 && def.timeout !== null && def.timeout >= 0 && { timeout: def.timeout },
|
|
16598
|
-
enabled: true
|
|
16964
|
+
enabled: def.enabled ?? true
|
|
16599
16965
|
});
|
|
16600
16966
|
}
|
|
16601
16967
|
return entries;
|
|
@@ -16635,6 +17001,7 @@ function kiroIdeHooksToCanonical(entries) {
|
|
|
16635
17001
|
if (entry.description !== void 0 && entry.description !== null) def.description = entry.description;
|
|
16636
17002
|
if (entry.matcher !== void 0 && entry.matcher !== null && entry.matcher !== "") def.matcher = entry.matcher;
|
|
16637
17003
|
if (entry.timeout !== void 0 && entry.timeout !== null) def.timeout = entry.timeout;
|
|
17004
|
+
if (entry.enabled === false) def.enabled = false;
|
|
16638
17005
|
(canonical[eventName] ??= []).push(def);
|
|
16639
17006
|
}
|
|
16640
17007
|
return canonical;
|
|
@@ -17579,6 +17946,37 @@ function unsupportedEventNames(params) {
|
|
|
17579
17946
|
const eventNames = factory.passthroughOverrideEvents ? Object.keys(sharedHooks) : Object.keys(effectiveHooks);
|
|
17580
17947
|
return [...new Set(eventNames)].filter((e) => !supportedEvents.has(e));
|
|
17581
17948
|
}
|
|
17949
|
+
/**
|
|
17950
|
+
* A logger whose warnings name the tool being converted, matching the rest of
|
|
17951
|
+
* this processor's warnings: the shared hooks converter says what is wrong but
|
|
17952
|
+
* not which tool's file it is reading or writing, and one run walks them all.
|
|
17953
|
+
*
|
|
17954
|
+
* Every method delegates explicitly rather than through a prototype, so the
|
|
17955
|
+
* real logger keeps owning its state — a wrapper that inherited it would
|
|
17956
|
+
* absorb the writes `configure` and `outputJson` make.
|
|
17957
|
+
*/
|
|
17958
|
+
function withToolTargetPrefix({ logger, toolTarget }) {
|
|
17959
|
+
return {
|
|
17960
|
+
configure: (options) => logger.configure(options),
|
|
17961
|
+
get verbose() {
|
|
17962
|
+
return logger.verbose;
|
|
17963
|
+
},
|
|
17964
|
+
get silent() {
|
|
17965
|
+
return logger.silent;
|
|
17966
|
+
},
|
|
17967
|
+
get jsonMode() {
|
|
17968
|
+
return logger.jsonMode;
|
|
17969
|
+
},
|
|
17970
|
+
captureData: (key, value) => logger.captureData(key, value),
|
|
17971
|
+
getJsonData: () => logger.getJsonData(),
|
|
17972
|
+
outputJson: (success, error) => logger.outputJson(success, error),
|
|
17973
|
+
info: (message, ...args) => logger.info(message, ...args),
|
|
17974
|
+
success: (message, ...args) => logger.success(message, ...args),
|
|
17975
|
+
warn: (message, ...args) => logger.warn(`For ${toolTarget}: ${message}`, ...args),
|
|
17976
|
+
error: (message, code, ...args) => logger.error(message, code, ...args),
|
|
17977
|
+
debug: (message, ...args) => logger.debug(message, ...args)
|
|
17978
|
+
};
|
|
17979
|
+
}
|
|
17582
17980
|
function unsupportedMatcherEventNames({ factory, effectiveHooks }) {
|
|
17583
17981
|
if (factory.supportsMatcher && !factory.matcherEvents) return [];
|
|
17584
17982
|
const matcherEvents = factory.matcherEvents ? new Set(factory.matcherEvents) : void 0;
|
|
@@ -17801,13 +18199,13 @@ const toolHooksFactories = /* @__PURE__ */ new Map([
|
|
|
17801
18199
|
["deepagents", {
|
|
17802
18200
|
class: DeepagentsHooks,
|
|
17803
18201
|
meta: {
|
|
17804
|
-
supportsProject:
|
|
18202
|
+
supportsProject: true,
|
|
17805
18203
|
supportsGlobal: true,
|
|
17806
18204
|
supportsImport: true
|
|
17807
18205
|
},
|
|
17808
18206
|
supportedEvents: DEEPAGENTS_HOOK_EVENTS,
|
|
17809
18207
|
supportedHookTypes: ["command"],
|
|
17810
|
-
supportsMatcher:
|
|
18208
|
+
supportsMatcher: true
|
|
17811
18209
|
}],
|
|
17812
18210
|
["kiro", {
|
|
17813
18211
|
class: KiroHooks,
|
|
@@ -18019,6 +18417,15 @@ var HooksProcessor = class extends FeatureProcessor {
|
|
|
18019
18417
|
}
|
|
18020
18418
|
for (const [hookType, events] of unsupportedTypeToEvents) this.logger.warn(`Skipped ${hookType}-type hook(s) for ${this.toolTarget} (not supported): ${Array.from(events).join(", ")}`);
|
|
18021
18419
|
}
|
|
18420
|
+
if (this.toolTarget !== "kiro-ide") {
|
|
18421
|
+
const skippedEvents = new Set(unsupportedEventNames({
|
|
18422
|
+
factory,
|
|
18423
|
+
sharedHooks,
|
|
18424
|
+
effectiveHooks
|
|
18425
|
+
}));
|
|
18426
|
+
const eventsWithDisabledHooks = Object.entries(sharedHooks).filter(([event, defs]) => !skippedEvents.has(event) && defs.some((def) => def.enabled === false)).map(([event]) => event);
|
|
18427
|
+
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(", ")}`);
|
|
18428
|
+
}
|
|
18022
18429
|
const eventsWithUnsupportedMatcher = unsupportedMatcherEventNames({
|
|
18023
18430
|
factory,
|
|
18024
18431
|
effectiveHooks
|
|
@@ -18029,7 +18436,10 @@ var HooksProcessor = class extends FeatureProcessor {
|
|
|
18029
18436
|
rulesyncHooks,
|
|
18030
18437
|
validate: true,
|
|
18031
18438
|
global: this.global,
|
|
18032
|
-
logger:
|
|
18439
|
+
logger: withToolTargetPrefix({
|
|
18440
|
+
logger: this.logger,
|
|
18441
|
+
toolTarget: this.toolTarget
|
|
18442
|
+
})
|
|
18033
18443
|
})];
|
|
18034
18444
|
const auxiliaryFiles = await factory.class.getAuxiliaryFiles?.({
|
|
18035
18445
|
outputRoot: this.outputRoot,
|
|
@@ -18039,7 +18449,12 @@ var HooksProcessor = class extends FeatureProcessor {
|
|
|
18039
18449
|
return result;
|
|
18040
18450
|
}
|
|
18041
18451
|
async convertToolFilesToRulesyncFiles(toolFiles) {
|
|
18042
|
-
|
|
18452
|
+
const hooks = toolFiles.filter((f) => f instanceof ToolHooks);
|
|
18453
|
+
const logger = withToolTargetPrefix({
|
|
18454
|
+
logger: this.logger,
|
|
18455
|
+
toolTarget: this.toolTarget
|
|
18456
|
+
});
|
|
18457
|
+
return hooks.map((h) => h.toRulesyncHooks({ logger }));
|
|
18043
18458
|
}
|
|
18044
18459
|
static getToolTargets({ global = false, importOnly = false } = {}) {
|
|
18045
18460
|
if (global) return importOnly ? hooksProcessorToolTargetsGlobalImportable : hooksProcessorToolTargetsGlobal;
|
|
@@ -19682,9 +20097,9 @@ function parseAmpSettingsJsonc(fileContent) {
|
|
|
19682
20097
|
}
|
|
19683
20098
|
function filterMcpServers(mcpServers) {
|
|
19684
20099
|
const filtered = {};
|
|
19685
|
-
if (!isRecord(mcpServers)) return filtered;
|
|
20100
|
+
if (!isRecord$1(mcpServers)) return filtered;
|
|
19686
20101
|
for (const [name, config] of Object.entries(mcpServers)) {
|
|
19687
|
-
if (isPrototypePollutionKey(name) || !isRecord(config)) continue;
|
|
20102
|
+
if (isPrototypePollutionKey(name) || !isRecord$1(config)) continue;
|
|
19688
20103
|
const filteredConfig = {};
|
|
19689
20104
|
for (const [key, value] of Object.entries(config)) {
|
|
19690
20105
|
if (isPrototypePollutionKey(key)) continue;
|
|
@@ -19799,7 +20214,7 @@ var AmpMcp = class AmpMcp extends ToolMcp {
|
|
|
19799
20214
|
success: true,
|
|
19800
20215
|
error: null
|
|
19801
20216
|
};
|
|
19802
|
-
if (!isRecord(mcpServers)) return {
|
|
20217
|
+
if (!isRecord$1(mcpServers)) return {
|
|
19803
20218
|
success: false,
|
|
19804
20219
|
error: /* @__PURE__ */ new Error(`${AMP_MCP_SERVERS_KEY} must be a JSON object`)
|
|
19805
20220
|
};
|
|
@@ -19808,7 +20223,7 @@ var AmpMcp = class AmpMcp extends ToolMcp {
|
|
|
19808
20223
|
success: false,
|
|
19809
20224
|
error: /* @__PURE__ */ new Error(`Server name "${serverName}" is a prototype pollution key and is not allowed`)
|
|
19810
20225
|
};
|
|
19811
|
-
if (!isRecord(serverConfig)) return {
|
|
20226
|
+
if (!isRecord$1(serverConfig)) return {
|
|
19812
20227
|
success: false,
|
|
19813
20228
|
error: /* @__PURE__ */ new Error(`MCP server "${serverName}" must be a JSON object`)
|
|
19814
20229
|
};
|
|
@@ -20408,13 +20823,13 @@ function normalizeCodexMcpServerName(name) {
|
|
|
20408
20823
|
function convertFromCodexFormat(codexMcp) {
|
|
20409
20824
|
const result = {};
|
|
20410
20825
|
for (const [name, config] of Object.entries(codexMcp)) {
|
|
20411
|
-
if (PROTOTYPE_POLLUTION_KEYS.has(name) || !isRecord(config)) continue;
|
|
20826
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(name) || !isRecord$1(config)) continue;
|
|
20412
20827
|
const converted = {};
|
|
20413
20828
|
for (const [key, value] of Object.entries(config)) {
|
|
20414
20829
|
if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
20415
20830
|
if (key === "enabled") {
|
|
20416
20831
|
if (value === false) converted["disabled"] = true;
|
|
20417
|
-
} else if (key === "oauth" && isRecord(value)) converted[key] = mapOauthFromCodex(value);
|
|
20832
|
+
} else if (key === "oauth" && isRecord$1(value)) converted[key] = mapOauthFromCodex(value);
|
|
20418
20833
|
else if (Object.hasOwn(CODEX_TO_RULESYNC_FIELD_MAP, key)) {
|
|
20419
20834
|
const mappedKey = CODEX_TO_RULESYNC_FIELD_MAP[key];
|
|
20420
20835
|
if (mappedKey) if (isValidRenamedArray(key, value)) converted[mappedKey] = value;
|
|
@@ -20433,7 +20848,7 @@ function convertToCodexFormat(mcpServers) {
|
|
|
20433
20848
|
const result = {};
|
|
20434
20849
|
const originalNames = /* @__PURE__ */ new Map();
|
|
20435
20850
|
for (const [name, config] of Object.entries(mcpServers)) {
|
|
20436
|
-
if (!isRecord(config)) continue;
|
|
20851
|
+
if (!isRecord$1(config)) continue;
|
|
20437
20852
|
const { codexName, usedFallback } = normalizeCodexMcpServerName(name);
|
|
20438
20853
|
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.`);
|
|
20439
20854
|
const converted = {};
|
|
@@ -20441,7 +20856,7 @@ function convertToCodexFormat(mcpServers) {
|
|
|
20441
20856
|
if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
20442
20857
|
if (key === "disabled") {
|
|
20443
20858
|
if (value === true) converted["enabled"] = false;
|
|
20444
|
-
} else if (key === "oauth" && isRecord(value)) converted[key] = mapOauthToCodex(value);
|
|
20859
|
+
} else if (key === "oauth" && isRecord$1(value)) converted[key] = mapOauthToCodex(value);
|
|
20445
20860
|
else if (Object.hasOwn(RULESYNC_TO_CODEX_FIELD_MAP, key)) {
|
|
20446
20861
|
const mappedKey = RULESYNC_TO_CODEX_FIELD_MAP[key];
|
|
20447
20862
|
if (mappedKey) if (isValidRenamedArray(key, value)) converted[mappedKey] = value;
|
|
@@ -20517,21 +20932,21 @@ var CodexcliMcp = class CodexcliMcp extends ToolMcp {
|
|
|
20517
20932
|
const strippedMcpServers = rulesyncMcp.getMcpServers();
|
|
20518
20933
|
const rawMcpServers = rulesyncMcp.getJson().mcpServers;
|
|
20519
20934
|
const converted = convertToCodexFormat(Object.fromEntries(Object.entries(strippedMcpServers).map(([serverName, serverConfig]) => {
|
|
20520
|
-
const rawServer = isRecord(rawMcpServers) ? rawMcpServers[serverName] : void 0;
|
|
20935
|
+
const rawServer = isRecord$1(rawMcpServers) ? rawMcpServers[serverName] : void 0;
|
|
20521
20936
|
return [serverName, {
|
|
20522
20937
|
...serverConfig,
|
|
20523
|
-
...isRecord(rawServer) && isEnvVarEntryArray(rawServer.envVars) ? { envVars: rawServer.envVars } : {},
|
|
20524
|
-
...isRecord(rawServer) && typeof rawServer.experimental_environment === "string" ? { experimentalEnvironment: rawServer.experimental_environment } : {},
|
|
20525
|
-
...isRecord(rawServer) && typeof rawServer.experimentalEnvironment === "string" ? { experimentalEnvironment: rawServer.experimentalEnvironment } : {}
|
|
20938
|
+
...isRecord$1(rawServer) && isEnvVarEntryArray(rawServer.envVars) ? { envVars: rawServer.envVars } : {},
|
|
20939
|
+
...isRecord$1(rawServer) && typeof rawServer.experimental_environment === "string" ? { experimentalEnvironment: rawServer.experimental_environment } : {},
|
|
20940
|
+
...isRecord$1(rawServer) && typeof rawServer.experimentalEnvironment === "string" ? { experimentalEnvironment: rawServer.experimentalEnvironment } : {}
|
|
20526
20941
|
}];
|
|
20527
20942
|
})));
|
|
20528
20943
|
const filteredMcpServers = this.removeEmptyEntries(converted);
|
|
20529
20944
|
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`);
|
|
20530
|
-
const existingMcpServers = isRecord(configToml["mcp_servers"]) ? configToml["mcp_servers"] : {};
|
|
20945
|
+
const existingMcpServers = isRecord$1(configToml["mcp_servers"]) ? configToml["mcp_servers"] : {};
|
|
20531
20946
|
const mergedMcpServers = Object.fromEntries(Object.entries(filteredMcpServers).map(([name, serverConfig]) => {
|
|
20532
|
-
const existingServer = isRecord(existingMcpServers[name]) ? existingMcpServers[name] : void 0;
|
|
20947
|
+
const existingServer = isRecord$1(existingMcpServers[name]) ? existingMcpServers[name] : void 0;
|
|
20533
20948
|
const serverRecord = serverConfig;
|
|
20534
|
-
if (existingServer && isRecord(existingServer["tools"]) && !("tools" in serverRecord)) return [name, {
|
|
20949
|
+
if (existingServer && isRecord$1(existingServer["tools"]) && !("tools" in serverRecord)) return [name, {
|
|
20535
20950
|
...serverRecord,
|
|
20536
20951
|
tools: existingServer["tools"]
|
|
20537
20952
|
}];
|
|
@@ -21488,7 +21903,7 @@ function convertServerToGooseExtension(name, config, logger) {
|
|
|
21488
21903
|
function convertToGooseFormat({ mcpServers, existingExtensions, logger }) {
|
|
21489
21904
|
const generated = {};
|
|
21490
21905
|
for (const [name, config] of Object.entries(mcpServers)) {
|
|
21491
|
-
if (PROTOTYPE_POLLUTION_KEYS.has(name) || !isRecord(config)) continue;
|
|
21906
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(name) || !isRecord$1(config)) continue;
|
|
21492
21907
|
const ext = convertServerToGooseExtension(name, config, logger);
|
|
21493
21908
|
if (ext !== void 0) generated[name] = ext;
|
|
21494
21909
|
}
|
|
@@ -21496,7 +21911,7 @@ function convertToGooseFormat({ mcpServers, existingExtensions, logger }) {
|
|
|
21496
21911
|
const retracted = [];
|
|
21497
21912
|
for (const [name, ext] of Object.entries(existingExtensions)) {
|
|
21498
21913
|
if (PROTOTYPE_POLLUTION_KEYS.has(name)) continue;
|
|
21499
|
-
const type = isRecord(ext) ? existingExtensionType(ext) : void 0;
|
|
21914
|
+
const type = isRecord$1(ext) ? existingExtensionType(ext) : void 0;
|
|
21500
21915
|
if (type !== void 0 && GOOSE_MCP_EXTENSION_TYPES.has(type)) {
|
|
21501
21916
|
if (!Object.hasOwn(generated, name)) retracted.push(name);
|
|
21502
21917
|
continue;
|
|
@@ -21530,7 +21945,7 @@ function convertFromGooseFormat(extensions) {
|
|
|
21530
21945
|
const result = {};
|
|
21531
21946
|
const skipped = [];
|
|
21532
21947
|
for (const [name, ext] of Object.entries(extensions)) {
|
|
21533
|
-
if (PROTOTYPE_POLLUTION_KEYS.has(name) || !isRecord(ext)) continue;
|
|
21948
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(name) || !isRecord$1(ext)) continue;
|
|
21534
21949
|
const type = existingExtensionType(ext);
|
|
21535
21950
|
if (type === void 0 || !GOOSE_MCP_EXTENSION_TYPES.has(type)) {
|
|
21536
21951
|
skipped.push(name);
|
|
@@ -21584,7 +21999,7 @@ function buildGoosePluginStdioServer(config) {
|
|
|
21584
21999
|
function convertToGoosePluginMcpServers(mcpServers, logger) {
|
|
21585
22000
|
const result = {};
|
|
21586
22001
|
for (const [name, config] of Object.entries(mcpServers)) {
|
|
21587
|
-
if (PROTOTYPE_POLLUTION_KEYS.has(name) || !isRecord(config)) continue;
|
|
22002
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(name) || !isRecord$1(config)) continue;
|
|
21588
22003
|
const gooseType = resolveGooseType(config, resolveGooseUrl(config));
|
|
21589
22004
|
if (gooseType !== "stdio") {
|
|
21590
22005
|
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.`);
|
|
@@ -21625,7 +22040,7 @@ var GooseMcp = class GooseMcp extends ToolMcp {
|
|
|
21625
22040
|
parsePluginManifest(fileContent) {
|
|
21626
22041
|
try {
|
|
21627
22042
|
const parsed = JSON.parse(fileContent);
|
|
21628
|
-
return isRecord(parsed) ? parsed : {};
|
|
22043
|
+
return isRecord$1(parsed) ? parsed : {};
|
|
21629
22044
|
} catch (error) {
|
|
21630
22045
|
throw new Error(`Failed to parse Goose MCP manifest at ${join(this.relativeDirPath, this.relativeFilePath)}: ${formatError(error)}`, { cause: error });
|
|
21631
22046
|
}
|
|
@@ -21674,7 +22089,7 @@ var GooseMcp = class GooseMcp extends ToolMcp {
|
|
|
21674
22089
|
const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
21675
22090
|
const existingContent = await readFileContentOrNull(filePath) ?? "";
|
|
21676
22091
|
const config = parseGooseConfig(existingContent, paths.relativeDirPath, paths.relativeFilePath);
|
|
21677
|
-
const existingExtensions = isRecord(config.extensions) ? config.extensions : {};
|
|
22092
|
+
const existingExtensions = isRecord$1(config.extensions) ? config.extensions : {};
|
|
21678
22093
|
return new GooseMcp({
|
|
21679
22094
|
outputRoot,
|
|
21680
22095
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -21696,10 +22111,10 @@ var GooseMcp = class GooseMcp extends ToolMcp {
|
|
|
21696
22111
|
}
|
|
21697
22112
|
toRulesyncMcp() {
|
|
21698
22113
|
if (!this.global) {
|
|
21699
|
-
const mcpServers = isRecord(this.config.mcpServers) ? this.config.mcpServers : {};
|
|
22114
|
+
const mcpServers = isRecord$1(this.config.mcpServers) ? this.config.mcpServers : {};
|
|
21700
22115
|
return this.toRulesyncMcpDefault({ fileContent: JSON.stringify({ mcpServers }, null, 2) });
|
|
21701
22116
|
}
|
|
21702
|
-
const mcpServers = convertFromGooseFormat(isRecord(this.config.extensions) ? this.config.extensions : {});
|
|
22117
|
+
const mcpServers = convertFromGooseFormat(isRecord$1(this.config.extensions) ? this.config.extensions : {});
|
|
21703
22118
|
return this.toRulesyncMcpDefault({ fileContent: JSON.stringify({ mcpServers }, null, 2) });
|
|
21704
22119
|
}
|
|
21705
22120
|
validate() {
|
|
@@ -21735,7 +22150,7 @@ function convertToGrokFormat(mcpServers) {
|
|
|
21735
22150
|
const result = {};
|
|
21736
22151
|
for (const [name, config] of Object.entries(mcpServers)) {
|
|
21737
22152
|
if (PROTOTYPE_POLLUTION_KEYS.has(name)) continue;
|
|
21738
|
-
if (!isRecord(config)) continue;
|
|
22153
|
+
if (!isRecord$1(config)) continue;
|
|
21739
22154
|
const converted = {};
|
|
21740
22155
|
for (const [key, value] of Object.entries(config)) {
|
|
21741
22156
|
if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
@@ -21750,7 +22165,7 @@ function convertToGrokFormat(mcpServers) {
|
|
|
21750
22165
|
function convertFromGrokFormat(grokMcp) {
|
|
21751
22166
|
const result = {};
|
|
21752
22167
|
for (const [name, config] of Object.entries(grokMcp)) {
|
|
21753
|
-
if (PROTOTYPE_POLLUTION_KEYS.has(name) || !isRecord(config)) continue;
|
|
22168
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(name) || !isRecord$1(config)) continue;
|
|
21754
22169
|
const converted = {};
|
|
21755
22170
|
for (const [key, value] of Object.entries(config)) {
|
|
21756
22171
|
if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
@@ -21888,7 +22303,7 @@ function resolveHermesTimeout(config) {
|
|
|
21888
22303
|
* import alike. See the Hermes mcp-config-reference.
|
|
21889
22304
|
*/
|
|
21890
22305
|
function copyHermesOauth(source) {
|
|
21891
|
-
if (!isRecord(source)) return;
|
|
22306
|
+
if (!isRecord$1(source)) return;
|
|
21892
22307
|
const oauth = {};
|
|
21893
22308
|
for (const key of [
|
|
21894
22309
|
"redirect_uri",
|
|
@@ -22022,13 +22437,13 @@ function convertServerToHermes(config) {
|
|
|
22022
22437
|
function convertToHermesFormat(mcpServers) {
|
|
22023
22438
|
const result = {};
|
|
22024
22439
|
for (const [name, config] of Object.entries(mcpServers)) {
|
|
22025
|
-
if (PROTOTYPE_POLLUTION_KEYS.has(name) || !isRecord(config)) continue;
|
|
22440
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(name) || !isRecord$1(config)) continue;
|
|
22026
22441
|
result[name] = convertServerToHermes(config);
|
|
22027
22442
|
}
|
|
22028
22443
|
return result;
|
|
22029
22444
|
}
|
|
22030
22445
|
function mergeHermesMcpServers(config, mcpServers) {
|
|
22031
|
-
const existingMcpServers = isRecord(config.mcp_servers) ? config.mcp_servers : {};
|
|
22446
|
+
const existingMcpServers = isRecord$1(config.mcp_servers) ? config.mcp_servers : {};
|
|
22032
22447
|
return {
|
|
22033
22448
|
...config,
|
|
22034
22449
|
mcp_servers: {
|
|
@@ -22047,7 +22462,7 @@ function convertFromHermesFormat(mcpServers) {
|
|
|
22047
22462
|
const result = {};
|
|
22048
22463
|
const hermesOverrides = {};
|
|
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
|
const server = {};
|
|
22052
22467
|
if (typeof config.command === "string") server.command = config.command;
|
|
22053
22468
|
if (isStringArray$1(config.args)) server.args = config.args;
|
|
@@ -22056,7 +22471,7 @@ function convertFromHermesFormat(mcpServers) {
|
|
|
22056
22471
|
if (isPlainObject$1(config.headers)) server.headers = omitPrototypePollutionKeys(config.headers);
|
|
22057
22472
|
if (config.enabled === false) server.disabled = true;
|
|
22058
22473
|
if (typeof config.timeout === "number") server.networkTimeout = config.timeout;
|
|
22059
|
-
if (isRecord(config.tools)) applyHermesToolsBlock(config.tools, server);
|
|
22474
|
+
if (isRecord$1(config.tools)) applyHermesToolsBlock(config.tools, server);
|
|
22060
22475
|
result[name] = server;
|
|
22061
22476
|
const hermesServer = { ...server };
|
|
22062
22477
|
if (copyHermesAdvancedFields(config, hermesServer)) hermesOverrides[name] = hermesServer;
|
|
@@ -22095,7 +22510,7 @@ var HermesagentMcp = class HermesagentMcp extends ToolMcp {
|
|
|
22095
22510
|
const merged = mergeHermesMcpServers(parseSharedConfig({
|
|
22096
22511
|
format: "yaml",
|
|
22097
22512
|
fileContent
|
|
22098
|
-
}), isRecord(this.config.mcp_servers) ? this.config.mcp_servers : {});
|
|
22513
|
+
}), isRecord$1(this.config.mcp_servers) ? this.config.mcp_servers : {});
|
|
22099
22514
|
this.config = merged;
|
|
22100
22515
|
super.setFileContent(applySharedConfigPatch({
|
|
22101
22516
|
fileKey: getHermesagentConfigSharedFileKey({ global: this.global }),
|
|
@@ -22159,7 +22574,7 @@ var HermesagentMcp = class HermesagentMcp extends ToolMcp {
|
|
|
22159
22574
|
});
|
|
22160
22575
|
}
|
|
22161
22576
|
toRulesyncMcp() {
|
|
22162
|
-
const { mcpServers: servers, hermesOverrides } = convertFromHermesFormat(isRecord(this.config.mcp_servers) ? this.config.mcp_servers : {});
|
|
22577
|
+
const { mcpServers: servers, hermesOverrides } = convertFromHermesFormat(isRecord$1(this.config.mcp_servers) ? this.config.mcp_servers : {});
|
|
22163
22578
|
return this.toRulesyncMcpDefault({
|
|
22164
22579
|
outputRoot: getHermesagentRulesyncOutputRoot({
|
|
22165
22580
|
nativeOutputRoot: this.outputRoot,
|
|
@@ -22471,7 +22886,7 @@ function convertServerToKiloFormat(serverName, serverConfig, existingEntry, logg
|
|
|
22471
22886
|
*/
|
|
22472
22887
|
function readExistingKiloMcpEntries(fileContent) {
|
|
22473
22888
|
const mcp = parse(fileContent || "{}")?.mcp;
|
|
22474
|
-
if (!isRecord(mcp)) return {};
|
|
22889
|
+
if (!isRecord$1(mcp)) return {};
|
|
22475
22890
|
const entries = {};
|
|
22476
22891
|
for (const [serverName, entry] of Object.entries(mcp)) {
|
|
22477
22892
|
const result = KiloMcpServerSchema.safeParse(entry);
|
|
@@ -22775,7 +23190,7 @@ async function readKimiCodeConfig({ outputRoot }) {
|
|
|
22775
23190
|
return {
|
|
22776
23191
|
parsed: true,
|
|
22777
23192
|
content,
|
|
22778
|
-
mcp: isRecord(mcp) ? mcp : {}
|
|
23193
|
+
mcp: isRecord$1(mcp) ? mcp : {}
|
|
22779
23194
|
};
|
|
22780
23195
|
} catch {
|
|
22781
23196
|
return {
|
|
@@ -22929,7 +23344,7 @@ var KimiCodeMcp = class KimiCodeMcp extends ToolMcp {
|
|
|
22929
23344
|
static async getAuxiliaryFiles({ outputRoot = process.cwd(), global = false, rulesyncMcp, logger }) {
|
|
22930
23345
|
if (!global) return [];
|
|
22931
23346
|
const block = rulesyncMcp.getJson()["kimi-code"];
|
|
22932
|
-
if (!isRecord(block)) return [];
|
|
23347
|
+
if (!isRecord$1(block)) return [];
|
|
22933
23348
|
const startupTimeoutMs = typeof block.startupTimeoutMs === "number" ? block.startupTimeoutMs : void 0;
|
|
22934
23349
|
const toolTimeoutMs = typeof block.toolTimeoutMs === "number" ? block.toolTimeoutMs : void 0;
|
|
22935
23350
|
if (startupTimeoutMs === void 0 && toolTimeoutMs === void 0) return [];
|
|
@@ -22982,6 +23397,68 @@ var KimiCodeMcp = class KimiCodeMcp extends ToolMcp {
|
|
|
22982
23397
|
};
|
|
22983
23398
|
//#endregion
|
|
22984
23399
|
//#region src/features/mcp/kiro-mcp.ts
|
|
23400
|
+
/**
|
|
23401
|
+
* Union of two optional string lists, preserving order and dropping duplicates.
|
|
23402
|
+
* Returns `undefined` only when neither side was authored at all, so the caller
|
|
23403
|
+
* omits the key entirely rather than writing an empty array — but an explicitly
|
|
23404
|
+
* authored `[]` is kept, which keeps import → generate idempotent.
|
|
23405
|
+
*/
|
|
23406
|
+
function mergeToolLists(...lists) {
|
|
23407
|
+
if (lists.every((list) => list === void 0)) return void 0;
|
|
23408
|
+
const merged = [];
|
|
23409
|
+
for (const list of lists) for (const tool of list ?? []) if (!merged.includes(tool)) merged.push(tool);
|
|
23410
|
+
return merged;
|
|
23411
|
+
}
|
|
23412
|
+
/**
|
|
23413
|
+
* Translate rulesync's Kiro-only authoring keys onto the field names Kiro
|
|
23414
|
+
* actually reads in `mcp.json`.
|
|
23415
|
+
*
|
|
23416
|
+
* - `kiroAutoApprove` → `autoApprove` (tools run without a confirmation prompt)
|
|
23417
|
+
* - `kiroAutoBlock` → `disabledTools` (tools hidden from the agent)
|
|
23418
|
+
*
|
|
23419
|
+
* `disabledTools` is the only block list Kiro reads, and it is also a canonical
|
|
23420
|
+
* rulesync field, so `kiroAutoBlock` is a redundant spelling of it. Prefer the
|
|
23421
|
+
* canonical field; see the note on `kiroAutoBlock` in `src/types/mcp.ts`.
|
|
23422
|
+
*
|
|
23423
|
+
* Both native names are documented per-server fields, so a config that already
|
|
23424
|
+
* spells them natively keeps working: the two lists are merged rather than
|
|
23425
|
+
* one overwriting the other.
|
|
23426
|
+
* @see https://kiro.dev/docs/mcp/configuration/
|
|
23427
|
+
*/
|
|
23428
|
+
function toKiroMcpServers(servers) {
|
|
23429
|
+
return Object.fromEntries(Object.entries(servers).map(([name, server]) => {
|
|
23430
|
+
const { kiroAutoApprove, kiroAutoBlock, disabledTools, ...rest } = server;
|
|
23431
|
+
const autoApprove = mergeToolLists(isStringArray$1(rest.autoApprove) ? rest.autoApprove : void 0, kiroAutoApprove);
|
|
23432
|
+
const disabled = mergeToolLists(disabledTools, kiroAutoBlock);
|
|
23433
|
+
return [name, {
|
|
23434
|
+
...rest,
|
|
23435
|
+
...autoApprove !== void 0 && { autoApprove },
|
|
23436
|
+
...disabled !== void 0 && { disabledTools: disabled }
|
|
23437
|
+
}];
|
|
23438
|
+
}));
|
|
23439
|
+
}
|
|
23440
|
+
/**
|
|
23441
|
+
* Import direction of {@link toKiroMcpServers}: Kiro's `autoApprove` becomes the
|
|
23442
|
+
* rulesync-only `kiroAutoApprove` so a regenerate reproduces it. `disabledTools`
|
|
23443
|
+
* is left alone — it is already a canonical rulesync key with the same meaning,
|
|
23444
|
+
* so `kiroAutoBlock` deliberately has no import counterpart.
|
|
23445
|
+
*
|
|
23446
|
+
* Only a genuine string array is renamed. `kiroAutoApprove` is typed as one, so
|
|
23447
|
+
* moving a hand-written `"autoApprove": "all"` there would produce a
|
|
23448
|
+
* `.rulesync/mcp.jsonc` the next generate refuses to parse; such a value stays
|
|
23449
|
+
* under its original key and passes through untouched instead.
|
|
23450
|
+
*/
|
|
23451
|
+
function fromKiroMcpServers(servers) {
|
|
23452
|
+
return Object.fromEntries(Object.entries(servers).map(([name, server]) => {
|
|
23453
|
+
if (server === null || typeof server !== "object" || Array.isArray(server)) return [name, server];
|
|
23454
|
+
const { autoApprove, ...rest } = server;
|
|
23455
|
+
if (!isStringArray$1(autoApprove)) return [name, server];
|
|
23456
|
+
return [name, {
|
|
23457
|
+
...rest,
|
|
23458
|
+
kiroAutoApprove: autoApprove
|
|
23459
|
+
}];
|
|
23460
|
+
}));
|
|
23461
|
+
}
|
|
22985
23462
|
var KiroMcp = class KiroMcp extends ToolMcp {
|
|
22986
23463
|
json;
|
|
22987
23464
|
constructor(params) {
|
|
@@ -23010,7 +23487,7 @@ var KiroMcp = class KiroMcp extends ToolMcp {
|
|
|
23010
23487
|
}
|
|
23011
23488
|
static fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true }) {
|
|
23012
23489
|
const paths = this.getSettablePaths();
|
|
23013
|
-
const fileContent = JSON.stringify({ mcpServers: rulesyncMcp.getMcpServers() }, null, 2);
|
|
23490
|
+
const fileContent = JSON.stringify({ mcpServers: toKiroMcpServers(rulesyncMcp.getMcpServers()) }, null, 2);
|
|
23014
23491
|
return new KiroMcp({
|
|
23015
23492
|
outputRoot,
|
|
23016
23493
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -23020,7 +23497,9 @@ var KiroMcp = class KiroMcp extends ToolMcp {
|
|
|
23020
23497
|
});
|
|
23021
23498
|
}
|
|
23022
23499
|
toRulesyncMcp() {
|
|
23023
|
-
|
|
23500
|
+
const mcpServers = this.json.mcpServers;
|
|
23501
|
+
const translated = isMcpServers(mcpServers) ? fromKiroMcpServers(mcpServers) : {};
|
|
23502
|
+
return this.toRulesyncMcpDefault({ fileContent: JSON.stringify({ mcpServers: translated }, null, 2) });
|
|
23024
23503
|
}
|
|
23025
23504
|
validate() {
|
|
23026
23505
|
return {
|
|
@@ -23906,7 +24385,7 @@ async function readRovodevConfigYaml({ outputRoot }) {
|
|
|
23906
24385
|
});
|
|
23907
24386
|
}
|
|
23908
24387
|
function disabledNamesOf(config) {
|
|
23909
|
-
const mcpBlock = config && isRecord(config.mcp) ? config.mcp : {};
|
|
24388
|
+
const mcpBlock = config && isRecord$1(config.mcp) ? config.mcp : {};
|
|
23910
24389
|
return isStringArray$1(mcpBlock.disabledMcpServers) ? mcpBlock.disabledMcpServers : [];
|
|
23911
24390
|
}
|
|
23912
24391
|
/**
|
|
@@ -24025,7 +24504,7 @@ var RovodevMcp = class RovodevMcp extends ToolMcp {
|
|
|
24025
24504
|
const managedNames = Object.keys(servers).filter((name) => toRovodevServer(name, servers[name]) !== null);
|
|
24026
24505
|
const disabledNames = managedNames.filter((name) => {
|
|
24027
24506
|
const server = servers[name];
|
|
24028
|
-
return isRecord(server) && server.disabled === true;
|
|
24507
|
+
return isRecord$1(server) && server.disabled === true;
|
|
24029
24508
|
});
|
|
24030
24509
|
const existingContent = await readFileContentOrNull(join(outputRoot, ROVODEV_DIR, ROVODEV_CONFIG_FILE_NAME)) ?? "";
|
|
24031
24510
|
let existingParsed;
|
|
@@ -24039,7 +24518,7 @@ var RovodevMcp = class RovodevMcp extends ToolMcp {
|
|
|
24039
24518
|
logger?.warn(`Skipping the Rovo Dev mcp.disabledMcpServers update: ${formatError(error)}`);
|
|
24040
24519
|
return [];
|
|
24041
24520
|
}
|
|
24042
|
-
const existingMcp = isRecord(existingParsed.mcp) ? { ...existingParsed.mcp } : {};
|
|
24521
|
+
const existingMcp = isRecord$1(existingParsed.mcp) ? { ...existingParsed.mcp } : {};
|
|
24043
24522
|
const existingDisabled = isStringArray$1(existingMcp.disabledMcpServers) ? existingMcp.disabledMcpServers : [];
|
|
24044
24523
|
const managedNameSet = new Set(managedNames);
|
|
24045
24524
|
const reEnabled = existingDisabled.filter((name) => managedNameSet.has(name) && !disabledNames.includes(name));
|
|
@@ -24231,7 +24710,7 @@ function deriveTransportAllowlist(servers) {
|
|
|
24231
24710
|
http: false
|
|
24232
24711
|
};
|
|
24233
24712
|
for (const server of Object.values(servers)) {
|
|
24234
|
-
if (!isRecord(server)) continue;
|
|
24713
|
+
if (!isRecord$1(server)) continue;
|
|
24235
24714
|
const transport = transportOf(server);
|
|
24236
24715
|
if (transport) allowlist[transport] = true;
|
|
24237
24716
|
}
|
|
@@ -27866,13 +28345,13 @@ const CURSOR_TYPE_TO_CANONICAL = {
|
|
|
27866
28345
|
WebFetch: "webfetch",
|
|
27867
28346
|
Mcp: "mcp"
|
|
27868
28347
|
};
|
|
27869
|
-
const MCP_CANONICAL_PREFIX$
|
|
28348
|
+
const MCP_CANONICAL_PREFIX$2 = "mcp__";
|
|
27870
28349
|
/**
|
|
27871
28350
|
* Returns true if the canonical category is the per-tool MCP form
|
|
27872
28351
|
* `mcp__<server>__<tool>`.
|
|
27873
28352
|
*/
|
|
27874
28353
|
function isMcpScopedCategory(canonical) {
|
|
27875
|
-
return canonical.startsWith(MCP_CANONICAL_PREFIX$
|
|
28354
|
+
return canonical.startsWith(MCP_CANONICAL_PREFIX$2) && canonical.length > 5;
|
|
27876
28355
|
}
|
|
27877
28356
|
function toCursorType(canonical) {
|
|
27878
28357
|
if (isMcpScopedCategory(canonical)) return "Mcp";
|
|
@@ -27906,7 +28385,7 @@ function toCanonicalCategory$1(cursorType, pattern) {
|
|
|
27906
28385
|
if (match) {
|
|
27907
28386
|
const server = match[1] ?? "*";
|
|
27908
28387
|
const tool = match[2] ?? "*";
|
|
27909
|
-
return `${MCP_CANONICAL_PREFIX$
|
|
28388
|
+
return `${MCP_CANONICAL_PREFIX$2}${server}__${tool}`;
|
|
27910
28389
|
}
|
|
27911
28390
|
return CURSOR_TYPE_TO_CANONICAL[cursorType] ?? cursorType.toLowerCase();
|
|
27912
28391
|
}
|
|
@@ -28135,7 +28614,7 @@ function convertCursorToRulesyncPermissions(params) {
|
|
|
28135
28614
|
const { type, pattern } = parseCursorPermissionEntry(entry);
|
|
28136
28615
|
const canonical = toCanonicalCategory$1(type, pattern);
|
|
28137
28616
|
if (!permission[canonical]) permission[canonical] = {};
|
|
28138
|
-
const canonicalPattern = type === "Mcp" && canonical.startsWith(MCP_CANONICAL_PREFIX$
|
|
28617
|
+
const canonicalPattern = type === "Mcp" && canonical.startsWith(MCP_CANONICAL_PREFIX$2) ? "*" : pattern;
|
|
28139
28618
|
permission[canonical][canonicalPattern] = action;
|
|
28140
28619
|
}
|
|
28141
28620
|
};
|
|
@@ -28270,14 +28749,14 @@ var DevinPermissions = class DevinPermissions extends ToolPermissions {
|
|
|
28270
28749
|
let settings;
|
|
28271
28750
|
try {
|
|
28272
28751
|
const parsed = JSON.parse(existingContent);
|
|
28273
|
-
settings = isRecord(parsed) ? parsed : {};
|
|
28752
|
+
settings = isRecord$1(parsed) ? parsed : {};
|
|
28274
28753
|
} catch (error) {
|
|
28275
28754
|
throw new Error(`Failed to parse existing Devin config at ${filePath}: ${formatError(error)}`, { cause: error });
|
|
28276
28755
|
}
|
|
28277
28756
|
const config = rulesyncPermissions.getJson();
|
|
28278
28757
|
const { allow, ask, deny } = convertRulesyncToDevinPermissions(config);
|
|
28279
28758
|
const managedScopes = new Set(Object.keys(config.permission).map((category) => toDevinScope(category)));
|
|
28280
|
-
const existingPermissions = isRecord(settings.permissions) ? settings.permissions : {};
|
|
28759
|
+
const existingPermissions = isRecord$1(settings.permissions) ? settings.permissions : {};
|
|
28281
28760
|
const preserve = (entries) => (entries ?? []).filter((entry) => !managedScopes.has(parseDevinPermissionEntry(entry).scope));
|
|
28282
28761
|
const mergedAllow = uniq([...preserve(existingPermissions.allow), ...allow].toSorted());
|
|
28283
28762
|
const mergedAsk = uniq([...preserve(existingPermissions.ask), ...ask].toSorted());
|
|
@@ -28307,11 +28786,11 @@ var DevinPermissions = class DevinPermissions extends ToolPermissions {
|
|
|
28307
28786
|
let settings;
|
|
28308
28787
|
try {
|
|
28309
28788
|
const parsed = JSON.parse(this.getFileContent());
|
|
28310
|
-
settings = isRecord(parsed) ? parsed : {};
|
|
28789
|
+
settings = isRecord$1(parsed) ? parsed : {};
|
|
28311
28790
|
} catch (error) {
|
|
28312
28791
|
throw new Error(`Failed to parse Devin permissions content in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
28313
28792
|
}
|
|
28314
|
-
const permissions = isRecord(settings.permissions) ? settings.permissions : {};
|
|
28793
|
+
const permissions = isRecord$1(settings.permissions) ? settings.permissions : {};
|
|
28315
28794
|
const config = convertDevinToRulesyncPermissions({
|
|
28316
28795
|
allow: Array.isArray(permissions.allow) ? permissions.allow : [],
|
|
28317
28796
|
ask: Array.isArray(permissions.ask) ? permissions.ask : [],
|
|
@@ -28660,7 +29139,7 @@ var GoosePermissions = class GoosePermissions extends ToolPermissions {
|
|
|
28660
29139
|
} catch (error) {
|
|
28661
29140
|
throw new Error(`Failed to parse existing Goose permission.yaml at ${filePath}: ${formatError(error)}`, { cause: error });
|
|
28662
29141
|
}
|
|
28663
|
-
const config = isRecord(parsed) ? { ...parsed } : {};
|
|
29142
|
+
const config = isRecord$1(parsed) ? { ...parsed } : {};
|
|
28664
29143
|
const userPermission = convertRulesyncToGoosePermissionConfig({
|
|
28665
29144
|
config: rulesyncPermissions.getJson(),
|
|
28666
29145
|
logger
|
|
@@ -28683,8 +29162,8 @@ var GoosePermissions = class GoosePermissions extends ToolPermissions {
|
|
|
28683
29162
|
} catch (error) {
|
|
28684
29163
|
throw new Error(`Failed to parse Goose permissions content in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
28685
29164
|
}
|
|
28686
|
-
const config = isRecord(parsed) ? parsed : {};
|
|
28687
|
-
const rulesyncConfig = convertGoosePermissionConfigToRulesync(isRecord(config[GOOSE_USER_KEY]) ? config[GOOSE_USER_KEY] : {});
|
|
29165
|
+
const config = isRecord$1(parsed) ? parsed : {};
|
|
29166
|
+
const rulesyncConfig = convertGoosePermissionConfigToRulesync(isRecord$1(config[GOOSE_USER_KEY]) ? config[GOOSE_USER_KEY] : {});
|
|
28688
29167
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(rulesyncConfig, null, 2) });
|
|
28689
29168
|
}
|
|
28690
29169
|
validate() {
|
|
@@ -28758,7 +29237,7 @@ const GROKCLI_UI_KEY = "ui";
|
|
|
28758
29237
|
const GROKCLI_PERMISSION_MODE_KEY = "permission_mode";
|
|
28759
29238
|
const GROKCLI_PERMISSION_KEY = "permission";
|
|
28760
29239
|
const CATCH_ALL_PATTERN$2 = "*";
|
|
28761
|
-
const MCP_CANONICAL_PREFIX = "mcp__";
|
|
29240
|
+
const MCP_CANONICAL_PREFIX$1 = "mcp__";
|
|
28762
29241
|
const CATEGORY_TO_GROK_TOOL = {
|
|
28763
29242
|
bash: "Bash",
|
|
28764
29243
|
read: "Read",
|
|
@@ -28786,7 +29265,7 @@ const GROK_MCP_TOOL = "MCPTool";
|
|
|
28786
29265
|
* concrete pattern emits `Tool(pattern)`.
|
|
28787
29266
|
*/
|
|
28788
29267
|
function buildGrokEntry(category, pattern) {
|
|
28789
|
-
if (category.startsWith(MCP_CANONICAL_PREFIX)) {
|
|
29268
|
+
if (category.startsWith(MCP_CANONICAL_PREFIX$1)) {
|
|
28790
29269
|
const remainder = category.slice(5);
|
|
28791
29270
|
return remainder.length > 0 ? `${GROK_MCP_TOOL}(${remainder})` : GROK_MCP_TOOL;
|
|
28792
29271
|
}
|
|
@@ -28813,7 +29292,7 @@ function parseGrokEntry(entry) {
|
|
|
28813
29292
|
inner = trimmed.slice(parenIndex + 1, -1).trim();
|
|
28814
29293
|
}
|
|
28815
29294
|
if (tool === GROK_MCP_TOOL) return inner.length > 0 ? {
|
|
28816
|
-
category: `${MCP_CANONICAL_PREFIX}${inner}`,
|
|
29295
|
+
category: `${MCP_CANONICAL_PREFIX$1}${inner}`,
|
|
28817
29296
|
pattern: CATCH_ALL_PATTERN$2
|
|
28818
29297
|
} : {
|
|
28819
29298
|
category: "mcp",
|
|
@@ -28911,7 +29390,7 @@ var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
|
|
|
28911
29390
|
throw new Error(`Failed to parse existing Grok config.toml at ${filePath}: ${formatError(error)}`, { cause: error });
|
|
28912
29391
|
}
|
|
28913
29392
|
const config = rulesyncPermissions.getJson();
|
|
28914
|
-
const existingPermission = isRecord(parsed[GROKCLI_PERMISSION_KEY]) ? parsed[GROKCLI_PERMISSION_KEY] : {};
|
|
29393
|
+
const existingPermission = isRecord$1(parsed[GROKCLI_PERMISSION_KEY]) ? parsed[GROKCLI_PERMISSION_KEY] : {};
|
|
28915
29394
|
const buckets = buildGrokPermissionArrays(config, existingPermission, logger);
|
|
28916
29395
|
const permission = {
|
|
28917
29396
|
...existingPermission,
|
|
@@ -28920,7 +29399,7 @@ var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
|
|
|
28920
29399
|
ask: buckets.ask
|
|
28921
29400
|
};
|
|
28922
29401
|
const uiPatch = global ? { [GROKCLI_UI_KEY]: {
|
|
28923
|
-
...isRecord(parsed[GROKCLI_UI_KEY]) ? parsed[GROKCLI_UI_KEY] : {},
|
|
29402
|
+
...isRecord$1(parsed[GROKCLI_UI_KEY]) ? parsed[GROKCLI_UI_KEY] : {},
|
|
28924
29403
|
[GROKCLI_PERMISSION_MODE_KEY]: deriveGrokPermissionMode(config)
|
|
28925
29404
|
} } : {};
|
|
28926
29405
|
return new GrokcliPermissions({
|
|
@@ -28949,7 +29428,7 @@ var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
|
|
|
28949
29428
|
} catch (error) {
|
|
28950
29429
|
throw new Error(`Failed to parse Grok config.toml content in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
28951
29430
|
}
|
|
28952
|
-
const fineGrained = parseGrokPermissionArrays(isRecord(parsed[GROKCLI_PERMISSION_KEY]) ? parsed[GROKCLI_PERMISSION_KEY] : {});
|
|
29431
|
+
const fineGrained = parseGrokPermissionArrays(isRecord$1(parsed[GROKCLI_PERMISSION_KEY]) ? parsed[GROKCLI_PERMISSION_KEY] : {});
|
|
28953
29432
|
const rulesyncConfig = fineGrained ? { permission: fineGrained } : { permission: { bash: { [CATCH_ALL_PATTERN$2]: legacyModeAction(parsed) } } };
|
|
28954
29433
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(rulesyncConfig, null, 2) });
|
|
28955
29434
|
}
|
|
@@ -29046,7 +29525,7 @@ function parseGrokPermissionArrays(permission) {
|
|
|
29046
29525
|
* `always-approve` ⇒ `allow`; anything else (including a missing mode) ⇒ `ask`.
|
|
29047
29526
|
*/
|
|
29048
29527
|
function legacyModeAction(parsed) {
|
|
29049
|
-
return (isRecord(parsed[GROKCLI_UI_KEY]) ? parsed[GROKCLI_UI_KEY] : {})[GROKCLI_PERMISSION_MODE_KEY] === "always-approve" ? "allow" : "ask";
|
|
29528
|
+
return (isRecord$1(parsed[GROKCLI_UI_KEY]) ? parsed[GROKCLI_UI_KEY] : {})[GROKCLI_PERMISSION_MODE_KEY] === "always-approve" ? "allow" : "ask";
|
|
29050
29529
|
}
|
|
29051
29530
|
/**
|
|
29052
29531
|
* Collapse a rulesync permissions config into Grok's single coarse mode.
|
|
@@ -29102,12 +29581,12 @@ function withoutKey(record, key) {
|
|
|
29102
29581
|
return Object.fromEntries(Object.entries(record).filter(([entryKey]) => entryKey !== key));
|
|
29103
29582
|
}
|
|
29104
29583
|
function buildHermesOverride(config, provenance) {
|
|
29105
|
-
const base = isRecord(provenance.hermes) ? { ...provenance.hermes } : {};
|
|
29106
|
-
const approvalsOverride = withoutKey(isRecord(config.approvals) ? config.approvals : {}, "deny");
|
|
29584
|
+
const base = isRecord$1(provenance.hermes) ? { ...provenance.hermes } : {};
|
|
29585
|
+
const approvalsOverride = withoutKey(isRecord$1(config.approvals) ? config.approvals : {}, "deny");
|
|
29107
29586
|
if (Object.keys(approvalsOverride).length > 0) base.approvals = approvalsOverride;
|
|
29108
29587
|
else delete base.approvals;
|
|
29109
|
-
const security = isRecord(config.security) ? { ...config.security } : {};
|
|
29110
|
-
const blocklist = isRecord(security.website_blocklist) ? { ...security.website_blocklist } : void 0;
|
|
29588
|
+
const security = isRecord$1(config.security) ? { ...config.security } : {};
|
|
29589
|
+
const blocklist = isRecord$1(security.website_blocklist) ? { ...security.website_blocklist } : void 0;
|
|
29111
29590
|
if (blocklist?.enabled === true) {
|
|
29112
29591
|
delete blocklist.domains;
|
|
29113
29592
|
delete blocklist.enabled;
|
|
@@ -29116,7 +29595,7 @@ function buildHermesOverride(config, provenance) {
|
|
|
29116
29595
|
else delete security.website_blocklist;
|
|
29117
29596
|
if (Object.keys(security).length > 0) base.security = security;
|
|
29118
29597
|
else delete base.security;
|
|
29119
|
-
for (const key of ["skills", "memory"]) if (isRecord(config[key])) base[key] = config[key];
|
|
29598
|
+
for (const key of ["skills", "memory"]) if (isRecord$1(config[key])) base[key] = config[key];
|
|
29120
29599
|
else delete base[key];
|
|
29121
29600
|
return base;
|
|
29122
29601
|
}
|
|
@@ -29188,7 +29667,7 @@ var HermesagentPermissions = class HermesagentPermissions extends ToolPermission
|
|
|
29188
29667
|
format: "yaml",
|
|
29189
29668
|
fileContent: this.getFileContent()
|
|
29190
29669
|
});
|
|
29191
|
-
const permissionsRoot = isRecord(config.permissions) ? config.permissions : {};
|
|
29670
|
+
const permissionsRoot = isRecord$1(config.permissions) ? config.permissions : {};
|
|
29192
29671
|
const parsedProvenance = RulesyncPermissionsFileSchema.safeParse(permissionsRoot.rulesync);
|
|
29193
29672
|
const provenance = parsedProvenance.success ? parsedProvenance.data : { permission: {} };
|
|
29194
29673
|
const permission = clonePermissionBlock(provenance.permission);
|
|
@@ -29196,14 +29675,14 @@ var HermesagentPermissions = class HermesagentPermissions extends ToolPermission
|
|
|
29196
29675
|
permission,
|
|
29197
29676
|
commandAllowlist: isStringArray$1(config.command_allowlist) ? config.command_allowlist : []
|
|
29198
29677
|
});
|
|
29199
|
-
const approvals = isRecord(config.approvals) ? config.approvals : {};
|
|
29678
|
+
const approvals = isRecord$1(config.approvals) ? config.approvals : {};
|
|
29200
29679
|
reconcileNativeDenies({
|
|
29201
29680
|
permission,
|
|
29202
29681
|
category: "bash",
|
|
29203
29682
|
patterns: isStringArray$1(approvals.deny) ? approvals.deny : []
|
|
29204
29683
|
});
|
|
29205
|
-
const security = isRecord(config.security) ? config.security : {};
|
|
29206
|
-
const websiteBlocklist = isRecord(security.website_blocklist) ? security.website_blocklist : {};
|
|
29684
|
+
const security = isRecord$1(config.security) ? config.security : {};
|
|
29685
|
+
const websiteBlocklist = isRecord$1(security.website_blocklist) ? security.website_blocklist : {};
|
|
29207
29686
|
reconcileNativeDenies({
|
|
29208
29687
|
permission,
|
|
29209
29688
|
category: "webfetch",
|
|
@@ -29878,8 +30357,8 @@ function mergeKimiCodeToolsSection({ existingContent, patch }) {
|
|
|
29878
30357
|
existing = void 0;
|
|
29879
30358
|
}
|
|
29880
30359
|
const merged = {
|
|
29881
|
-
...isRecord(existing) ? existing : {},
|
|
29882
|
-
...isRecord(patch.tools) ? patch.tools : {}
|
|
30360
|
+
...isRecord$1(existing) ? existing : {},
|
|
30361
|
+
...isRecord$1(patch.tools) ? patch.tools : {}
|
|
29883
30362
|
};
|
|
29884
30363
|
if (Object.keys(merged).length === 0) return;
|
|
29885
30364
|
warnAboutMistypedToolLists(merged);
|
|
@@ -29903,7 +30382,7 @@ function mergeKimiCodeToolsSection({ existingContent, patch }) {
|
|
|
29903
30382
|
* @see https://moonshotai.github.io/kimi-code/en/configuration/config-files.html#tools
|
|
29904
30383
|
*/
|
|
29905
30384
|
function buildKimiCodeToolsSection(tools) {
|
|
29906
|
-
if (!isRecord(tools)) return;
|
|
30385
|
+
if (!isRecord$1(tools)) return;
|
|
29907
30386
|
const section = Object.fromEntries(Object.entries(tools).filter(([key, value]) => key === "enabled" || key === "disabled" ? isStringList(value) : true));
|
|
29908
30387
|
return Object.keys(section).length > 0 ? section : void 0;
|
|
29909
30388
|
}
|
|
@@ -30002,7 +30481,7 @@ function preserveKimiCodeRules(rules) {
|
|
|
30002
30481
|
nativeRules
|
|
30003
30482
|
};
|
|
30004
30483
|
for (const raw of rules) {
|
|
30005
|
-
if (!isRecord(raw)) continue;
|
|
30484
|
+
if (!isRecord$1(raw)) continue;
|
|
30006
30485
|
const decision = raw.decision;
|
|
30007
30486
|
const pattern = raw.pattern;
|
|
30008
30487
|
if (decision !== "allow" && decision !== "ask" && decision !== "deny" || typeof pattern !== "string") continue;
|
|
@@ -30117,7 +30596,7 @@ var KimiCodePermissions = class KimiCodePermissions extends ToolPermissions {
|
|
|
30117
30596
|
format: "toml",
|
|
30118
30597
|
fileContent: this.getFileContent()
|
|
30119
30598
|
});
|
|
30120
|
-
const { permission, nativeRules } = preserveKimiCodeRules((isRecord(config.permission) ? config.permission : {}).rules);
|
|
30599
|
+
const { permission, nativeRules } = preserveKimiCodeRules((isRecord$1(config.permission) ? config.permission : {}).rules);
|
|
30121
30600
|
const defaultPermissionMode = config.default_permission_mode;
|
|
30122
30601
|
const tools = buildKimiCodeToolsSection(config.tools);
|
|
30123
30602
|
const toolOverride = {
|
|
@@ -31394,7 +31873,7 @@ var RovodevPermissions = class RovodevPermissions extends ToolPermissions {
|
|
|
31394
31873
|
} catch (error) {
|
|
31395
31874
|
throw new Error(`Failed to parse existing Rovodev config at ${filePath}: ${formatError(error)}`, { cause: error });
|
|
31396
31875
|
}
|
|
31397
|
-
const config = isRecord(parsed) ? { ...parsed } : {};
|
|
31876
|
+
const config = isRecord$1(parsed) ? { ...parsed } : {};
|
|
31398
31877
|
const rulesyncConfig = rulesyncPermissions.getJson();
|
|
31399
31878
|
const toolPermissions = convertRulesyncToRovodevToolPermissions({
|
|
31400
31879
|
config: rulesyncConfig,
|
|
@@ -31431,8 +31910,8 @@ var RovodevPermissions = class RovodevPermissions extends ToolPermissions {
|
|
|
31431
31910
|
} catch (error) {
|
|
31432
31911
|
throw new Error(`Failed to parse Rovodev permissions content in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
31433
31912
|
}
|
|
31434
|
-
const config = isRecord(parsed) ? parsed : {};
|
|
31435
|
-
const rulesyncConfig = convertRovodevToolPermissionsToRulesync(isRecord(config.toolPermissions) ? config.toolPermissions : {});
|
|
31913
|
+
const config = isRecord$1(parsed) ? parsed : {};
|
|
31914
|
+
const rulesyncConfig = convertRovodevToolPermissionsToRulesync(isRecord$1(config.toolPermissions) ? config.toolPermissions : {});
|
|
31436
31915
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(rulesyncConfig, null, 2) });
|
|
31437
31916
|
}
|
|
31438
31917
|
validate() {
|
|
@@ -31459,14 +31938,14 @@ var RovodevPermissions = class RovodevPermissions extends ToolPermissions {
|
|
|
31459
31938
|
* keys it does not are kept as-is.
|
|
31460
31939
|
*/
|
|
31461
31940
|
function resolveToolPermissionsBlock({ existing, generated, sourceStatesRules, filePath, logger }) {
|
|
31462
|
-
const existingToolPermissions = isRecord(existing) ? { ...existing } : {};
|
|
31941
|
+
const existingToolPermissions = isRecord$1(existing) ? { ...existing } : {};
|
|
31463
31942
|
if (Object.keys(generated).length === 0 && sourceStatesRules) {
|
|
31464
|
-
if (!isRecord(existing)) return;
|
|
31943
|
+
if (!isRecord$1(existing)) return;
|
|
31465
31944
|
const strippedKeys = stripPermissiveOwnedValues(existingToolPermissions);
|
|
31466
31945
|
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(", ")}.` : `.`));
|
|
31467
31946
|
return existingToolPermissions;
|
|
31468
31947
|
}
|
|
31469
|
-
const hasExistingToolsRecord = isRecord(existingToolPermissions.tools);
|
|
31948
|
+
const hasExistingToolsRecord = isRecord$1(existingToolPermissions.tools);
|
|
31470
31949
|
const existingTools = hasExistingToolsRecord ? { ...existingToolPermissions.tools } : {};
|
|
31471
31950
|
warnAboutDroppedOwnedKeys({
|
|
31472
31951
|
existingToolPermissions,
|
|
@@ -31510,7 +31989,7 @@ function warnAboutDroppedOwnedKeys({ existingToolPermissions, existingTools, gen
|
|
|
31510
31989
|
*/
|
|
31511
31990
|
function stripPermissiveOwnedValues(toolPermissions) {
|
|
31512
31991
|
const strippedKeys = [];
|
|
31513
|
-
if (isRecord(toolPermissions.tools)) {
|
|
31992
|
+
if (isRecord$1(toolPermissions.tools)) {
|
|
31514
31993
|
const tools = { ...toolPermissions.tools };
|
|
31515
31994
|
for (const toolKey of MANAGED_TOOL_KEYS) if (tools[toolKey] === "allow") {
|
|
31516
31995
|
delete tools[toolKey];
|
|
@@ -31532,14 +32011,14 @@ function stripPermissiveOwnedValues(toolPermissions) {
|
|
|
31532
32011
|
strippedKeys.push("default");
|
|
31533
32012
|
}
|
|
31534
32013
|
const bash = toolPermissions.bash;
|
|
31535
|
-
if (isRecord(bash)) {
|
|
32014
|
+
if (isRecord$1(bash)) {
|
|
31536
32015
|
const stripped = { ...bash };
|
|
31537
32016
|
if (stripped.default === "allow") {
|
|
31538
32017
|
delete stripped.default;
|
|
31539
32018
|
strippedKeys.push("bash.default");
|
|
31540
32019
|
}
|
|
31541
32020
|
if (Array.isArray(stripped.commands)) {
|
|
31542
|
-
const kept = stripped.commands.filter((entry) => !(isRecord(entry) && entry.permission === "allow"));
|
|
32021
|
+
const kept = stripped.commands.filter((entry) => !(isRecord$1(entry) && entry.permission === "allow"));
|
|
31543
32022
|
if (kept.length !== stripped.commands.length) strippedKeys.push("bash.commands");
|
|
31544
32023
|
if (kept.length > 0) stripped.commands = kept;
|
|
31545
32024
|
else delete stripped.commands;
|
|
@@ -31647,15 +32126,15 @@ function convertRovodevToolPermissionsToRulesync(toolPermissions) {
|
|
|
31647
32126
|
const permission = {};
|
|
31648
32127
|
if (isPermissionAction(toolPermissions.default)) permission[CATCH_ALL_PATTERN$1] = { [CATCH_ALL_PATTERN$1]: toolPermissions.default };
|
|
31649
32128
|
const bash = toolPermissions.bash;
|
|
31650
|
-
if (isRecord(bash)) {
|
|
32129
|
+
if (isRecord$1(bash)) {
|
|
31651
32130
|
const bashRules = {};
|
|
31652
32131
|
if (isPermissionAction(bash.default)) bashRules[CATCH_ALL_PATTERN$1] = bash.default;
|
|
31653
32132
|
if (Array.isArray(bash.commands)) {
|
|
31654
|
-
for (const entry of bash.commands) if (isRecord(entry) && typeof entry.command === "string" && isPermissionAction(entry.permission)) bashRules[entry.command] = entry.permission;
|
|
32133
|
+
for (const entry of bash.commands) if (isRecord$1(entry) && typeof entry.command === "string" && isPermissionAction(entry.permission)) bashRules[entry.command] = entry.permission;
|
|
31655
32134
|
}
|
|
31656
32135
|
if (Object.keys(bashRules).length > 0) permission.bash = bashRules;
|
|
31657
32136
|
}
|
|
31658
|
-
const nestedTools = isRecord(toolPermissions.tools) ? toolPermissions.tools : {};
|
|
32137
|
+
const nestedTools = isRecord$1(toolPermissions.tools) ? toolPermissions.tools : {};
|
|
31659
32138
|
const implicitLevel = isPermissionAction(toolPermissions.default) ? toolPermissions.default : "ask";
|
|
31660
32139
|
for (const category of new Set(Object.values(TOOL_KEY_TO_CATEGORY))) {
|
|
31661
32140
|
const levels = Object.entries(TOOL_KEY_TO_CATEGORY).filter(([, mapped]) => mapped === category).map(([toolKey]) => {
|
|
@@ -32370,11 +32849,11 @@ var WarpPermissions = class WarpPermissions extends ToolPermissions {
|
|
|
32370
32849
|
config,
|
|
32371
32850
|
logger
|
|
32372
32851
|
});
|
|
32373
|
-
const agents = isRecord(settings.agents) ? { ...settings.agents } : {};
|
|
32374
|
-
const profiles = isRecord(agents.profiles) ? { ...agents.profiles } : {};
|
|
32852
|
+
const agents = isRecord$1(settings.agents) ? { ...settings.agents } : {};
|
|
32853
|
+
const profiles = isRecord$1(agents.profiles) ? { ...agents.profiles } : {};
|
|
32375
32854
|
const override = config.warp;
|
|
32376
|
-
const executionProfileOverride = isRecord(override) && isRecord(override[WARP_EXECUTION_PROFILE_OVERRIDE_KEY]) ? override[WARP_EXECUTION_PROFILE_OVERRIDE_KEY] : void 0;
|
|
32377
|
-
if (isRecord(override)) {
|
|
32855
|
+
const executionProfileOverride = isRecord$1(override) && isRecord$1(override[WARP_EXECUTION_PROFILE_OVERRIDE_KEY]) ? override[WARP_EXECUTION_PROFILE_OVERRIDE_KEY] : void 0;
|
|
32856
|
+
if (isRecord$1(override)) {
|
|
32378
32857
|
const { [WARP_EXECUTION_PROFILE_OVERRIDE_KEY]: _executionProfile, ...legacyOverride } = override;
|
|
32379
32858
|
Object.assign(profiles, legacyOverride);
|
|
32380
32859
|
}
|
|
@@ -32409,10 +32888,10 @@ var WarpPermissions = class WarpPermissions extends ToolPermissions {
|
|
|
32409
32888
|
} catch (error) {
|
|
32410
32889
|
throw new Error(`Failed to parse Warp permissions content in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
32411
32890
|
}
|
|
32412
|
-
const agents = isRecord(settings.agents) ? settings.agents : {};
|
|
32413
|
-
const profiles = isRecord(agents.profiles) ? agents.profiles : {};
|
|
32414
|
-
const executionProfiles = isRecord(agents[EXECUTION_PROFILES_KEY]) ? agents[EXECUTION_PROFILES_KEY] : void 0;
|
|
32415
|
-
const defaultProfile = executionProfiles && isRecord(executionProfiles[DEFAULT_PROFILE_KEY]) ? executionProfiles[DEFAULT_PROFILE_KEY] : void 0;
|
|
32891
|
+
const agents = isRecord$1(settings.agents) ? settings.agents : {};
|
|
32892
|
+
const profiles = isRecord$1(agents.profiles) ? agents.profiles : {};
|
|
32893
|
+
const executionProfiles = isRecord$1(agents[EXECUTION_PROFILES_KEY]) ? agents[EXECUTION_PROFILES_KEY] : void 0;
|
|
32894
|
+
const defaultProfile = executionProfiles && isRecord$1(executionProfiles[DEFAULT_PROFILE_KEY]) ? executionProfiles[DEFAULT_PROFILE_KEY] : void 0;
|
|
32416
32895
|
const config = convertWarpToRulesyncPermissions({
|
|
32417
32896
|
allow: defaultProfile ? isStringArray$1(defaultProfile[PROFILE_ALLOWLIST_KEY]) ? defaultProfile[PROFILE_ALLOWLIST_KEY] : [] : isStringArray$1(profiles[ALLOWLIST_KEY]) ? profiles[ALLOWLIST_KEY] : [],
|
|
32418
32897
|
deny: defaultProfile ? isStringArray$1(defaultProfile[PROFILE_DENYLIST_KEY]) ? defaultProfile[PROFILE_DENYLIST_KEY] : [] : isStringArray$1(profiles[DENYLIST_KEY]) ? profiles[DENYLIST_KEY] : []
|
|
@@ -32459,12 +32938,12 @@ var WarpPermissions = class WarpPermissions extends ToolPermissions {
|
|
|
32459
32938
|
*/
|
|
32460
32939
|
function mergeIntoDefaultExecutionProfile({ agents, mergedAllow, mergedDeny, executionProfileOverride, logger }) {
|
|
32461
32940
|
const hasOverrideKeys = executionProfileOverride !== void 0 && Object.keys(executionProfileOverride).length > 0;
|
|
32462
|
-
if (!isRecord(agents[EXECUTION_PROFILES_KEY])) {
|
|
32941
|
+
if (!isRecord$1(agents[EXECUTION_PROFILES_KEY])) {
|
|
32463
32942
|
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.");
|
|
32464
32943
|
return;
|
|
32465
32944
|
}
|
|
32466
32945
|
const executionProfiles = { ...agents[EXECUTION_PROFILES_KEY] };
|
|
32467
|
-
const defaultProfile = isRecord(executionProfiles[DEFAULT_PROFILE_KEY]) ? { ...executionProfiles[DEFAULT_PROFILE_KEY] } : {};
|
|
32946
|
+
const defaultProfile = isRecord$1(executionProfiles[DEFAULT_PROFILE_KEY]) ? { ...executionProfiles[DEFAULT_PROFILE_KEY] } : {};
|
|
32468
32947
|
if (executionProfileOverride) Object.assign(defaultProfile, executionProfileOverride);
|
|
32469
32948
|
if (mergedAllow.length > 0) defaultProfile[PROFILE_ALLOWLIST_KEY] = mergedAllow;
|
|
32470
32949
|
else delete defaultProfile[PROFILE_ALLOWLIST_KEY];
|
|
@@ -32538,9 +33017,13 @@ const ZedToolPermissionsSchema = z.looseObject({
|
|
|
32538
33017
|
default: z.optional(ZedPermissionActionSchema),
|
|
32539
33018
|
tools: z.optional(z.record(z.string(), ZedToolPermissionSchema))
|
|
32540
33019
|
});
|
|
33020
|
+
/** Canonical per-tool MCP category prefix: `mcp__<server>__<tool>`. */
|
|
33021
|
+
const MCP_CANONICAL_PREFIX = "mcp__";
|
|
33022
|
+
/** Zed's per-tool MCP name prefix: `mcp:<server>:<tool>`. */
|
|
33023
|
+
const MCP_ZED_PREFIX = "mcp:";
|
|
32541
33024
|
/**
|
|
32542
33025
|
* Mapping from rulesync canonical tool category names to Zed agent tool names.
|
|
32543
|
-
* Unknown names are passed through as-is
|
|
33026
|
+
* Unknown names are passed through as-is.
|
|
32544
33027
|
*/
|
|
32545
33028
|
const CANONICAL_TO_ZED_TOOL_NAMES = {
|
|
32546
33029
|
bash: "terminal",
|
|
@@ -32575,12 +33058,68 @@ const ZED_EXCLUDED_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
|
32575
33058
|
]);
|
|
32576
33059
|
const isZedExcludedCategory = (category) => ZED_EXCLUDED_CANONICAL_CATEGORIES.has(category) || ZED_EXCLUDED_TOOL_NAMES.has(toZedToolName(category));
|
|
32577
33060
|
const ZED_TO_CANONICAL_TOOL_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_ZED_TOOL_NAMES).map(([k, v]) => [v, k]));
|
|
33061
|
+
/**
|
|
33062
|
+
* Zed addresses an MCP tool as `mcp:<server>:<tool>`, where rulesync's canonical
|
|
33063
|
+
* category is `mcp__<server>__<tool>`. Without this translation the canonical
|
|
33064
|
+
* spelling lands under a key Zed never looks up.
|
|
33065
|
+
*
|
|
33066
|
+
* Only the FIRST separator is split, matching the `cursor-permissions.ts`
|
|
33067
|
+
* precedent: upstream's `mcp_tool_id` concatenates the two names without
|
|
33068
|
+
* escaping either, so a tool called `create__issue` is legitimately
|
|
33069
|
+
* `mcp:github:create__issue`. Splitting every separator would rewrite that into
|
|
33070
|
+
* a third key on the next generate.
|
|
33071
|
+
*
|
|
33072
|
+
* @see https://zed.dev/docs/ai/tool-permissions
|
|
33073
|
+
*/
|
|
32578
33074
|
function toZedToolName(canonical) {
|
|
33075
|
+
if (canonical.startsWith(MCP_CANONICAL_PREFIX)) {
|
|
33076
|
+
const [server, ...toolParts] = canonical.slice(5).split("__");
|
|
33077
|
+
const address = toolParts.length > 0 ? `${server}:${toolParts.join("__")}` : server ?? "";
|
|
33078
|
+
return `${MCP_ZED_PREFIX}${address}`;
|
|
33079
|
+
}
|
|
32579
33080
|
return CANONICAL_TO_ZED_TOOL_NAMES[canonical] ?? canonical;
|
|
32580
33081
|
}
|
|
32581
33082
|
function toCanonicalToolName(zedName) {
|
|
33083
|
+
if (zedName.startsWith(MCP_ZED_PREFIX)) {
|
|
33084
|
+
const [server, ...toolParts] = zedName.slice(4).split(":");
|
|
33085
|
+
const address = toolParts.length > 0 ? `${server}__${toolParts.join(":")}` : server ?? "";
|
|
33086
|
+
return `${MCP_CANONICAL_PREFIX}${address}`;
|
|
33087
|
+
}
|
|
32582
33088
|
return ZED_TO_CANONICAL_TOOL_NAMES[zedName] ?? zedName;
|
|
32583
33089
|
}
|
|
33090
|
+
/**
|
|
33091
|
+
* Zed matches `always_allow`/`always_deny`/`always_confirm` regexes against the
|
|
33092
|
+
* tool's text input, and it dispatches every MCP tool with a single empty input
|
|
33093
|
+
* (`&[String::new()]`, commented upstream as "MCP tools are gated only by tool
|
|
33094
|
+
* id (no per-input pattern matching)"). The regexes still run, but against `""`,
|
|
33095
|
+
* so a pattern-scoped rule silently does something other than what its author
|
|
33096
|
+
* meant — and a non-matching `always_allow` downgrades the outcome to confirm.
|
|
33097
|
+
* Only the category's `*` rule (Zed's per-tool `default`) is therefore emitted.
|
|
33098
|
+
*/
|
|
33099
|
+
const isMcpZedToolName = (zedName) => zedName.startsWith(MCP_ZED_PREFIX);
|
|
33100
|
+
/**
|
|
33101
|
+
* Zed looks a tool up by exact key on the full `mcp:<server>:<tool>` triple, so
|
|
33102
|
+
* an address is inert unless it names both a concrete server and a concrete
|
|
33103
|
+
* tool: a missing half matches nothing, and so does a wildcard, since Zed does
|
|
33104
|
+
* no glob or prefix matching on the key.
|
|
33105
|
+
*/
|
|
33106
|
+
function isInertMcpAddress(zedToolName) {
|
|
33107
|
+
if (!isMcpZedToolName(zedToolName)) return false;
|
|
33108
|
+
const [server, ...toolParts] = zedToolName.slice(4).split(":");
|
|
33109
|
+
const tool = toolParts.join(":");
|
|
33110
|
+
return !server || server === "*" || !tool || tool === "*";
|
|
33111
|
+
}
|
|
33112
|
+
/**
|
|
33113
|
+
* Strip pattern-scoped rules from an MCP category, warning once about the ones
|
|
33114
|
+
* dropped. Non-MCP categories are returned untouched.
|
|
33115
|
+
*/
|
|
33116
|
+
function withoutInertMcpPatterns({ category, zedToolName, rules, logger }) {
|
|
33117
|
+
if (!isMcpZedToolName(zedToolName)) return rules;
|
|
33118
|
+
const scopedPatterns = Object.keys(rules).filter((pattern) => pattern !== "*");
|
|
33119
|
+
if (scopedPatterns.length === 0) return rules;
|
|
33120
|
+
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.`);
|
|
33121
|
+
return Object.fromEntries(Object.entries(rules).filter(([pattern]) => pattern === "*"));
|
|
33122
|
+
}
|
|
32584
33123
|
const CANONICAL_TO_ZED_ACTION = {
|
|
32585
33124
|
allow: "allow",
|
|
32586
33125
|
ask: "confirm",
|
|
@@ -32623,6 +33162,53 @@ function buildZedToolPermission(rules) {
|
|
|
32623
33162
|
if (alwaysConfirm.length > 0) tool.always_confirm = alwaysConfirm;
|
|
32624
33163
|
return Object.keys(tool).length > 0 ? tool : null;
|
|
32625
33164
|
}
|
|
33165
|
+
/**
|
|
33166
|
+
* Split a canonical permission block into the Zed shapes it maps onto, plus the
|
|
33167
|
+
* categories the caller should report as dropped.
|
|
33168
|
+
*
|
|
33169
|
+
* The canonical `*` category is the all-tools catch-all. Zed's counterpart is
|
|
33170
|
+
* `agent.tool_permissions.default` (rung 6 of its precedence ladder), not a
|
|
33171
|
+
* `tools["*"]` entry — `*` is not a Zed tool name, so writing one produces a
|
|
33172
|
+
* rule Zed silently ignores. Only the category's own `*` pattern can be
|
|
33173
|
+
* expressed there: Zed's global default carries no pattern list, so
|
|
33174
|
+
* pattern-scoped rules in the `*` category are dropped with a warning instead of
|
|
33175
|
+
* being emitted as inert config.
|
|
33176
|
+
*/
|
|
33177
|
+
function buildZedToolPermissions({ permission, logger }) {
|
|
33178
|
+
let managedDefault;
|
|
33179
|
+
const managedTools = {};
|
|
33180
|
+
const excludedCategories = [];
|
|
33181
|
+
const inertMcpCategories = [];
|
|
33182
|
+
for (const [category, rules] of Object.entries(permission)) {
|
|
33183
|
+
if (category === "*") {
|
|
33184
|
+
for (const [pattern, action] of Object.entries(rules)) if (pattern === "*") managedDefault = CANONICAL_TO_ZED_ACTION[action];
|
|
33185
|
+
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.`);
|
|
33186
|
+
continue;
|
|
33187
|
+
}
|
|
33188
|
+
if (isZedExcludedCategory(category)) {
|
|
33189
|
+
if (Object.values(rules).some((action) => action === "deny" || action === "ask")) excludedCategories.push(category);
|
|
33190
|
+
continue;
|
|
33191
|
+
}
|
|
33192
|
+
const zedToolName = toZedToolName(category);
|
|
33193
|
+
if (isInertMcpAddress(zedToolName)) {
|
|
33194
|
+
inertMcpCategories.push(category);
|
|
33195
|
+
continue;
|
|
33196
|
+
}
|
|
33197
|
+
const tool = buildZedToolPermission(withoutInertMcpPatterns({
|
|
33198
|
+
category,
|
|
33199
|
+
zedToolName,
|
|
33200
|
+
rules,
|
|
33201
|
+
logger
|
|
33202
|
+
}));
|
|
33203
|
+
if (tool) managedTools[zedToolName] = tool;
|
|
33204
|
+
}
|
|
33205
|
+
return {
|
|
33206
|
+
managedDefault,
|
|
33207
|
+
managedTools,
|
|
33208
|
+
excludedCategories,
|
|
33209
|
+
inertMcpCategories
|
|
33210
|
+
};
|
|
33211
|
+
}
|
|
32626
33212
|
function asRecord(value) {
|
|
32627
33213
|
if (value === null || typeof value !== "object" || Array.isArray(value)) return {};
|
|
32628
33214
|
return Object.fromEntries(Object.entries(value));
|
|
@@ -32693,25 +33279,15 @@ var ZedPermissions = class ZedPermissions extends ToolPermissions {
|
|
|
32693
33279
|
const agent = asRecord(settings.agent);
|
|
32694
33280
|
const toolPermissions = asRecord(agent.tool_permissions);
|
|
32695
33281
|
const existingTools = asRecord(toolPermissions.tools);
|
|
32696
|
-
|
|
32697
|
-
|
|
32698
|
-
|
|
32699
|
-
|
|
32700
|
-
if (category === "*") {
|
|
32701
|
-
for (const [pattern, action] of Object.entries(rules)) if (pattern === "*") managedDefault = CANONICAL_TO_ZED_ACTION[action];
|
|
32702
|
-
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.`);
|
|
32703
|
-
continue;
|
|
32704
|
-
}
|
|
32705
|
-
if (isZedExcludedCategory(category)) {
|
|
32706
|
-
if (Object.values(rules).some((action) => action === "deny" || action === "ask")) excludedCategories.push(category);
|
|
32707
|
-
continue;
|
|
32708
|
-
}
|
|
32709
|
-
const tool = buildZedToolPermission(rules);
|
|
32710
|
-
if (tool) managedTools[toZedToolName(category)] = tool;
|
|
32711
|
-
}
|
|
33282
|
+
const { managedDefault, managedTools, excludedCategories, inertMcpCategories } = buildZedToolPermissions({
|
|
33283
|
+
permission: config.permission,
|
|
33284
|
+
logger
|
|
33285
|
+
});
|
|
32712
33286
|
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\`.`);
|
|
33287
|
+
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.`);
|
|
32713
33288
|
const managedToolNames = new Set(Object.keys(managedTools));
|
|
32714
33289
|
if ("*" in config.permission) managedToolNames.add("*");
|
|
33290
|
+
for (const toolName of Object.keys(existingTools)) if (toolName.startsWith(MCP_CANONICAL_PREFIX)) managedToolNames.add(toolName);
|
|
32715
33291
|
const preservedTools = Object.fromEntries(Object.entries(existingTools).filter(([toolName]) => !managedToolNames.has(toolName)));
|
|
32716
33292
|
return new ZedPermissions({
|
|
32717
33293
|
outputRoot,
|
|
@@ -34912,18 +35488,10 @@ var CopilotSkill = class CopilotSkill extends ToolSkill {
|
|
|
34912
35488
|
};
|
|
34913
35489
|
}
|
|
34914
35490
|
toRulesyncSkill() {
|
|
34915
|
-
const
|
|
34916
|
-
const copilotSection = {
|
|
34917
|
-
...frontmatter.license !== void 0 && { license: frontmatter.license },
|
|
34918
|
-
...frontmatter["allowed-tools"] !== void 0 && { "allowed-tools": frontmatter["allowed-tools"] },
|
|
34919
|
-
...frontmatter["argument-hint"] !== void 0 && { "argument-hint": frontmatter["argument-hint"] },
|
|
34920
|
-
...frontmatter["user-invocable"] !== void 0 && { "user-invocable": frontmatter["user-invocable"] },
|
|
34921
|
-
...frontmatter["disable-model-invocation"] !== void 0 && { "disable-model-invocation": frontmatter["disable-model-invocation"] },
|
|
34922
|
-
...frontmatter.context !== void 0 && { context: frontmatter.context }
|
|
34923
|
-
};
|
|
35491
|
+
const { name, description, ...copilotSection } = this.getFrontmatter();
|
|
34924
35492
|
const rulesyncFrontmatter = {
|
|
34925
|
-
name
|
|
34926
|
-
description
|
|
35493
|
+
name,
|
|
35494
|
+
description,
|
|
34927
35495
|
targets: ["*"],
|
|
34928
35496
|
...Object.keys(copilotSection).length > 0 && { copilot: copilotSection }
|
|
34929
35497
|
};
|
|
@@ -34950,15 +35518,13 @@ var CopilotSkill = class CopilotSkill extends ToolSkill {
|
|
|
34950
35518
|
rootFrontmatter: rulesyncFrontmatter,
|
|
34951
35519
|
section: copilotSection
|
|
34952
35520
|
});
|
|
35521
|
+
const { "user-invocable": _userInvocable, "disable-model-invocation": _disableModelInvocation, ...copilotFields } = copilotSection ?? {};
|
|
34953
35522
|
const copilotFrontmatter = {
|
|
35523
|
+
...copilotFields,
|
|
34954
35524
|
name: rulesyncFrontmatter.name,
|
|
34955
35525
|
description: rulesyncFrontmatter.description,
|
|
34956
|
-
...copilotSection?.license !== void 0 && { license: copilotSection.license },
|
|
34957
|
-
...copilotSection?.["allowed-tools"] !== void 0 && { "allowed-tools": copilotSection["allowed-tools"] },
|
|
34958
|
-
...copilotSection?.["argument-hint"] !== void 0 && { "argument-hint": copilotSection["argument-hint"] },
|
|
34959
35526
|
...resolvedUserInvocable !== void 0 && { "user-invocable": resolvedUserInvocable },
|
|
34960
|
-
...resolvedDisableModelInvocation !== void 0 && { "disable-model-invocation": resolvedDisableModelInvocation }
|
|
34961
|
-
...copilotSection?.context !== void 0 && { context: copilotSection.context }
|
|
35527
|
+
...resolvedDisableModelInvocation !== void 0 && { "disable-model-invocation": resolvedDisableModelInvocation }
|
|
34962
35528
|
};
|
|
34963
35529
|
return new CopilotSkill({
|
|
34964
35530
|
outputRoot,
|
|
@@ -35077,17 +35643,10 @@ var CopilotcliSkill = class CopilotcliSkill extends ToolSkill {
|
|
|
35077
35643
|
};
|
|
35078
35644
|
}
|
|
35079
35645
|
toRulesyncSkill() {
|
|
35080
|
-
const
|
|
35081
|
-
const copilotcliSection = {
|
|
35082
|
-
...frontmatter.license !== void 0 && { license: frontmatter.license },
|
|
35083
|
-
...frontmatter["allowed-tools"] !== void 0 && { "allowed-tools": frontmatter["allowed-tools"] },
|
|
35084
|
-
...frontmatter["argument-hint"] !== void 0 && { "argument-hint": frontmatter["argument-hint"] },
|
|
35085
|
-
...frontmatter["user-invocable"] !== void 0 && { "user-invocable": frontmatter["user-invocable"] },
|
|
35086
|
-
...frontmatter["disable-model-invocation"] !== void 0 && { "disable-model-invocation": frontmatter["disable-model-invocation"] }
|
|
35087
|
-
};
|
|
35646
|
+
const { name, description, ...copilotcliSection } = this.getFrontmatter();
|
|
35088
35647
|
const rulesyncFrontmatter = {
|
|
35089
|
-
name
|
|
35090
|
-
description
|
|
35648
|
+
name,
|
|
35649
|
+
description,
|
|
35091
35650
|
targets: ["*"],
|
|
35092
35651
|
...Object.keys(copilotcliSection).length > 0 && { copilotcli: copilotcliSection }
|
|
35093
35652
|
};
|
|
@@ -35114,12 +35673,11 @@ var CopilotcliSkill = class CopilotcliSkill extends ToolSkill {
|
|
|
35114
35673
|
rootFrontmatter: rulesyncFrontmatter,
|
|
35115
35674
|
section: copilotcliSection
|
|
35116
35675
|
});
|
|
35676
|
+
const { "user-invocable": _userInvocable, "disable-model-invocation": _disableModelInvocation, ...copilotcliFields } = copilotcliSection ?? {};
|
|
35117
35677
|
const copilotcliFrontmatter = {
|
|
35678
|
+
...copilotcliFields,
|
|
35118
35679
|
name: rulesyncFrontmatter.name,
|
|
35119
35680
|
description: rulesyncFrontmatter.description,
|
|
35120
|
-
...copilotcliSection?.license !== void 0 && { license: copilotcliSection.license },
|
|
35121
|
-
...copilotcliSection?.["allowed-tools"] !== void 0 && { "allowed-tools": copilotcliSection["allowed-tools"] },
|
|
35122
|
-
...copilotcliSection?.["argument-hint"] !== void 0 && { "argument-hint": copilotcliSection["argument-hint"] },
|
|
35123
35681
|
...resolvedUserInvocable !== void 0 && { "user-invocable": resolvedUserInvocable },
|
|
35124
35682
|
...resolvedDisableModelInvocation !== void 0 && { "disable-model-invocation": resolvedDisableModelInvocation }
|
|
35125
35683
|
};
|
|
@@ -36593,7 +37151,10 @@ var KimiCodeSkill = class KimiCodeSkill extends ToolSkill {
|
|
|
36593
37151
|
//#region src/features/skills/kiro-skill.ts
|
|
36594
37152
|
const KiroSkillFrontmatterSchema = z.looseObject({
|
|
36595
37153
|
name: z.string(),
|
|
36596
|
-
description: z.string()
|
|
37154
|
+
description: z.string(),
|
|
37155
|
+
license: z.optional(z.string()),
|
|
37156
|
+
compatibility: z.optional(z.union([z.string(), z.looseObject({})])),
|
|
37157
|
+
metadata: z.optional(z.looseObject({}))
|
|
36597
37158
|
});
|
|
36598
37159
|
/**
|
|
36599
37160
|
* Represents a Kiro skill directory.
|
|
@@ -36647,11 +37208,12 @@ var KiroSkill = class KiroSkill extends ToolSkill {
|
|
|
36647
37208
|
};
|
|
36648
37209
|
}
|
|
36649
37210
|
toRulesyncSkill() {
|
|
36650
|
-
const
|
|
37211
|
+
const { name, description, ...kiroSection } = this.getFrontmatter();
|
|
36651
37212
|
const rulesyncFrontmatter = {
|
|
36652
|
-
name
|
|
36653
|
-
description
|
|
36654
|
-
targets: ["*"]
|
|
37213
|
+
name,
|
|
37214
|
+
description,
|
|
37215
|
+
targets: ["*"],
|
|
37216
|
+
...Object.keys(kiroSection).length > 0 && { kiro: kiroSection }
|
|
36655
37217
|
};
|
|
36656
37218
|
return new RulesyncSkill({
|
|
36657
37219
|
outputRoot: this.outputRoot,
|
|
@@ -36668,6 +37230,7 @@ var KiroSkill = class KiroSkill extends ToolSkill {
|
|
|
36668
37230
|
const settablePaths = KiroSkill.getSettablePaths({ global });
|
|
36669
37231
|
const rulesyncFrontmatter = rulesyncSkill.getFrontmatter();
|
|
36670
37232
|
const kiroFrontmatter = {
|
|
37233
|
+
...rulesyncFrontmatter.kiro,
|
|
36671
37234
|
name: rulesyncFrontmatter.name,
|
|
36672
37235
|
description: rulesyncFrontmatter.description
|
|
36673
37236
|
};
|
|
@@ -43236,14 +43799,14 @@ var ZoocodeSubagent = class extends RooSubagent {
|
|
|
43236
43799
|
static toRooMode(rulesyncSubagent) {
|
|
43237
43800
|
const mode = super.toRooMode(rulesyncSubagent);
|
|
43238
43801
|
const frontmatter = rulesyncSubagent.getFrontmatter();
|
|
43239
|
-
const zoocodeSection = isRecord(frontmatter.zoocode) ? frontmatter.zoocode : {};
|
|
43802
|
+
const zoocodeSection = isRecord$1(frontmatter.zoocode) ? frontmatter.zoocode : {};
|
|
43240
43803
|
if (isStringArray$1(zoocodeSection.allowedMcpServers)) mode.allowedMcpServers = zoocodeSection.allowedMcpServers;
|
|
43241
43804
|
return mode;
|
|
43242
43805
|
}
|
|
43243
43806
|
toRulesyncSubagents() {
|
|
43244
43807
|
return super.toRulesyncSubagents().map((subagent) => {
|
|
43245
43808
|
const frontmatter = subagent.getFrontmatter();
|
|
43246
|
-
const { allowedMcpServers, ...restRooSection } = isRecord(frontmatter.roo) ? { ...frontmatter.roo } : {};
|
|
43809
|
+
const { allowedMcpServers, ...restRooSection } = isRecord$1(frontmatter.roo) ? { ...frontmatter.roo } : {};
|
|
43247
43810
|
const rebuilt = {
|
|
43248
43811
|
...frontmatter,
|
|
43249
43812
|
targets: ["zoocode"],
|
|
@@ -51315,4 +51878,4 @@ async function importChecksCore(params) {
|
|
|
51315
51878
|
//#endregion
|
|
51316
51879
|
export { JsonLogger as $, RULESYNC_PERMISSIONS_LEGACY_FILE_NAME as $t, RulesyncRuleFrontmatterSchema as A, ALL_TOOL_TARGETS_WITH_WILDCARD as At, RulesyncCheck as B, RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH as Bt, CODEXCLI_DIR as C, removeTempDirectory as Ct, RulesyncSkill as D, writeFileBuffer as Dt, RulesyncSubagentFrontmatterSchema as E, toPosixPath as Et, getRulesyncSourceCandidates as F, RULESYNC_AIIGNORE_RELATIVE_FILE_PATH as Ft, SKILL_FILE_NAME as G, RULESYNC_IGNORE_RELATIVE_FILE_PATH as Gt, stringifyFrontmatter as H, RULESYNC_HOOKS_FILE_NAME as Ht, resolveRulesyncSourceWritePath as I, RULESYNC_CHECKS_RELATIVE_DIR_PATH as It, ConfigFileSchema as J, RULESYNC_MCP_LEGACY_FILE_NAME as Jt, ConfigResolver as K, RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH as Kt, parseJsonc as L, RULESYNC_COMMANDS_RELATIVE_DIR_PATH as Lt, RulesyncMcp as M, ToolTargetSchema as Mt, RulesyncIgnore as N, MAX_FILE_SIZE as Nt, RulesyncSkillFrontmatterSchema as O, writeFileContent as Ot, RulesyncHooks as P, RULESYNC_AIIGNORE_FILE_NAME as Pt, ConsoleLogger as Q, RULESYNC_PERMISSIONS_FILE_NAME as Qt, RulesyncCommand as R, RULESYNC_CONFIG_RELATIVE_FILE_PATH as Rt, CODEXCLI_BASH_RULES_FILE_NAME as S, removeFileStrict as St, RulesyncSubagent as T, runWithDirectoryRollback as Tt, loadYaml as U, RULESYNC_HOOKS_LEGACY_FILE_NAME as Ut, RulesyncCheckFrontmatterSchema as V, RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH as Vt, SHARED_USER_MANAGED_CONFIG_PATHS as W, RULESYNC_HOOKS_RELATIVE_FILE_PATH as Wt, SourceEntrySchema as X, RULESYNC_MCP_SCHEMA_URL as Xt, GITIGNORE_DESTINATION_KEY as Y, RULESYNC_MCP_RELATIVE_FILE_PATH as Yt, findControlCharacter as Z, RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH as Zt, CLAUDECODE_LOCAL_RULE_FILE_NAME as _, readFileContent as _t, convertFromTool as a, RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH as an, assertTreeContainsNoSymlinks as at, CLAUDECODE_SKILLS_DIR_PATH as b, removeDirectoryStrict as bt, SubagentsProcessor as c, ALL_FEATURES_WITH_WILDCARD as cn, createTempDirectory as ct, IgnoreProcessor as d, fileExists as dt, RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH as en, fallbackLogger as et, HooksProcessor as f, findFilesByGlobs as ft, CLAUDECODE_DIR as g, listDirectoryFiles as gt, QWENCODE_LOCAL_RULE_FILE_NAME as h, isSymlink as ht, getProcessorRegistryEntry as i, RULESYNC_SKILLS_RELATIVE_DIR_PATH as in, assertDirectoryIfExists as it, RulesyncPermissions as j, PACKAGING_TOOL_TARGETS as jt, RulesyncRule as k, ALL_TOOL_TARGETS as kt, SkillsProcessor as l, DEPRECATED_FEATURE_REPLACEMENTS as ln, directoryExists as lt, QWENCODE_DIR as m, getHomeDirectory as mt, checkRulesyncDirExists as n, RULESYNC_RELATIVE_DIR_PATH as nn, CLIError as nt, isPackagingToolTarget as o, RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH as on, assertWritablePathInsideRoot as ot, CommandsProcessor as p, getFileSize as pt, CONFLICTING_TARGET_PAIRS as q, RULESYNC_MCP_FILE_NAME as qt, generate as r, RULESYNC_RULES_RELATIVE_DIR_PATH as rn, ErrorCodes as rt, RulesProcessor as s, ALL_FEATURES as sn, checkPathTraversal as st, importFromTool as t, RULESYNC_PERMISSIONS_SCHEMA_URL as tn, warnOnConflictingFlags as tt, McpProcessor as u, formatError as un, ensureDir as ut, CLAUDECODE_MEMORIES_DIR_NAME as v, readFileContentOrNull as vt, getLocalSkillDirNames as w, resolvePath as wt, ChecksProcessor as x, removeFile as xt, CLAUDECODE_SETTINGS_LOCAL_FILE_NAME as y, removeDirectory as yt, RulesyncCommandFrontmatterSchema as z, RULESYNC_CONFIG_SCHEMA_URL as zt };
|
|
51317
51880
|
|
|
51318
|
-
//# sourceMappingURL=import-
|
|
51881
|
+
//# sourceMappingURL=import-KXnvmbzr.js.map
|