rulesync 16.0.0 → 16.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -7
- package/dist/cli/index.cjs +298 -5
- package/dist/cli/index.js +299 -5
- package/dist/cli/index.js.map +1 -1
- package/dist/{import-BT5KR_K-.js → import-CArKOPG_.js} +2435 -504
- package/dist/import-CArKOPG_.js.map +1 -0
- package/dist/{import-Dyo6JaHg.cjs → import-DNfxS6QZ.cjs} +2448 -517
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +5 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/import-BT5KR_K-.js.map +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ZodError } from "zod";
|
|
2
2
|
import { meta, minLength, nonnegative, optional, refine, z } from "zod/mini";
|
|
3
3
|
import { cp, lstat, mkdir, mkdtemp, readFile, readdir, realpath, rm, stat, writeFile } from "node:fs/promises";
|
|
4
|
-
import path, { basename, dirname, extname, isAbsolute, join, posix, relative, resolve, sep, win32 } from "node:path";
|
|
4
|
+
import path, { basename, dirname, extname, isAbsolute, join, normalize, posix, relative, resolve, sep, win32 } from "node:path";
|
|
5
5
|
import { parse, printParseErrorCode } from "jsonc-parser";
|
|
6
6
|
import os from "node:os";
|
|
7
7
|
import { intersection, kebabCase, uniq } from "es-toolkit";
|
|
@@ -192,6 +192,7 @@ const ignoreProcessorToolTargetTuple = [
|
|
|
192
192
|
"kiro-cli",
|
|
193
193
|
"kiro-ide",
|
|
194
194
|
"qwencode",
|
|
195
|
+
"reasonix",
|
|
195
196
|
"roo",
|
|
196
197
|
"devin",
|
|
197
198
|
"vibe",
|
|
@@ -249,6 +250,7 @@ const commandsProcessorToolTargetTuple = [
|
|
|
249
250
|
"cursor",
|
|
250
251
|
"factorydroid",
|
|
251
252
|
"goose",
|
|
253
|
+
"grokcli",
|
|
252
254
|
"hermesagent",
|
|
253
255
|
"junie",
|
|
254
256
|
"kilo",
|
|
@@ -268,6 +270,9 @@ const commandsProcessorToolTargetTuple = [
|
|
|
268
270
|
const subagentsProcessorToolTargetTuple = [
|
|
269
271
|
"kilo",
|
|
270
272
|
"agentsmd",
|
|
273
|
+
"antigravity-cli",
|
|
274
|
+
"antigravity-ide",
|
|
275
|
+
"antigravity-plugin",
|
|
271
276
|
"augmentcode",
|
|
272
277
|
"claudecode",
|
|
273
278
|
"claudecode-plugin",
|
|
@@ -399,7 +404,9 @@ const permissionsProcessorToolTargetTuple = [
|
|
|
399
404
|
];
|
|
400
405
|
const checksProcessorToolTargetTuple = [
|
|
401
406
|
"amp",
|
|
407
|
+
"cursor",
|
|
402
408
|
"hermesagent",
|
|
409
|
+
"rovodev",
|
|
403
410
|
"takt"
|
|
404
411
|
];
|
|
405
412
|
//#endregion
|
|
@@ -1053,6 +1060,17 @@ const ConfigFileSchema = z.object({
|
|
|
1053
1060
|
});
|
|
1054
1061
|
z.required(ConfigParamsSchema);
|
|
1055
1062
|
/**
|
|
1063
|
+
* Normalizes the configuration file location to an absolute path.
|
|
1064
|
+
*
|
|
1065
|
+
* `ConfigResolver` always supplies the path it actually loaded; the fallback
|
|
1066
|
+
* only covers direct programmatic construction, where the conventional
|
|
1067
|
+
* location next to the input root is the best guess.
|
|
1068
|
+
*/
|
|
1069
|
+
function normalizeConfigFilePath({ configFilePath, inputRoot }) {
|
|
1070
|
+
if (configFilePath === void 0) return join(inputRoot, RULESYNC_CONFIG_RELATIVE_FILE_PATH);
|
|
1071
|
+
return isAbsolute(configFilePath) ? configFilePath : resolve(configFilePath);
|
|
1072
|
+
}
|
|
1073
|
+
/**
|
|
1056
1074
|
* Conflicting target pairs that cannot be used together
|
|
1057
1075
|
*/
|
|
1058
1076
|
const CONFLICTING_TARGET_PAIRS = [["augmentcode", "augmentcode-legacy"], ["claudecode", "claudecode-legacy"]];
|
|
@@ -1123,8 +1141,9 @@ var Config = class Config {
|
|
|
1123
1141
|
dryRun;
|
|
1124
1142
|
check;
|
|
1125
1143
|
inputRoot;
|
|
1144
|
+
configFilePath;
|
|
1126
1145
|
sources;
|
|
1127
|
-
constructor({ outputRoots, targets, features, verbose, delete: isDelete, global, silent, simulateCommands, simulateSubagents, simulateSkills, flattenedCommandNaming, gitignoreTargetsOnly, gitignoreDestination, dryRun, check, inputRoot, sources, configFileTargets }) {
|
|
1146
|
+
constructor({ outputRoots, targets, features, verbose, delete: isDelete, global, silent, simulateCommands, simulateSubagents, simulateSkills, flattenedCommandNaming, gitignoreTargetsOnly, gitignoreDestination, dryRun, check, inputRoot, configFilePath, sources, configFileTargets }) {
|
|
1128
1147
|
assertTargetsFeaturesExclusive({
|
|
1129
1148
|
targets,
|
|
1130
1149
|
features
|
|
@@ -1157,6 +1176,10 @@ var Config = class Config {
|
|
|
1157
1176
|
this.dryRun = dryRun ?? false;
|
|
1158
1177
|
this.check = check ?? false;
|
|
1159
1178
|
this.inputRoot = inputRoot === void 0 ? process.cwd() : isAbsolute(inputRoot) ? inputRoot : resolve(inputRoot);
|
|
1179
|
+
this.configFilePath = normalizeConfigFilePath({
|
|
1180
|
+
configFilePath,
|
|
1181
|
+
inputRoot: this.inputRoot
|
|
1182
|
+
});
|
|
1160
1183
|
this.sources = sources ?? [];
|
|
1161
1184
|
}
|
|
1162
1185
|
/**
|
|
@@ -1350,6 +1373,14 @@ var Config = class Config {
|
|
|
1350
1373
|
getInputRoot() {
|
|
1351
1374
|
return this.inputRoot;
|
|
1352
1375
|
}
|
|
1376
|
+
/**
|
|
1377
|
+
* Returns the absolute path of the configuration file this config was
|
|
1378
|
+
* resolved from. The file itself may not exist — `rulesync` runs fine
|
|
1379
|
+
* without one — so callers must treat this as a location, not a guarantee.
|
|
1380
|
+
*/
|
|
1381
|
+
getConfigFilePath() {
|
|
1382
|
+
return this.configFilePath;
|
|
1383
|
+
}
|
|
1353
1384
|
getSources() {
|
|
1354
1385
|
return this.sources;
|
|
1355
1386
|
}
|
|
@@ -1572,6 +1603,7 @@ var ConfigResolver = class {
|
|
|
1572
1603
|
fallback: getDefaults().check
|
|
1573
1604
|
}),
|
|
1574
1605
|
inputRoot: resolvedInputRoot !== void 0 ? resolve(resolvedInputRoot) : cwd,
|
|
1606
|
+
configFilePath: validatedConfigPath,
|
|
1575
1607
|
sources: configByFile.sources ?? getDefaults().sources,
|
|
1576
1608
|
flattenedCommandNaming: configByFile.flattenedCommandNaming ?? getDefaults().flattenedCommandNaming,
|
|
1577
1609
|
configFileTargets: extractConfigFileTargets(configByFile.targets)
|
|
@@ -2109,7 +2141,10 @@ const HookDefinitionSchema = z.looseObject({
|
|
|
2109
2141
|
model: z.optional(safeString),
|
|
2110
2142
|
args: z.optional(z.array(safeString)),
|
|
2111
2143
|
metadata: z.optional(z.looseObject({})),
|
|
2112
|
-
if: z.optional(safeString)
|
|
2144
|
+
if: z.optional(safeString),
|
|
2145
|
+
commandWindows: z.optional(safeString),
|
|
2146
|
+
asyncRewake: z.optional(z.boolean()),
|
|
2147
|
+
continueOnBlock: z.optional(z.boolean())
|
|
2113
2148
|
});
|
|
2114
2149
|
/**
|
|
2115
2150
|
* All canonical hook event names.
|
|
@@ -2163,6 +2198,7 @@ const HOOK_EVENTS = [
|
|
|
2163
2198
|
"configChange",
|
|
2164
2199
|
"cwdChanged",
|
|
2165
2200
|
"fileChanged",
|
|
2201
|
+
"directoryAdded",
|
|
2166
2202
|
"elicitation",
|
|
2167
2203
|
"elicitationResult"
|
|
2168
2204
|
];
|
|
@@ -2224,6 +2260,7 @@ const CLAUDE_HOOK_EVENTS = [
|
|
|
2224
2260
|
"configChange",
|
|
2225
2261
|
"cwdChanged",
|
|
2226
2262
|
"fileChanged",
|
|
2263
|
+
"directoryAdded",
|
|
2227
2264
|
"postCompact",
|
|
2228
2265
|
"elicitation",
|
|
2229
2266
|
"elicitationResult"
|
|
@@ -2256,7 +2293,15 @@ const DEVIN_HOOK_EVENTS = [
|
|
|
2256
2293
|
"permissionRequest",
|
|
2257
2294
|
"postCompact"
|
|
2258
2295
|
];
|
|
2259
|
-
/**
|
|
2296
|
+
/**
|
|
2297
|
+
* Hook events supported by OpenCode.
|
|
2298
|
+
*
|
|
2299
|
+
* `preCompact` maps to `experimental.session.compacting`, which the plugin docs
|
|
2300
|
+
* document as a named `(input, output)` hook rather than an `event.type`
|
|
2301
|
+
* dispatch; the other entries are all generic events.
|
|
2302
|
+
*
|
|
2303
|
+
* @see https://opencode.ai/docs/plugins/
|
|
2304
|
+
*/
|
|
2260
2305
|
const OPENCODE_HOOK_EVENTS = [
|
|
2261
2306
|
"sessionStart",
|
|
2262
2307
|
"preToolUse",
|
|
@@ -2264,9 +2309,19 @@ const OPENCODE_HOOK_EVENTS = [
|
|
|
2264
2309
|
"stop",
|
|
2265
2310
|
"afterFileEdit",
|
|
2266
2311
|
"afterShellExecution",
|
|
2267
|
-
"permissionRequest"
|
|
2312
|
+
"permissionRequest",
|
|
2313
|
+
"preCompact",
|
|
2314
|
+
"postCompact",
|
|
2315
|
+
"afterError",
|
|
2316
|
+
"fileChanged"
|
|
2268
2317
|
];
|
|
2269
|
-
/**
|
|
2318
|
+
/**
|
|
2319
|
+
* Hook events supported by Kilo. Identical to OpenCode: Kilo's plugin docs list
|
|
2320
|
+
* the same event surface, including `session.compacted`, `session.error`,
|
|
2321
|
+
* `file.watcher.updated` and the experimental compaction hook.
|
|
2322
|
+
*
|
|
2323
|
+
* @see https://kilo.ai/docs/automate/extending/plugins
|
|
2324
|
+
*/
|
|
2270
2325
|
const KILO_HOOK_EVENTS = OPENCODE_HOOK_EVENTS;
|
|
2271
2326
|
/**
|
|
2272
2327
|
* Hook events supported by Pi Coding Agent, bridged through a generated
|
|
@@ -2333,7 +2388,8 @@ const COPILOT_HOOK_EVENTS = [
|
|
|
2333
2388
|
* `sessionStart`, `sessionEnd`, `userPromptSubmitted`, `preToolUse`,
|
|
2334
2389
|
* `postToolUse`, `postToolUseFailure`, `agentStop`, `subagentStart`,
|
|
2335
2390
|
* `subagentStop`, `errorOccurred`, `preCompact`, `permissionRequest`,
|
|
2336
|
-
* `notification`, `
|
|
2391
|
+
* `notification`, `userPromptTransformed` ← `userPromptExpansion`,
|
|
2392
|
+
* `preMcpToolCall` ← `beforeMCPExecution`.
|
|
2337
2393
|
*
|
|
2338
2394
|
* `preMcpToolCall` (canonical `beforeMCPExecution`) was added in Copilot CLI
|
|
2339
2395
|
* v1.0.51 (2026-05-20) for hook providers to control outgoing MCP request
|
|
@@ -2355,6 +2411,7 @@ const COPILOTCLI_HOOK_EVENTS = [
|
|
|
2355
2411
|
"preCompact",
|
|
2356
2412
|
"permissionRequest",
|
|
2357
2413
|
"notification",
|
|
2414
|
+
"userPromptExpansion",
|
|
2358
2415
|
"beforeMCPExecution"
|
|
2359
2416
|
];
|
|
2360
2417
|
/**
|
|
@@ -2399,6 +2456,7 @@ const DEEPAGENTS_HOOK_EVENTS = [
|
|
|
2399
2456
|
/** Hook events supported by Codex CLI. */
|
|
2400
2457
|
const CODEXCLI_HOOK_EVENTS = [
|
|
2401
2458
|
"sessionStart",
|
|
2459
|
+
"sessionEnd",
|
|
2402
2460
|
"preToolUse",
|
|
2403
2461
|
"postToolUse",
|
|
2404
2462
|
"beforeSubmitPrompt",
|
|
@@ -2833,6 +2891,7 @@ const CANONICAL_TO_CLAUDE_EVENT_NAMES = {
|
|
|
2833
2891
|
configChange: "ConfigChange",
|
|
2834
2892
|
cwdChanged: "CwdChanged",
|
|
2835
2893
|
fileChanged: "FileChanged",
|
|
2894
|
+
directoryAdded: "DirectoryAdded",
|
|
2836
2895
|
postCompact: "PostCompact",
|
|
2837
2896
|
elicitation: "Elicitation",
|
|
2838
2897
|
elicitationResult: "ElicitationResult"
|
|
@@ -2954,7 +3013,11 @@ const CANONICAL_TO_OPENCODE_EVENT_NAMES = {
|
|
|
2954
3013
|
stop: "session.idle",
|
|
2955
3014
|
afterFileEdit: "file.edited",
|
|
2956
3015
|
afterShellExecution: "command.executed",
|
|
2957
|
-
permissionRequest: "permission.asked"
|
|
3016
|
+
permissionRequest: "permission.asked",
|
|
3017
|
+
preCompact: "experimental.session.compacting",
|
|
3018
|
+
postCompact: "session.compacted",
|
|
3019
|
+
afterError: "session.error",
|
|
3020
|
+
fileChanged: "file.watcher.updated"
|
|
2958
3021
|
};
|
|
2959
3022
|
/**
|
|
2960
3023
|
* Map canonical camelCase event names to Kilo dot-notation.
|
|
@@ -3030,6 +3093,7 @@ const CANONICAL_TO_COPILOTCLI_EVENT_NAMES = {
|
|
|
3030
3093
|
preCompact: "preCompact",
|
|
3031
3094
|
permissionRequest: "permissionRequest",
|
|
3032
3095
|
notification: "notification",
|
|
3096
|
+
userPromptExpansion: "userPromptTransformed",
|
|
3033
3097
|
beforeMCPExecution: "preMcpToolCall"
|
|
3034
3098
|
};
|
|
3035
3099
|
/** Map GitHub Copilot CLI event names back to canonical camelCase. */
|
|
@@ -3039,6 +3103,7 @@ const COPILOTCLI_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CA
|
|
|
3039
3103
|
*/
|
|
3040
3104
|
const CANONICAL_TO_CODEXCLI_EVENT_NAMES = {
|
|
3041
3105
|
sessionStart: "SessionStart",
|
|
3106
|
+
sessionEnd: "SessionEnd",
|
|
3042
3107
|
preToolUse: "PreToolUse",
|
|
3043
3108
|
postToolUse: "PostToolUse",
|
|
3044
3109
|
beforeSubmitPrompt: "UserPromptSubmit",
|
|
@@ -3461,6 +3526,25 @@ var RulesyncIgnore = class RulesyncIgnore extends RulesyncFile {
|
|
|
3461
3526
|
//#endregion
|
|
3462
3527
|
//#region src/types/mcp.ts
|
|
3463
3528
|
const EnvVarNameSchema = z.string().check(refine((value) => /^[A-Za-z_][A-Za-z0-9_]*$/.test(value), "envVars entries must be valid environment variable names"));
|
|
3529
|
+
/**
|
|
3530
|
+
* One `envVars` entry. A bare name reads the variable from Codex's own
|
|
3531
|
+
* environment; the object form names the environment to read it from, where
|
|
3532
|
+
* `source = "remote"` reads from the remote executor environment.
|
|
3533
|
+
* @see https://learn.chatgpt.com/docs/extend/mcp
|
|
3534
|
+
*/
|
|
3535
|
+
const EnvVarEntrySchema = z.union([EnvVarNameSchema, z.strictObject({
|
|
3536
|
+
name: EnvVarNameSchema,
|
|
3537
|
+
source: z.optional(z.enum(["local", "remote"]))
|
|
3538
|
+
})]);
|
|
3539
|
+
/**
|
|
3540
|
+
* Whether a value is usable as `envVars`. Applied in both directions by the
|
|
3541
|
+
* codex adapter, so an entry read out of somebody's `config.toml` can never be
|
|
3542
|
+
* imported into a `.rulesync/mcp.jsonc` that the next generate would refuse to
|
|
3543
|
+
* parse.
|
|
3544
|
+
*/
|
|
3545
|
+
function isEnvVarEntryArray(value) {
|
|
3546
|
+
return Array.isArray(value) && value.every((entry) => EnvVarEntrySchema.safeParse(entry).success);
|
|
3547
|
+
}
|
|
3464
3548
|
const McpServerSchema = z.looseObject({
|
|
3465
3549
|
type: z.optional(z.enum([
|
|
3466
3550
|
"local",
|
|
@@ -3475,7 +3559,8 @@ const McpServerSchema = z.looseObject({
|
|
|
3475
3559
|
url: z.optional(z.string()),
|
|
3476
3560
|
httpUrl: z.optional(z.string()),
|
|
3477
3561
|
env: z.optional(z.record(z.string(), z.string())),
|
|
3478
|
-
envVars: z.optional(z.array(
|
|
3562
|
+
envVars: z.optional(z.array(EnvVarEntrySchema)),
|
|
3563
|
+
experimentalEnvironment: z.optional(z.string()),
|
|
3479
3564
|
disabled: z.optional(z.boolean()),
|
|
3480
3565
|
networkTimeout: z.optional(z.number()),
|
|
3481
3566
|
timeout: z.optional(z.number()),
|
|
@@ -3641,6 +3726,8 @@ var RulesyncMcp = class RulesyncMcp extends RulesyncFile {
|
|
|
3641
3726
|
"description",
|
|
3642
3727
|
"exposed",
|
|
3643
3728
|
"envVars",
|
|
3729
|
+
"experimentalEnvironment",
|
|
3730
|
+
"experimental_environment",
|
|
3644
3731
|
"enabled"
|
|
3645
3732
|
])];
|
|
3646
3733
|
}));
|
|
@@ -3927,10 +4014,30 @@ const ClinePermissionsOverrideSchema = z.looseObject({
|
|
|
3927
4014
|
* portable and keeps them out of other tools' configs. Mirrors the OpenCode
|
|
3928
4015
|
* override; each value may be a bare action string or a pattern map.
|
|
3929
4016
|
*
|
|
4017
|
+
* `sandbox` is the sibling top-level block that governs the sandbox Kilo runs
|
|
4018
|
+
* commands in: `enabled` (boolean), `network` (`"deny"` and friends),
|
|
4019
|
+
* `allowed_hosts` (a list of `host` / `host:port` destination exceptions) and
|
|
4020
|
+
* `writable_paths`. It has no canonical permission category, so it is authored
|
|
4021
|
+
* here and emitted only for Kilo.
|
|
4022
|
+
*
|
|
4023
|
+
* Upstream restricts what a *project* config may say: `allowed_hosts` and
|
|
4024
|
+
* `writable_paths` are honored from the global config only, and a project
|
|
4025
|
+
* config may merely tighten (`enabled: true`, `network: "deny"`) — a
|
|
4026
|
+
* project-level network denial even clears the global destination exceptions.
|
|
4027
|
+
* rulesync mirrors that: at project scope only `enabled` and `network` are
|
|
4028
|
+
* written, and the rest are dropped with a warning rather than emitted into a
|
|
4029
|
+
* file Kilo would ignore.
|
|
4030
|
+
*
|
|
3930
4031
|
* @example
|
|
3931
4032
|
* { "permission": { "external_directory": "deny", "doom_loop": "ask" } }
|
|
4033
|
+
* @example
|
|
4034
|
+
* { "sandbox": { "enabled": true, "network": "deny" } }
|
|
4035
|
+
* @see https://kilo.ai/docs/getting-started/settings/sandboxing
|
|
3932
4036
|
*/
|
|
3933
|
-
const KiloPermissionsOverrideSchema = z.looseObject({
|
|
4037
|
+
const KiloPermissionsOverrideSchema = z.looseObject({
|
|
4038
|
+
permission: z.optional(z.record(z.string(), OpencodeOverridePermissionValueSchema)),
|
|
4039
|
+
sandbox: z.optional(z.looseObject({}))
|
|
4040
|
+
});
|
|
3934
4041
|
/**
|
|
3935
4042
|
* Tool-scoped override block for Claude Code. Claude Code's `permissions` object
|
|
3936
4043
|
* (in `.claude/settings.json`) carries non-list fields that have no canonical
|
|
@@ -3943,12 +4050,23 @@ const KiloPermissionsOverrideSchema = z.looseObject({ permission: z.optional(z.r
|
|
|
3943
4050
|
* authored without modeling each one; the managed `allow`/`ask`/`deny` arrays are
|
|
3944
4051
|
* ignored here (rulesync owns them).
|
|
3945
4052
|
*
|
|
4053
|
+
* `sandbox` is the sibling top-level settings subtree that governs the sandbox
|
|
4054
|
+
* Claude Code runs commands in (`sandbox.network.*`, `sandbox.filesystem.*`,
|
|
4055
|
+
* `sandbox.credentials`, `sandbox.allowAppleEvents`, ...). It has no canonical
|
|
4056
|
+
* permission category either — it constrains how a permitted command runs
|
|
4057
|
+
* rather than which commands are permitted — so it is a loose passthrough on
|
|
4058
|
+
* the same terms, merged into the top level of `.claude/settings.json`.
|
|
4059
|
+
*
|
|
3946
4060
|
* @example
|
|
3947
4061
|
* { "permissions": { "defaultMode": "acceptEdits", "additionalDirectories": ["../shared"] } }
|
|
4062
|
+
* @example
|
|
4063
|
+
* { "sandbox": { "network": { "allowedDomains": ["example.com"], "strictAllowlist": true } } }
|
|
4064
|
+
* @see https://code.claude.com/docs/en/sandboxing
|
|
3948
4065
|
*/
|
|
3949
4066
|
const ClaudecodePermissionsOverrideSchema = z.looseObject({
|
|
3950
4067
|
permission: z.optional(ToolScopedPermissionSchema),
|
|
3951
|
-
permissions: z.optional(z.looseObject({}))
|
|
4068
|
+
permissions: z.optional(z.looseObject({})),
|
|
4069
|
+
sandbox: z.optional(z.looseObject({}))
|
|
3952
4070
|
});
|
|
3953
4071
|
/**
|
|
3954
4072
|
* Tool-scoped override block for Mistral Vibe. Vibe's per-tool `BaseToolConfig`
|
|
@@ -4221,19 +4339,24 @@ const AmpPermissionsOverrideSchema = z.looseObject({
|
|
|
4221
4339
|
});
|
|
4222
4340
|
/**
|
|
4223
4341
|
* Tool-scoped override block for the Google Antigravity CLI. Antigravity's CLI
|
|
4224
|
-
* `settings.json` carries
|
|
4342
|
+
* `settings.json` carries four global autonomy/sandbox knobs outside the
|
|
4225
4343
|
* `permissions.allow/ask/deny` arrays rulesync manages: `toolPermission` (the
|
|
4226
4344
|
* global autonomy preset — `request-review` (default) / `proceed-in-sandbox` /
|
|
4227
|
-
* `always-proceed` / `strict`)
|
|
4228
|
-
* agent-run commands to OS containment)
|
|
4345
|
+
* `always-proceed` / `strict`), `enableTerminalSandbox` (a boolean confining
|
|
4346
|
+
* agent-run commands to OS containment), `artifactReviewPolicy` (whether the
|
|
4347
|
+
* agent's artifact changes are gated on a review prompt — `asks-for-review`
|
|
4348
|
+
* (default) / `agent-decides` / `always-proceed`) and `allowNonWorkspaceAccess`
|
|
4349
|
+
* (a boolean, off by default, letting the agent read or write files outside the
|
|
4350
|
+
* active workspace roots). Antigravity applies the allow/deny
|
|
4229
4351
|
* lists as per-rule exceptions to the preset at runtime, so rulesync only
|
|
4230
4352
|
* authors these keys verbatim — no precedence modeling is needed on our side.
|
|
4231
4353
|
* Fields placed here are merged onto the top level of
|
|
4232
4354
|
* `~/.gemini/antigravity-cli/settings.json` (global-only) and emitted only for
|
|
4233
4355
|
* the CLI. The Antigravity IDE exposes the same concepts through a GUI (no
|
|
4234
4356
|
* documented JSON schema), so this override does NOT apply to `antigravity-ide`.
|
|
4235
|
-
* Verified against https://antigravity.google/docs/cli/reference
|
|
4236
|
-
* https://antigravity.google/docs/cli/sandbox
|
|
4357
|
+
* Verified against https://antigravity.google/docs/cli/reference,
|
|
4358
|
+
* https://antigravity.google/docs/cli/sandbox and
|
|
4359
|
+
* https://antigravity.google/docs/cli/settings.
|
|
4237
4360
|
*
|
|
4238
4361
|
* @example
|
|
4239
4362
|
* { "toolPermission": "strict", "enableTerminalSandbox": true }
|
|
@@ -4246,7 +4369,13 @@ const AntigravityCliPermissionsOverrideSchema = z.looseObject({
|
|
|
4246
4369
|
"always-proceed",
|
|
4247
4370
|
"strict"
|
|
4248
4371
|
])),
|
|
4249
|
-
enableTerminalSandbox: z.optional(z.boolean())
|
|
4372
|
+
enableTerminalSandbox: z.optional(z.boolean()),
|
|
4373
|
+
artifactReviewPolicy: z.optional(z.enum([
|
|
4374
|
+
"asks-for-review",
|
|
4375
|
+
"agent-decides",
|
|
4376
|
+
"always-proceed"
|
|
4377
|
+
])),
|
|
4378
|
+
allowNonWorkspaceAccess: z.optional(z.boolean())
|
|
4250
4379
|
});
|
|
4251
4380
|
/**
|
|
4252
4381
|
* Tool-scoped override block for AugmentCode. AugmentCode's `toolPermissions[]`
|
|
@@ -4650,11 +4779,14 @@ const RulesyncRuleFrontmatterSchema = z.object({
|
|
|
4650
4779
|
description: z.optional(z.string()),
|
|
4651
4780
|
globs: z.optional(z.array(z.string()))
|
|
4652
4781
|
})),
|
|
4653
|
-
copilot: z.optional(z.looseObject({
|
|
4654
|
-
z.
|
|
4655
|
-
|
|
4656
|
-
|
|
4657
|
-
|
|
4782
|
+
copilot: z.optional(z.looseObject({
|
|
4783
|
+
excludeAgent: z.optional(z.union([
|
|
4784
|
+
z.literal("code-review"),
|
|
4785
|
+
z.literal("cloud-agent"),
|
|
4786
|
+
z.literal("coding-agent")
|
|
4787
|
+
])),
|
|
4788
|
+
name: z.optional(z.string())
|
|
4789
|
+
})),
|
|
4658
4790
|
antigravity: z.optional(z.looseObject({
|
|
4659
4791
|
trigger: z.optional(z.string()),
|
|
4660
4792
|
globs: z.optional(z.array(z.string()))
|
|
@@ -4862,6 +4994,7 @@ const RulesyncSkillFrontmatterSchema = z.looseObject({
|
|
|
4862
4994
|
arguments: z.optional(z.union([z.string(), z.array(z.string())])),
|
|
4863
4995
|
context: z.optional(z.string()),
|
|
4864
4996
|
agent: z.optional(z.string()),
|
|
4997
|
+
background: z.optional(z.boolean()),
|
|
4865
4998
|
hooks: z.optional(z.looseObject({})),
|
|
4866
4999
|
shell: z.optional(z.string()),
|
|
4867
5000
|
"disable-model-invocation": z.optional(z.boolean()),
|
|
@@ -4913,7 +5046,9 @@ const RulesyncSkillFrontmatterSchema = z.looseObject({
|
|
|
4913
5046
|
copilotcli: z.optional(z.looseObject({
|
|
4914
5047
|
license: z.optional(z.string()),
|
|
4915
5048
|
"allowed-tools": z.optional(z.union([z.string(), z.array(z.string())])),
|
|
4916
|
-
"argument-hint": z.optional(z.string())
|
|
5049
|
+
"argument-hint": z.optional(z.string()),
|
|
5050
|
+
"user-invocable": z.optional(z.boolean()),
|
|
5051
|
+
"disable-model-invocation": z.optional(z.boolean())
|
|
4917
5052
|
})),
|
|
4918
5053
|
pi: z.optional(z.looseObject({
|
|
4919
5054
|
"allowed-tools": z.optional(z.union([z.string(), z.array(z.string())])),
|
|
@@ -5199,6 +5334,38 @@ function resolveUserInvocable({ rootFrontmatter, section }) {
|
|
|
5199
5334
|
return section?.["user-invocable"] ?? rootFrontmatter["user-invocable"];
|
|
5200
5335
|
}
|
|
5201
5336
|
//#endregion
|
|
5337
|
+
//#region src/constants/cursor-paths.ts
|
|
5338
|
+
const CURSOR_DIR = ".cursor";
|
|
5339
|
+
const CURSOR_COMMANDS_DIR_PATH = join(CURSOR_DIR, "commands");
|
|
5340
|
+
const CURSOR_SKILLS_DIR_PATH = join(CURSOR_DIR, "skills");
|
|
5341
|
+
const CURSOR_AGENTS_DIR_PATH = join(CURSOR_DIR, "agents");
|
|
5342
|
+
const CURSOR_BUGBOT_FILE_NAME = "BUGBOT.md";
|
|
5343
|
+
const CURSOR_MCP_FILE_NAME = "mcp.json";
|
|
5344
|
+
const CURSOR_HOOKS_FILE_NAME = "hooks.json";
|
|
5345
|
+
const CURSOR_IGNORE_FILE_NAME = ".cursorignore";
|
|
5346
|
+
const CURSOR_PERMISSIONS_FILE_NAME = "cli.json";
|
|
5347
|
+
const CURSOR_PERMISSIONS_GLOBAL_FILE_NAME = "cli-config.json";
|
|
5348
|
+
//#endregion
|
|
5349
|
+
//#region src/constants/rovodev-paths.ts
|
|
5350
|
+
const ROVODEV_DIR = ".rovodev";
|
|
5351
|
+
const ROVODEV_SKILLS_DIR_PATH = join(ROVODEV_DIR, "skills");
|
|
5352
|
+
const ROVODEV_SUBAGENTS_DIR_PATH = join(ROVODEV_DIR, "subagents");
|
|
5353
|
+
const ROVODEV_MODULAR_RULES_DIR_PATH = join(ROVODEV_DIR, ".rulesync", "modular-rules");
|
|
5354
|
+
const ROVODEV_RULE_FILE_NAME = "AGENTS.md";
|
|
5355
|
+
const ROVODEV_LEGACY_RULE_FILE_NAME = "AGENTS.local.md";
|
|
5356
|
+
const ROVODEV_MCP_FILE_NAME = "mcp.json";
|
|
5357
|
+
const ROVODEV_CONFIG_FILE_NAME = "config.yml";
|
|
5358
|
+
const ROVODEV_AGENTS_SKILLS_DIR_PATH = join(".agents", "skills");
|
|
5359
|
+
const ROVODEV_PROMPTS_FILE_NAME = "prompts.yml";
|
|
5360
|
+
const ROVODEV_PROMPTS_DIR_PATH = join(ROVODEV_DIR, "prompts");
|
|
5361
|
+
/**
|
|
5362
|
+
* Custom instructions for Rovo Dev's code reviews: a plain-Markdown file (no
|
|
5363
|
+
* frontmatter) in the repository root's `.rovodev/` folder. Note the leading
|
|
5364
|
+
* dot in the file name.
|
|
5365
|
+
* @see https://support.atlassian.com/rovo/docs/set-custom-instructions-for-code-reviews/
|
|
5366
|
+
*/
|
|
5367
|
+
const ROVODEV_REVIEW_AGENT_FILE_NAME = ".review-agent.md";
|
|
5368
|
+
//#endregion
|
|
5202
5369
|
//#region src/constants/takt-paths.ts
|
|
5203
5370
|
const TAKT_DIR = ".takt";
|
|
5204
5371
|
const TAKT_FACETS_SUBDIR = "facets";
|
|
@@ -5649,6 +5816,276 @@ var AmpCheck = class AmpCheck extends ToolCheck {
|
|
|
5649
5816
|
}
|
|
5650
5817
|
};
|
|
5651
5818
|
//#endregion
|
|
5819
|
+
//#region src/features/checks/check-slug.ts
|
|
5820
|
+
/**
|
|
5821
|
+
* Turn a name that came out of a tool's own config file into one safe to use as
|
|
5822
|
+
* a `.rulesync/checks/<name>.md` file name. Shared by the adapters whose checks
|
|
5823
|
+
* collapse into a single file, since the name they read back is whatever the
|
|
5824
|
+
* user wrote there.
|
|
5825
|
+
*/
|
|
5826
|
+
function slugifyCheckName(value) {
|
|
5827
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48).replace(/-+$/, "");
|
|
5828
|
+
}
|
|
5829
|
+
//#endregion
|
|
5830
|
+
//#region src/features/checks/aggregated-check-file.ts
|
|
5831
|
+
/**
|
|
5832
|
+
* Shared machinery for the tools whose checks surface is **one aggregated
|
|
5833
|
+
* instruction file** rather than a file per check — Cursor Bugbot's
|
|
5834
|
+
* `.cursor/BUGBOT.md` and Rovo Dev's `.rovodev/.review-agent.md`.
|
|
5835
|
+
*
|
|
5836
|
+
* Both read the file as free prose, so the check identities have to be carried
|
|
5837
|
+
* in something invisible to the reader: an HTML-comment marker per section.
|
|
5838
|
+
* That marker convention, the escaping that keeps a check body from splitting
|
|
5839
|
+
* itself, and the import-side split are the same for both files, so they live
|
|
5840
|
+
* here once.
|
|
5841
|
+
*/
|
|
5842
|
+
/** Marks where one check starts inside the single instruction file. */
|
|
5843
|
+
const CHECK_MARKER_PATTERN = /^<!--\s*rulesync:check:(.+?)\s*-->[ \t]*$/gm;
|
|
5844
|
+
/**
|
|
5845
|
+
* A marker line a check body wrote itself — a rulesync doc fragment quoted in a
|
|
5846
|
+
* code block, say. Emitting it verbatim would split that check in two on the
|
|
5847
|
+
* next import, so `literal-` is inserted before `check:` on the way out and
|
|
5848
|
+
* taken off on the way back. `(?:literal-)*` makes it a ladder, so a body that
|
|
5849
|
+
* already contains an escaped marker survives the round trip too.
|
|
5850
|
+
*/
|
|
5851
|
+
const ESCAPABLE_MARKER_PATTERN = /^(<!--\s*rulesync:)((?:literal-)*check:.+?\s*-->[ \t]*)$/gm;
|
|
5852
|
+
const ESCAPED_MARKER_PATTERN = /^(<!--\s*rulesync:)literal-((?:literal-)*check:.+?\s*-->[ \t]*)$/gm;
|
|
5853
|
+
function renderCheckMarker(name) {
|
|
5854
|
+
return `<!-- rulesync:check:${name} -->`;
|
|
5855
|
+
}
|
|
5856
|
+
function escapeCheckMarkers(content) {
|
|
5857
|
+
return content.replace(ESCAPABLE_MARKER_PATTERN, "$1literal-$2");
|
|
5858
|
+
}
|
|
5859
|
+
function unescapeCheckMarkers(content) {
|
|
5860
|
+
return content.replace(ESCAPED_MARKER_PATTERN, "$1$2");
|
|
5861
|
+
}
|
|
5862
|
+
function findCheckMarkers(fileContent) {
|
|
5863
|
+
CHECK_MARKER_PATTERN.lastIndex = 0;
|
|
5864
|
+
const markers = [];
|
|
5865
|
+
let match = CHECK_MARKER_PATTERN.exec(fileContent);
|
|
5866
|
+
while (match !== null) {
|
|
5867
|
+
markers.push({
|
|
5868
|
+
name: match[1] ?? "",
|
|
5869
|
+
start: match.index,
|
|
5870
|
+
end: match.index + match[0].length
|
|
5871
|
+
});
|
|
5872
|
+
match = CHECK_MARKER_PATTERN.exec(fileContent);
|
|
5873
|
+
}
|
|
5874
|
+
return markers;
|
|
5875
|
+
}
|
|
5876
|
+
/**
|
|
5877
|
+
* Whether the file holds instruction text ahead of the first marker — the
|
|
5878
|
+
* question the "generating replaces this" warning asks. A file with no marker
|
|
5879
|
+
* at all is entirely hand-written, so it qualifies; an empty one does not,
|
|
5880
|
+
* since there is nothing to replace.
|
|
5881
|
+
*/
|
|
5882
|
+
function hasHandWrittenPreamble(fileContent) {
|
|
5883
|
+
const firstMarkerStart = findCheckMarkers(fileContent)[0]?.start ?? fileContent.length;
|
|
5884
|
+
return fileContent.slice(0, firstMarkerStart).trim().length > 0;
|
|
5885
|
+
}
|
|
5886
|
+
/**
|
|
5887
|
+
* Whether the file is nothing but sections rulesync generated — the question
|
|
5888
|
+
* the deletion guard asks, and a stricter one than
|
|
5889
|
+
* {@link hasHandWrittenPreamble}. A file carrying no marker at all is not
|
|
5890
|
+
* rulesync's to remove even when it is empty: rulesync never wrote it, so an
|
|
5891
|
+
* empty one is somebody's placeholder rather than our leftover.
|
|
5892
|
+
*/
|
|
5893
|
+
function isOnlyGeneratedSections(fileContent) {
|
|
5894
|
+
const firstMarkerStart = findCheckMarkers(fileContent)[0]?.start;
|
|
5895
|
+
if (firstMarkerStart === void 0) return false;
|
|
5896
|
+
return fileContent.slice(0, firstMarkerStart).trim().length === 0;
|
|
5897
|
+
}
|
|
5898
|
+
/**
|
|
5899
|
+
* The instruction text one check contributes. Neither file has a field to put a
|
|
5900
|
+
* summary in, so `description` is used only when there is no body.
|
|
5901
|
+
*/
|
|
5902
|
+
function toInstruction(rulesyncCheck) {
|
|
5903
|
+
const body = rulesyncCheck.getBody().trim();
|
|
5904
|
+
if (body.length > 0) return body;
|
|
5905
|
+
return rulesyncCheck.getFrontmatter().description?.trim() ?? "";
|
|
5906
|
+
}
|
|
5907
|
+
function renderCheckSection(rulesyncCheck) {
|
|
5908
|
+
const name = basename(rulesyncCheck.getRelativeFilePath(), ".md");
|
|
5909
|
+
const heading = `## ${name}`;
|
|
5910
|
+
const instruction = toInstruction(rulesyncCheck);
|
|
5911
|
+
const lines = [renderCheckMarker(name), heading];
|
|
5912
|
+
if (instruction.length > 0) lines.push("", escapeCheckMarkers(instruction));
|
|
5913
|
+
return lines.join("\n");
|
|
5914
|
+
}
|
|
5915
|
+
function renderCheckFile(rulesyncChecks) {
|
|
5916
|
+
return `${rulesyncChecks.map(renderCheckSection).join("\n\n")}\n`;
|
|
5917
|
+
}
|
|
5918
|
+
/** Drop the heading generate writes, so a round trip does not stack headings. */
|
|
5919
|
+
function stripGeneratedHeading(section, name) {
|
|
5920
|
+
const [firstLine, ...rest] = section.split("\n");
|
|
5921
|
+
if (firstLine?.trim() === `## ${name}`) return rest.join("\n").trim();
|
|
5922
|
+
return section.trim();
|
|
5923
|
+
}
|
|
5924
|
+
/**
|
|
5925
|
+
* Split an aggregated instruction file back into one check per section.
|
|
5926
|
+
*
|
|
5927
|
+
* Content ahead of the first marker — and a hand-written file with no markers
|
|
5928
|
+
* at all — becomes a single check named `fallbackName`, so nothing in the file
|
|
5929
|
+
* is dropped.
|
|
5930
|
+
*/
|
|
5931
|
+
function splitCheckFile({ fileContent, fallbackName }) {
|
|
5932
|
+
const sections = [];
|
|
5933
|
+
const markers = findCheckMarkers(fileContent);
|
|
5934
|
+
const preambleEnd = markers[0]?.start ?? fileContent.length;
|
|
5935
|
+
const preamble = fileContent.slice(0, preambleEnd).trim();
|
|
5936
|
+
if (preamble.length > 0) sections.push({
|
|
5937
|
+
name: fallbackName,
|
|
5938
|
+
content: unescapeCheckMarkers(preamble)
|
|
5939
|
+
});
|
|
5940
|
+
for (const [index, marker] of markers.entries()) {
|
|
5941
|
+
const sectionEnd = markers[index + 1]?.start ?? fileContent.length;
|
|
5942
|
+
const markerName = marker.name.trim();
|
|
5943
|
+
const name = slugifyCheckName(markerName) || fallbackName;
|
|
5944
|
+
const content = stripGeneratedHeading(fileContent.slice(marker.end, sectionEnd).trim(), markerName);
|
|
5945
|
+
sections.push({
|
|
5946
|
+
name,
|
|
5947
|
+
content: unescapeCheckMarkers(content)
|
|
5948
|
+
});
|
|
5949
|
+
}
|
|
5950
|
+
const used = /* @__PURE__ */ new Set();
|
|
5951
|
+
return sections.map(({ name, content }) => {
|
|
5952
|
+
let uniqueName = name;
|
|
5953
|
+
let suffix = 2;
|
|
5954
|
+
while (used.has(uniqueName)) {
|
|
5955
|
+
uniqueName = `${name}-${suffix}`;
|
|
5956
|
+
suffix += 1;
|
|
5957
|
+
}
|
|
5958
|
+
used.add(uniqueName);
|
|
5959
|
+
return new RulesyncCheck({
|
|
5960
|
+
outputRoot: ".",
|
|
5961
|
+
relativeDirPath: RULESYNC_CHECKS_RELATIVE_DIR_PATH,
|
|
5962
|
+
relativeFilePath: `${uniqueName}.md`,
|
|
5963
|
+
frontmatter: { targets: ["*"] },
|
|
5964
|
+
body: content
|
|
5965
|
+
});
|
|
5966
|
+
});
|
|
5967
|
+
}
|
|
5968
|
+
//#endregion
|
|
5969
|
+
//#region src/features/checks/cursor-check.ts
|
|
5970
|
+
const FALLBACK_CHECK_NAME$1 = "bugbot";
|
|
5971
|
+
/**
|
|
5972
|
+
* Checks adapter for Cursor Bugbot (`.cursor/BUGBOT.md`).
|
|
5973
|
+
*
|
|
5974
|
+
* Bugbot takes one aggregated instruction file per directory rather than a file
|
|
5975
|
+
* per check, so every `.rulesync/checks/*.md` targeting Cursor collapses into
|
|
5976
|
+
* the repository-root `.cursor/BUGBOT.md` — hence {@link fromRulesyncChecks}
|
|
5977
|
+
* rather than the usual per-check conversion. Each check becomes one section:
|
|
5978
|
+
* an HTML-comment marker carrying the check name, an `## <name>` heading, and
|
|
5979
|
+
* the check body as the instruction text (the `description` is used when the
|
|
5980
|
+
* body is empty).
|
|
5981
|
+
*
|
|
5982
|
+
* Bugbot reads the file as free prose, so a check's `severity` and `tools` have
|
|
5983
|
+
* no equivalent there: they are not written and do not come back on import. So
|
|
5984
|
+
* is `description` whenever the check also has a body.
|
|
5985
|
+
*
|
|
5986
|
+
* Project scope only — Bugbot reads repository files, and there is no
|
|
5987
|
+
* user-level instruction file. Bugbot also merges nested `<dir>/.cursor/BUGBOT.md`
|
|
5988
|
+
* files found while traversing upward from changed files, but rulesync check
|
|
5989
|
+
* sources carry no directory-placement semantics, so only the root file is
|
|
5990
|
+
* generated.
|
|
5991
|
+
*
|
|
5992
|
+
* On import the markers split the file back into one check per section; content
|
|
5993
|
+
* before the first marker — and a hand-written file with no markers at all —
|
|
5994
|
+
* becomes a single `bugbot` check, so nothing in the file is dropped. A file
|
|
5995
|
+
* holding anything rulesync did not write is never deleted either (see
|
|
5996
|
+
* {@link canDeleteAuxiliaryFiles}), though generating checks for Cursor does
|
|
5997
|
+
* replace it — import first to keep what is there, which is warned about.
|
|
5998
|
+
*
|
|
5999
|
+
* @see https://cursor.com/docs/bugbot
|
|
6000
|
+
*/
|
|
6001
|
+
var CursorCheck = class CursorCheck extends ToolCheck {
|
|
6002
|
+
static getSettablePaths(_options = {}) {
|
|
6003
|
+
return {
|
|
6004
|
+
relativeDirPath: CURSOR_DIR,
|
|
6005
|
+
relativeFilePath: CURSOR_BUGBOT_FILE_NAME
|
|
6006
|
+
};
|
|
6007
|
+
}
|
|
6008
|
+
static isTargetedByRulesyncCheck(rulesyncCheck) {
|
|
6009
|
+
return this.isTargetedByRulesyncCheckDefault({
|
|
6010
|
+
rulesyncCheck,
|
|
6011
|
+
toolTarget: "cursor"
|
|
6012
|
+
});
|
|
6013
|
+
}
|
|
6014
|
+
/**
|
|
6015
|
+
* Ownership guard the processor consults before it deletes anything for this
|
|
6016
|
+
* tool. `.cursor/BUGBOT.md` is a file Cursor's own documentation tells users
|
|
6017
|
+
* to hand-write, so anything in it that rulesync did not write is not
|
|
6018
|
+
* rulesync's to remove — dropping the last check targeting Cursor must not
|
|
6019
|
+
* take somebody's hand-written review instructions with it. Deletion is
|
|
6020
|
+
* therefore allowed only for a file that is nothing but generated sections:
|
|
6021
|
+
* one that carries no marker at all, or that carries hand-written text ahead
|
|
6022
|
+
* of the first marker, stays.
|
|
6023
|
+
*/
|
|
6024
|
+
static async canDeleteAuxiliaryFiles({ outputRoot }) {
|
|
6025
|
+
const paths = CursorCheck.getSettablePaths();
|
|
6026
|
+
const fileContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath ?? "BUGBOT.md"));
|
|
6027
|
+
if (fileContent === null) return true;
|
|
6028
|
+
return isOnlyGeneratedSections(fileContent);
|
|
6029
|
+
}
|
|
6030
|
+
static fromRulesyncCheck(_params) {
|
|
6031
|
+
throw new Error("Cursor checks are built from all checks at once; use fromRulesyncChecks.");
|
|
6032
|
+
}
|
|
6033
|
+
static async fromRulesyncChecks({ outputRoot = process.cwd(), rulesyncChecks, global = false, logger }) {
|
|
6034
|
+
if (rulesyncChecks.length === 0) return [];
|
|
6035
|
+
const paths = CursorCheck.getSettablePaths({ global });
|
|
6036
|
+
const relativeFilePath = paths.relativeFilePath ?? "BUGBOT.md";
|
|
6037
|
+
const filePath = join(outputRoot, paths.relativeDirPath, relativeFilePath);
|
|
6038
|
+
if (hasHandWrittenPreamble(await readFileContentOrNull(filePath) ?? "")) logger?.warn(`Cursor checks: ${filePath} holds instructions rulesync did not write, and generating replaces the whole file. Run \`rulesync import --targets cursor --features checks\` first to keep them.`);
|
|
6039
|
+
const fileContent = renderCheckFile(rulesyncChecks);
|
|
6040
|
+
return [new CursorCheck({
|
|
6041
|
+
outputRoot,
|
|
6042
|
+
relativeDirPath: paths.relativeDirPath,
|
|
6043
|
+
relativeFilePath,
|
|
6044
|
+
fileContent,
|
|
6045
|
+
global
|
|
6046
|
+
})];
|
|
6047
|
+
}
|
|
6048
|
+
static async fromFile({ outputRoot = process.cwd(), global = false }) {
|
|
6049
|
+
const paths = CursorCheck.getSettablePaths({ global });
|
|
6050
|
+
const relativeFilePath = paths.relativeFilePath ?? "BUGBOT.md";
|
|
6051
|
+
const filePath = join(outputRoot, paths.relativeDirPath, relativeFilePath);
|
|
6052
|
+
return new CursorCheck({
|
|
6053
|
+
outputRoot,
|
|
6054
|
+
relativeDirPath: paths.relativeDirPath,
|
|
6055
|
+
relativeFilePath,
|
|
6056
|
+
fileContent: await readFileContentOrNull(filePath) ?? "",
|
|
6057
|
+
global
|
|
6058
|
+
});
|
|
6059
|
+
}
|
|
6060
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
6061
|
+
return new CursorCheck({
|
|
6062
|
+
outputRoot,
|
|
6063
|
+
relativeDirPath,
|
|
6064
|
+
relativeFilePath,
|
|
6065
|
+
fileContent: "",
|
|
6066
|
+
validate: false,
|
|
6067
|
+
global
|
|
6068
|
+
});
|
|
6069
|
+
}
|
|
6070
|
+
validate() {
|
|
6071
|
+
return {
|
|
6072
|
+
success: true,
|
|
6073
|
+
error: null
|
|
6074
|
+
};
|
|
6075
|
+
}
|
|
6076
|
+
toRulesyncCheck() {
|
|
6077
|
+
const first = this.toRulesyncChecks()[0];
|
|
6078
|
+
if (!first) throw new Error(`No check instructions found in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}.`);
|
|
6079
|
+
return first;
|
|
6080
|
+
}
|
|
6081
|
+
toRulesyncChecks() {
|
|
6082
|
+
return splitCheckFile({
|
|
6083
|
+
fileContent: this.getFileContent(),
|
|
6084
|
+
fallbackName: FALLBACK_CHECK_NAME$1
|
|
6085
|
+
});
|
|
6086
|
+
}
|
|
6087
|
+
};
|
|
6088
|
+
//#endregion
|
|
5652
6089
|
//#region src/constants/hermesagent-paths.ts
|
|
5653
6090
|
/**
|
|
5654
6091
|
* Hermes Agent configuration-layout conventions.
|
|
@@ -5666,8 +6103,22 @@ var AmpCheck = class AmpCheck extends ToolCheck {
|
|
|
5666
6103
|
*/
|
|
5667
6104
|
/** Project-root instruction file auto-injected by Hermes Agent. */
|
|
5668
6105
|
const HERMESAGENT_RULE_FILE_NAME = ".hermes.md";
|
|
5669
|
-
/**
|
|
6106
|
+
/**
|
|
6107
|
+
* Root directory for Hermes Agent global configuration (the HERMES_HOME dir).
|
|
6108
|
+
* Also the project-local plugin tree, which is `.hermes/` on every platform.
|
|
6109
|
+
*/
|
|
5670
6110
|
const HERMESAGENT_GLOBAL_DIR = ".hermes";
|
|
6111
|
+
/**
|
|
6112
|
+
* Home-relative global profile root on Windows: upstream defaults to
|
|
6113
|
+
* `%LOCALAPPDATA%\hermes` there, not `~/.hermes`.
|
|
6114
|
+
* Resolve it through `getHermesagentGlobalDir()` rather than reading it directly.
|
|
6115
|
+
*
|
|
6116
|
+
* Home-relative rather than read from `LOCALAPPDATA`, matching how every other
|
|
6117
|
+
* Windows global path in rulesync is spelled (`ZED_GLOBAL_WIN32_DIR`,
|
|
6118
|
+
* `WARP_WIN32_DIR`). A profile with `LOCALAPPDATA` redirected elsewhere is not
|
|
6119
|
+
* followed; those users should set `HERMES_HOME` explicitly.
|
|
6120
|
+
*/
|
|
6121
|
+
const HERMESAGENT_GLOBAL_WIN32_DIR = join("AppData", "Local", "hermes");
|
|
5671
6122
|
/** MCP servers and other settings live in `config.yaml` under `~/.hermes/`. */
|
|
5672
6123
|
const HERMESAGENT_CONFIG_FILE_NAME = "config.yaml";
|
|
5673
6124
|
const HERMESAGENT_CONFIG_FILE_PATH = join(HERMESAGENT_GLOBAL_DIR, HERMESAGENT_CONFIG_FILE_NAME);
|
|
@@ -5890,6 +6341,114 @@ var HermesagentCheck = class HermesagentCheck extends ToolCheck {
|
|
|
5890
6341
|
}
|
|
5891
6342
|
};
|
|
5892
6343
|
//#endregion
|
|
6344
|
+
//#region src/features/checks/rovodev-check.ts
|
|
6345
|
+
const FALLBACK_CHECK_NAME = "review-agent";
|
|
6346
|
+
/**
|
|
6347
|
+
* Checks adapter for Rovo Dev CLI's code-review custom instructions
|
|
6348
|
+
* (`.rovodev/.review-agent.md`).
|
|
6349
|
+
*
|
|
6350
|
+
* Rovo Dev takes one plain-Markdown instruction file at the repository root's
|
|
6351
|
+
* `.rovodev/` folder — no frontmatter, and note the leading dot in the file
|
|
6352
|
+
* name. Like Cursor Bugbot it is a single aggregated file rather than a file
|
|
6353
|
+
* per check, so every `.rulesync/checks/*.md` targeting Rovo Dev collapses into
|
|
6354
|
+
* it via {@link fromRulesyncChecks}, with each check written as a marked
|
|
6355
|
+
* section (see `aggregated-check-file.ts` for the marker convention the two
|
|
6356
|
+
* adapters share).
|
|
6357
|
+
*
|
|
6358
|
+
* Rovo Dev reads the file as free prose, so a check's `severity` and `tools`
|
|
6359
|
+
* have no equivalent there: they are not written and do not come back on
|
|
6360
|
+
* import. Neither does `description` whenever the check also has a body.
|
|
6361
|
+
*
|
|
6362
|
+
* Project scope only — these are per-repository review instructions, and Rovo
|
|
6363
|
+
* Dev documents no user-level equivalent. (The `permissions` adapter for the
|
|
6364
|
+
* same tool is the opposite: global only.)
|
|
6365
|
+
*
|
|
6366
|
+
* @see https://support.atlassian.com/rovo/docs/set-custom-instructions-for-code-reviews/
|
|
6367
|
+
*/
|
|
6368
|
+
var RovodevCheck = class RovodevCheck extends ToolCheck {
|
|
6369
|
+
static getSettablePaths(_options = {}) {
|
|
6370
|
+
return {
|
|
6371
|
+
relativeDirPath: ROVODEV_DIR,
|
|
6372
|
+
relativeFilePath: ROVODEV_REVIEW_AGENT_FILE_NAME
|
|
6373
|
+
};
|
|
6374
|
+
}
|
|
6375
|
+
static isTargetedByRulesyncCheck(rulesyncCheck) {
|
|
6376
|
+
return this.isTargetedByRulesyncCheckDefault({
|
|
6377
|
+
rulesyncCheck,
|
|
6378
|
+
toolTarget: "rovodev"
|
|
6379
|
+
});
|
|
6380
|
+
}
|
|
6381
|
+
/**
|
|
6382
|
+
* Ownership guard the processor consults before it deletes anything for this
|
|
6383
|
+
* tool. `.review-agent.md` is a file Rovo Dev's own documentation tells users
|
|
6384
|
+
* to hand-write, so anything in it that rulesync did not write is not
|
|
6385
|
+
* rulesync's to remove — dropping the last check targeting Rovo Dev must not
|
|
6386
|
+
* take somebody's hand-written review instructions with it.
|
|
6387
|
+
*/
|
|
6388
|
+
static async canDeleteAuxiliaryFiles({ outputRoot }) {
|
|
6389
|
+
const paths = RovodevCheck.getSettablePaths();
|
|
6390
|
+
const fileContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath ?? ".review-agent.md"));
|
|
6391
|
+
if (fileContent === null) return true;
|
|
6392
|
+
return isOnlyGeneratedSections(fileContent);
|
|
6393
|
+
}
|
|
6394
|
+
static fromRulesyncCheck(_params) {
|
|
6395
|
+
throw new Error("Rovo Dev checks are built from all checks at once; use fromRulesyncChecks.");
|
|
6396
|
+
}
|
|
6397
|
+
static async fromRulesyncChecks({ outputRoot = process.cwd(), rulesyncChecks, global = false, logger }) {
|
|
6398
|
+
if (rulesyncChecks.length === 0) return [];
|
|
6399
|
+
const paths = RovodevCheck.getSettablePaths({ global });
|
|
6400
|
+
const relativeFilePath = paths.relativeFilePath ?? ".review-agent.md";
|
|
6401
|
+
const filePath = join(outputRoot, paths.relativeDirPath, relativeFilePath);
|
|
6402
|
+
if (hasHandWrittenPreamble(await readFileContentOrNull(filePath) ?? "")) logger?.warn(`Rovo Dev checks: ${filePath} holds instructions rulesync did not write, and generating replaces the whole file. Run \`rulesync import --targets rovodev --features checks\` first to keep them.`);
|
|
6403
|
+
return [new RovodevCheck({
|
|
6404
|
+
outputRoot,
|
|
6405
|
+
relativeDirPath: paths.relativeDirPath,
|
|
6406
|
+
relativeFilePath,
|
|
6407
|
+
fileContent: renderCheckFile(rulesyncChecks),
|
|
6408
|
+
global
|
|
6409
|
+
})];
|
|
6410
|
+
}
|
|
6411
|
+
static async fromFile({ outputRoot = process.cwd(), global = false }) {
|
|
6412
|
+
const paths = RovodevCheck.getSettablePaths({ global });
|
|
6413
|
+
const relativeFilePath = paths.relativeFilePath ?? ".review-agent.md";
|
|
6414
|
+
const filePath = join(outputRoot, paths.relativeDirPath, relativeFilePath);
|
|
6415
|
+
return new RovodevCheck({
|
|
6416
|
+
outputRoot,
|
|
6417
|
+
relativeDirPath: paths.relativeDirPath,
|
|
6418
|
+
relativeFilePath,
|
|
6419
|
+
fileContent: await readFileContentOrNull(filePath) ?? "",
|
|
6420
|
+
global
|
|
6421
|
+
});
|
|
6422
|
+
}
|
|
6423
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
6424
|
+
return new RovodevCheck({
|
|
6425
|
+
outputRoot,
|
|
6426
|
+
relativeDirPath,
|
|
6427
|
+
relativeFilePath,
|
|
6428
|
+
fileContent: "",
|
|
6429
|
+
validate: false,
|
|
6430
|
+
global
|
|
6431
|
+
});
|
|
6432
|
+
}
|
|
6433
|
+
validate() {
|
|
6434
|
+
return {
|
|
6435
|
+
success: true,
|
|
6436
|
+
error: null
|
|
6437
|
+
};
|
|
6438
|
+
}
|
|
6439
|
+
toRulesyncCheck() {
|
|
6440
|
+
const first = this.toRulesyncChecks()[0];
|
|
6441
|
+
if (!first) throw new Error(`No check instructions found in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}.`);
|
|
6442
|
+
return first;
|
|
6443
|
+
}
|
|
6444
|
+
toRulesyncChecks() {
|
|
6445
|
+
return splitCheckFile({
|
|
6446
|
+
fileContent: this.getFileContent(),
|
|
6447
|
+
fallbackName: FALLBACK_CHECK_NAME
|
|
6448
|
+
});
|
|
6449
|
+
}
|
|
6450
|
+
};
|
|
6451
|
+
//#endregion
|
|
5893
6452
|
//#region src/constants/codexcli-paths.ts
|
|
5894
6453
|
const CODEXCLI_DIR = ".codex";
|
|
5895
6454
|
const CODEXCLI_PROMPTS_DIR_PATH = join(CODEXCLI_DIR, "prompts");
|
|
@@ -6002,11 +6561,14 @@ function mergeSharedConfigDeep({ base, patch }) {
|
|
|
6002
6561
|
}
|
|
6003
6562
|
const CLAUDE_SETTINGS_SHARED_FILE_KEY = ".claude/settings.json";
|
|
6004
6563
|
const HERMES_CONFIG_SHARED_FILE_KEY = ".hermes/config.yaml";
|
|
6564
|
+
const HERMES_WIN32_CONFIG_SHARED_FILE_KEY = "AppData/Local/hermes/config.yaml";
|
|
6565
|
+
const HERMES_HOME_CONFIG_SHARED_FILE_KEY = "config.yaml";
|
|
6005
6566
|
const TAKT_CONFIG_SHARED_FILE_KEY = ".takt/config.yaml";
|
|
6006
6567
|
const CODEXCLI_CONFIG_SHARED_FILE_KEY = ".codex/config.toml";
|
|
6007
6568
|
const GROKCLI_CONFIG_SHARED_FILE_KEY = ".grok/config.toml";
|
|
6008
6569
|
const VIBE_CONFIG_SHARED_FILE_KEY = ".vibe/config.toml";
|
|
6009
6570
|
const KIMI_CODE_CONFIG_SHARED_FILE_KEY = ".kimi-code/config.toml";
|
|
6571
|
+
const KIMI_CODE_HOME_CONFIG_SHARED_FILE_KEY = "config.toml";
|
|
6010
6572
|
const REASONIX_PROJECT_CONFIG_SHARED_FILE_KEY = "reasonix.toml";
|
|
6011
6573
|
const REASONIX_GLOBAL_CONFIG_SHARED_FILE_KEY = ".reasonix/config.toml";
|
|
6012
6574
|
/**
|
|
@@ -6030,6 +6592,65 @@ const sharedConfigFileKey = ({ relativeDirPath, relativeFilePath }) => {
|
|
|
6030
6592
|
* lock-step with the writers derived from the processor registry, so an
|
|
6031
6593
|
* undeclared writer fails CI instead of merging by accident.
|
|
6032
6594
|
*/
|
|
6595
|
+
/**
|
|
6596
|
+
* Hermes writes one `config.yaml`, but its global profile root has three
|
|
6597
|
+
* spellings (`~/.hermes`, the win32 `%LOCALAPPDATA%\hermes`, and `HERMES_HOME`
|
|
6598
|
+
* itself). They are the same file with the same owners, so the declaration is
|
|
6599
|
+
* written once and shared — a policy edit cannot land on one spelling only.
|
|
6600
|
+
*/
|
|
6601
|
+
const HERMES_CONFIG_DECLARATION = {
|
|
6602
|
+
format: "yaml",
|
|
6603
|
+
features: {
|
|
6604
|
+
commands: {
|
|
6605
|
+
kind: "replace-owned-keys",
|
|
6606
|
+
ownedKeys: ["plugins"]
|
|
6607
|
+
},
|
|
6608
|
+
subagents: {
|
|
6609
|
+
kind: "replace-owned-keys",
|
|
6610
|
+
ownedKeys: ["plugins"]
|
|
6611
|
+
},
|
|
6612
|
+
mcp: {
|
|
6613
|
+
kind: "replace-owned-keys",
|
|
6614
|
+
ownedKeys: ["mcp_servers"]
|
|
6615
|
+
},
|
|
6616
|
+
hooks: {
|
|
6617
|
+
kind: "replace-owned-keys",
|
|
6618
|
+
ownedKeys: ["hooks"]
|
|
6619
|
+
},
|
|
6620
|
+
permissions: {
|
|
6621
|
+
kind: "deep-merge",
|
|
6622
|
+
replaceKeys: ["permissions"]
|
|
6623
|
+
}
|
|
6624
|
+
}
|
|
6625
|
+
};
|
|
6626
|
+
/**
|
|
6627
|
+
* Kimi Code's user config: hooks owns the flat `hooks` array; permissions owns
|
|
6628
|
+
* the ordered rule list and optional coarse default mode. `KIMI_CODE_HOME` can
|
|
6629
|
+
* name the profile directory itself, so the file has two spellings that share
|
|
6630
|
+
* one declaration — a policy edit cannot land on only one of them.
|
|
6631
|
+
*/
|
|
6632
|
+
const KIMI_CODE_CONFIG_DECLARATION = {
|
|
6633
|
+
format: "toml",
|
|
6634
|
+
invalidRootPolicy: "error",
|
|
6635
|
+
features: {
|
|
6636
|
+
hooks: {
|
|
6637
|
+
kind: "replace-owned-keys",
|
|
6638
|
+
ownedKeys: ["hooks"]
|
|
6639
|
+
},
|
|
6640
|
+
mcp: {
|
|
6641
|
+
kind: "replace-owned-keys",
|
|
6642
|
+
ownedKeys: ["mcp"]
|
|
6643
|
+
},
|
|
6644
|
+
permissions: {
|
|
6645
|
+
kind: "replace-owned-keys",
|
|
6646
|
+
ownedKeys: [
|
|
6647
|
+
"permission",
|
|
6648
|
+
"default_permission_mode",
|
|
6649
|
+
"tools"
|
|
6650
|
+
]
|
|
6651
|
+
}
|
|
6652
|
+
}
|
|
6653
|
+
};
|
|
6033
6654
|
const SHARED_CONFIG_OWNERSHIP = {
|
|
6034
6655
|
[CLAUDE_SETTINGS_SHARED_FILE_KEY]: {
|
|
6035
6656
|
format: "json",
|
|
@@ -6048,31 +6669,9 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
6048
6669
|
}
|
|
6049
6670
|
}
|
|
6050
6671
|
},
|
|
6051
|
-
[HERMES_CONFIG_SHARED_FILE_KEY]:
|
|
6052
|
-
|
|
6053
|
-
|
|
6054
|
-
commands: {
|
|
6055
|
-
kind: "replace-owned-keys",
|
|
6056
|
-
ownedKeys: ["plugins"]
|
|
6057
|
-
},
|
|
6058
|
-
subagents: {
|
|
6059
|
-
kind: "replace-owned-keys",
|
|
6060
|
-
ownedKeys: ["plugins"]
|
|
6061
|
-
},
|
|
6062
|
-
mcp: {
|
|
6063
|
-
kind: "replace-owned-keys",
|
|
6064
|
-
ownedKeys: ["mcp_servers"]
|
|
6065
|
-
},
|
|
6066
|
-
hooks: {
|
|
6067
|
-
kind: "replace-owned-keys",
|
|
6068
|
-
ownedKeys: ["hooks"]
|
|
6069
|
-
},
|
|
6070
|
-
permissions: {
|
|
6071
|
-
kind: "deep-merge",
|
|
6072
|
-
replaceKeys: ["permissions"]
|
|
6073
|
-
}
|
|
6074
|
-
}
|
|
6075
|
-
},
|
|
6672
|
+
[HERMES_CONFIG_SHARED_FILE_KEY]: HERMES_CONFIG_DECLARATION,
|
|
6673
|
+
[HERMES_WIN32_CONFIG_SHARED_FILE_KEY]: HERMES_CONFIG_DECLARATION,
|
|
6674
|
+
[HERMES_HOME_CONFIG_SHARED_FILE_KEY]: HERMES_CONFIG_DECLARATION,
|
|
6076
6675
|
[TAKT_CONFIG_SHARED_FILE_KEY]: {
|
|
6077
6676
|
format: "yaml",
|
|
6078
6677
|
invalidRootPolicy: "error",
|
|
@@ -6118,6 +6717,10 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
6118
6717
|
".config/zed/settings.json": {
|
|
6119
6718
|
format: "json",
|
|
6120
6719
|
features: {
|
|
6720
|
+
ignore: {
|
|
6721
|
+
kind: "replace-owned-keys",
|
|
6722
|
+
ownedKeys: ["private_files"]
|
|
6723
|
+
},
|
|
6121
6724
|
mcp: {
|
|
6122
6725
|
kind: "replace-owned-keys",
|
|
6123
6726
|
ownedKeys: ["context_servers"]
|
|
@@ -6131,6 +6734,10 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
6131
6734
|
"AppData/Roaming/Zed/settings.json": {
|
|
6132
6735
|
format: "json",
|
|
6133
6736
|
features: {
|
|
6737
|
+
ignore: {
|
|
6738
|
+
kind: "replace-owned-keys",
|
|
6739
|
+
ownedKeys: ["private_files"]
|
|
6740
|
+
},
|
|
6134
6741
|
mcp: {
|
|
6135
6742
|
kind: "replace-owned-keys",
|
|
6136
6743
|
ownedKeys: ["context_servers"]
|
|
@@ -6147,7 +6754,20 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
6147
6754
|
jsoncParseErrors: "error",
|
|
6148
6755
|
features: { permissions: {
|
|
6149
6756
|
kind: "replace-owned-keys",
|
|
6150
|
-
ownedKeys: [
|
|
6757
|
+
ownedKeys: [
|
|
6758
|
+
"chat.tools.terminal.autoApprove",
|
|
6759
|
+
"chat.tools.edits.autoApprove",
|
|
6760
|
+
"chat.tools.urls.autoApprove"
|
|
6761
|
+
]
|
|
6762
|
+
} }
|
|
6763
|
+
},
|
|
6764
|
+
".vscode/mcp.json": {
|
|
6765
|
+
format: "jsonc",
|
|
6766
|
+
invalidRootPolicy: "error",
|
|
6767
|
+
jsoncParseErrors: "error",
|
|
6768
|
+
features: { mcp: {
|
|
6769
|
+
kind: "replace-owned-keys",
|
|
6770
|
+
ownedKeys: ["servers"]
|
|
6151
6771
|
} }
|
|
6152
6772
|
},
|
|
6153
6773
|
".qwen/settings.json": {
|
|
@@ -6374,31 +6994,15 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
6374
6994
|
}
|
|
6375
6995
|
}
|
|
6376
6996
|
},
|
|
6377
|
-
[KIMI_CODE_CONFIG_SHARED_FILE_KEY]:
|
|
6378
|
-
|
|
6379
|
-
invalidRootPolicy: "error",
|
|
6380
|
-
features: {
|
|
6381
|
-
hooks: {
|
|
6382
|
-
kind: "replace-owned-keys",
|
|
6383
|
-
ownedKeys: ["hooks"]
|
|
6384
|
-
},
|
|
6385
|
-
mcp: {
|
|
6386
|
-
kind: "replace-owned-keys",
|
|
6387
|
-
ownedKeys: ["mcp"]
|
|
6388
|
-
},
|
|
6389
|
-
permissions: {
|
|
6390
|
-
kind: "replace-owned-keys",
|
|
6391
|
-
ownedKeys: [
|
|
6392
|
-
"permission",
|
|
6393
|
-
"default_permission_mode",
|
|
6394
|
-
"tools"
|
|
6395
|
-
]
|
|
6396
|
-
}
|
|
6397
|
-
}
|
|
6398
|
-
},
|
|
6997
|
+
[KIMI_CODE_CONFIG_SHARED_FILE_KEY]: KIMI_CODE_CONFIG_DECLARATION,
|
|
6998
|
+
[KIMI_CODE_HOME_CONFIG_SHARED_FILE_KEY]: KIMI_CODE_CONFIG_DECLARATION,
|
|
6399
6999
|
[REASONIX_PROJECT_CONFIG_SHARED_FILE_KEY]: {
|
|
6400
7000
|
format: "toml",
|
|
6401
7001
|
features: {
|
|
7002
|
+
ignore: {
|
|
7003
|
+
kind: "custom",
|
|
7004
|
+
policyFunction: "applyIgnoreReadDenies"
|
|
7005
|
+
},
|
|
6402
7006
|
mcp: {
|
|
6403
7007
|
kind: "replace-owned-keys",
|
|
6404
7008
|
ownedKeys: ["plugins"]
|
|
@@ -6416,6 +7020,10 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
6416
7020
|
[REASONIX_GLOBAL_CONFIG_SHARED_FILE_KEY]: {
|
|
6417
7021
|
format: "toml",
|
|
6418
7022
|
features: {
|
|
7023
|
+
ignore: {
|
|
7024
|
+
kind: "custom",
|
|
7025
|
+
policyFunction: "applyIgnoreReadDenies"
|
|
7026
|
+
},
|
|
6419
7027
|
mcp: {
|
|
6420
7028
|
kind: "replace-owned-keys",
|
|
6421
7029
|
ownedKeys: ["plugins"]
|
|
@@ -6513,7 +7121,12 @@ const applyIgnoreReadDenies = (params) => {
|
|
|
6513
7121
|
const applyPermissions = (params) => {
|
|
6514
7122
|
const { settings, managedToolNames, toolNameOf, allow, ask, deny, logger } = params;
|
|
6515
7123
|
const current = parsePermissionsBlock(settings);
|
|
6516
|
-
const
|
|
7124
|
+
const emitted = /* @__PURE__ */ new Set([
|
|
7125
|
+
...allow,
|
|
7126
|
+
...ask,
|
|
7127
|
+
...deny
|
|
7128
|
+
]);
|
|
7129
|
+
const keepUnmanaged = (entries) => entries.filter((entry) => !managedToolNames.has(toolNameOf(entry)) && !emitted.has(entry));
|
|
6517
7130
|
if (logger && managedToolNames.has(READ_TOOL_NAME)) {
|
|
6518
7131
|
const overwrittenReadDenies = current.deny.filter((entry) => toolNameOf(entry) === READ_TOOL_NAME);
|
|
6519
7132
|
if (overwrittenReadDenies.length > 0) logger.warn(`Permissions feature manages '${READ_TOOL_NAME}' tool and will overwrite ${overwrittenReadDenies.length} existing ${READ_TOOL_NAME} deny entries. Permissions take precedence.`);
|
|
@@ -6663,12 +7276,9 @@ function toRulesyncCheckFromGate({ gate, index, scope, editOnly }) {
|
|
|
6663
7276
|
body: ""
|
|
6664
7277
|
});
|
|
6665
7278
|
}
|
|
6666
|
-
function slugify(value) {
|
|
6667
|
-
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48).replace(/-+$/, "");
|
|
6668
|
-
}
|
|
6669
7279
|
function slugForGate({ gate, index, scope }) {
|
|
6670
|
-
const slug =
|
|
6671
|
-
return `${scope ? `${
|
|
7280
|
+
const slug = slugifyCheckName(typeof gate === "string" ? gate : typeof gate.name === "string" ? gate.name : typeof gate.command === "string" ? gate.command : "");
|
|
7281
|
+
return `${scope ? `${slugifyCheckName(scope.name)}-` : ""}${slug.length > 0 ? slug : "quality-gate"}-${index + 1}`;
|
|
6672
7282
|
}
|
|
6673
7283
|
/**
|
|
6674
7284
|
* Checks adapter for Takt (`.takt/config.yaml` project / `~/.takt/config.yaml`
|
|
@@ -6844,6 +7454,13 @@ const toolCheckFactories = /* @__PURE__ */ new Map([
|
|
|
6844
7454
|
filePattern: "*.md"
|
|
6845
7455
|
}
|
|
6846
7456
|
}],
|
|
7457
|
+
["cursor", {
|
|
7458
|
+
class: CursorCheck,
|
|
7459
|
+
meta: {
|
|
7460
|
+
supportsGlobal: false,
|
|
7461
|
+
filePattern: CURSOR_BUGBOT_FILE_NAME
|
|
7462
|
+
}
|
|
7463
|
+
}],
|
|
6847
7464
|
["hermesagent", {
|
|
6848
7465
|
class: HermesagentCheck,
|
|
6849
7466
|
meta: {
|
|
@@ -6851,6 +7468,13 @@ const toolCheckFactories = /* @__PURE__ */ new Map([
|
|
|
6851
7468
|
filePattern: "*.json"
|
|
6852
7469
|
}
|
|
6853
7470
|
}],
|
|
7471
|
+
["rovodev", {
|
|
7472
|
+
class: RovodevCheck,
|
|
7473
|
+
meta: {
|
|
7474
|
+
supportsGlobal: false,
|
|
7475
|
+
filePattern: ROVODEV_REVIEW_AGENT_FILE_NAME
|
|
7476
|
+
}
|
|
7477
|
+
}],
|
|
6854
7478
|
["takt", {
|
|
6855
7479
|
class: TaktCheck,
|
|
6856
7480
|
meta: {
|
|
@@ -7006,19 +7630,65 @@ var ChecksProcessor = class extends FeatureProcessor {
|
|
|
7006
7630
|
}
|
|
7007
7631
|
};
|
|
7008
7632
|
//#endregion
|
|
7633
|
+
//#region src/utils/tool-home.ts
|
|
7634
|
+
/**
|
|
7635
|
+
* Where the rulesync-side source files of a tool with a home override belong.
|
|
7636
|
+
*
|
|
7637
|
+
* A home override redirects the tool's OWN output, but the `.rulesync/` sources
|
|
7638
|
+
* imported back out of it are not part of the tool's profile — they stay under
|
|
7639
|
+
* the rulesync home. When no override is set, the native output root already is
|
|
7640
|
+
* that place.
|
|
7641
|
+
*/
|
|
7642
|
+
function getToolRulesyncOutputRoot({ nativeOutputRoot, global, toolHome }) {
|
|
7643
|
+
return global && toolHome() ? getHomeDirectory() : nativeOutputRoot;
|
|
7644
|
+
}
|
|
7645
|
+
//#endregion
|
|
7009
7646
|
//#region src/utils/hermesagent.ts
|
|
7010
7647
|
function getHermesagentHome() {
|
|
7011
7648
|
const configuredHome = process.env.HERMES_HOME?.trim();
|
|
7012
7649
|
return configuredHome ? resolve(configuredHome) : void 0;
|
|
7013
7650
|
}
|
|
7651
|
+
/**
|
|
7652
|
+
* The home-relative Hermes profile directory used when `HERMES_HOME` is unset.
|
|
7653
|
+
*
|
|
7654
|
+
* Upstream `_get_platform_default_hermes_home()` returns `%LOCALAPPDATA%\hermes`
|
|
7655
|
+
* on win32 and `~/.hermes` everywhere else, so the global output directory is
|
|
7656
|
+
* platform-dependent — a global generate on Windows that wrote `~/.hermes`
|
|
7657
|
+
* would land where Hermes never reads.
|
|
7658
|
+
*
|
|
7659
|
+
* @see https://github.com/NousResearch/hermes-agent `hermes_constants.py`
|
|
7660
|
+
*/
|
|
7661
|
+
function getHermesagentGlobalDir() {
|
|
7662
|
+
return process.platform === "win32" ? HERMESAGENT_GLOBAL_WIN32_DIR : HERMESAGENT_GLOBAL_DIR;
|
|
7663
|
+
}
|
|
7014
7664
|
function resolveHermesagentOutputRoot({ outputRoot, global }) {
|
|
7015
7665
|
return global ? getHermesagentHome() ?? outputRoot : outputRoot;
|
|
7016
7666
|
}
|
|
7667
|
+
/**
|
|
7668
|
+
* Map a canonical `.hermes/...` path constant onto the directory rulesync
|
|
7669
|
+
* actually writes in the requested scope.
|
|
7670
|
+
*
|
|
7671
|
+
* Project scope keeps the constant as-is (the project tree is `.hermes/`
|
|
7672
|
+
* everywhere). Global scope strips the `.hermes` prefix and re-anchors it:
|
|
7673
|
+
* `HERMES_HOME` *is* the profile root, so nothing is prepended; otherwise the
|
|
7674
|
+
* platform default directory takes its place.
|
|
7675
|
+
*/
|
|
7017
7676
|
function getHermesagentRelativeDirPath({ global, relativeDirPath }) {
|
|
7018
|
-
if (!global
|
|
7677
|
+
if (!global) return relativeDirPath;
|
|
7019
7678
|
const relativePath = relative(HERMESAGENT_GLOBAL_DIR, relativeDirPath);
|
|
7020
|
-
|
|
7021
|
-
|
|
7679
|
+
try {
|
|
7680
|
+
checkPathTraversal({
|
|
7681
|
+
relativePath: relativeDirPath,
|
|
7682
|
+
intendedRootDir: "."
|
|
7683
|
+
});
|
|
7684
|
+
checkPathTraversal({
|
|
7685
|
+
relativePath,
|
|
7686
|
+
intendedRootDir: HERMESAGENT_GLOBAL_DIR
|
|
7687
|
+
});
|
|
7688
|
+
} catch {
|
|
7689
|
+
throw new Error(`Hermes Agent global path must be within ${HERMESAGENT_GLOBAL_DIR}: ${relativeDirPath}`);
|
|
7690
|
+
}
|
|
7691
|
+
return getHermesagentHome() ? relativePath || "." : join(getHermesagentGlobalDir(), relativePath);
|
|
7022
7692
|
}
|
|
7023
7693
|
function getHermesagentRelativeFilePath({ global, relativeFilePath }) {
|
|
7024
7694
|
return join(getHermesagentRelativeDirPath({
|
|
@@ -7026,8 +7696,53 @@ function getHermesagentRelativeFilePath({ global, relativeFilePath }) {
|
|
|
7026
7696
|
relativeDirPath: dirname(relativeFilePath)
|
|
7027
7697
|
}), basename(relativeFilePath));
|
|
7028
7698
|
}
|
|
7699
|
+
/**
|
|
7700
|
+
* Every spelling `config.yaml` can take in global scope, so that the
|
|
7701
|
+
* shared-write derivation and the gateway ownership table it is checked against
|
|
7702
|
+
* see the same set of keys on every platform and with or without `HERMES_HOME`.
|
|
7703
|
+
*
|
|
7704
|
+
* `getHermesagentRelativeDirPath` resolves exactly one of these per process,
|
|
7705
|
+
* which would otherwise make the derived shared-file key depend on the ambient
|
|
7706
|
+
* environment — the drift guards would then go blind in precisely the
|
|
7707
|
+
* configuration this feature exists for.
|
|
7708
|
+
*/
|
|
7709
|
+
function getHermesagentSharedConfigWritePaths() {
|
|
7710
|
+
return [
|
|
7711
|
+
{
|
|
7712
|
+
relativeDirPath: HERMESAGENT_GLOBAL_DIR,
|
|
7713
|
+
relativeFilePath: HERMESAGENT_CONFIG_FILE_NAME
|
|
7714
|
+
},
|
|
7715
|
+
{
|
|
7716
|
+
relativeDirPath: HERMESAGENT_GLOBAL_WIN32_DIR,
|
|
7717
|
+
relativeFilePath: HERMESAGENT_CONFIG_FILE_NAME
|
|
7718
|
+
},
|
|
7719
|
+
{
|
|
7720
|
+
relativeDirPath: ".",
|
|
7721
|
+
relativeFilePath: HERMESAGENT_CONFIG_FILE_NAME
|
|
7722
|
+
}
|
|
7723
|
+
];
|
|
7724
|
+
}
|
|
7725
|
+
/**
|
|
7726
|
+
* The `SHARED_CONFIG_OWNERSHIP` key of the `config.yaml` this scope actually
|
|
7727
|
+
* writes. All three spellings carry the same declaration, but passing the key of
|
|
7728
|
+
* the file being written keeps the write path and the drift guards reading the
|
|
7729
|
+
* same entry.
|
|
7730
|
+
*/
|
|
7731
|
+
function getHermesagentConfigSharedFileKey({ global }) {
|
|
7732
|
+
return sharedConfigFileKey({
|
|
7733
|
+
relativeDirPath: getHermesagentRelativeDirPath({
|
|
7734
|
+
global,
|
|
7735
|
+
relativeDirPath: HERMESAGENT_GLOBAL_DIR
|
|
7736
|
+
}),
|
|
7737
|
+
relativeFilePath: HERMESAGENT_CONFIG_FILE_NAME
|
|
7738
|
+
});
|
|
7739
|
+
}
|
|
7029
7740
|
function getHermesagentRulesyncOutputRoot({ nativeOutputRoot, global }) {
|
|
7030
|
-
return
|
|
7741
|
+
return getToolRulesyncOutputRoot({
|
|
7742
|
+
nativeOutputRoot,
|
|
7743
|
+
global,
|
|
7744
|
+
toolHome: getHermesagentHome
|
|
7745
|
+
});
|
|
7031
7746
|
}
|
|
7032
7747
|
//#endregion
|
|
7033
7748
|
//#region src/constants/agentsmd-paths.ts
|
|
@@ -7259,6 +7974,7 @@ var AgentsmdCommand = class AgentsmdCommand extends SimulatedCommand {
|
|
|
7259
7974
|
const ANTIGRAVITY_DIR = ".agents";
|
|
7260
7975
|
const ANTIGRAVITY_SKILLS_DIR_PATH = join(ANTIGRAVITY_DIR, "skills");
|
|
7261
7976
|
const ANTIGRAVITY_WORKFLOWS_DIR_PATH = join(ANTIGRAVITY_DIR, "workflows");
|
|
7977
|
+
const ANTIGRAVITY_AGENTS_DIR_PATH = join(ANTIGRAVITY_DIR, "agents");
|
|
7262
7978
|
const ANTIGRAVITY_MCP_FILE_NAME = "mcp_config.json";
|
|
7263
7979
|
const ANTIGRAVITY_HOOKS_FILE_NAME = "hooks.json";
|
|
7264
7980
|
const ANTIGRAVITY_IGNORE_FILE_NAME = ".geminiignore";
|
|
@@ -7269,6 +7985,7 @@ const ANTIGRAVITY_CLI_PERMISSIONS_FILE_NAME = "settings.json";
|
|
|
7269
7985
|
const ANTIGRAVITY_CLI_GLOBAL_WORKFLOWS_DIR_PATH = join(ANTIGRAVITY_GEMINI_DIR, ANTIGRAVITY_CLI_PERMISSIONS_SUBDIR, "global_workflows");
|
|
7270
7986
|
const ANTIGRAVITY_GLOBAL_CONFIG_SUBDIR = "config";
|
|
7271
7987
|
const ANTIGRAVITY_GLOBAL_CONFIG_DIR_PATH = join(ANTIGRAVITY_GEMINI_DIR, ANTIGRAVITY_GLOBAL_CONFIG_SUBDIR);
|
|
7988
|
+
const ANTIGRAVITY_GLOBAL_AGENTS_DIR_PATH = join(ANTIGRAVITY_GLOBAL_CONFIG_DIR_PATH, "agents");
|
|
7272
7989
|
//#endregion
|
|
7273
7990
|
//#region src/constants/antigravity-cli-paths.ts
|
|
7274
7991
|
const ANTIGRAVITY_AGENTS_DIR = ANTIGRAVITY_DIR;
|
|
@@ -7827,6 +8544,7 @@ const CLAUDECODE_PLUGIN_HOOKS_DIR = "hooks";
|
|
|
7827
8544
|
const CLAUDECODE_PLUGIN_HOOKS_FILE_NAME = "hooks.json";
|
|
7828
8545
|
const ANTIGRAVITY_PLUGIN_RULES_DIR = "rules";
|
|
7829
8546
|
const ANTIGRAVITY_PLUGIN_SKILLS_DIR = "skills";
|
|
8547
|
+
const ANTIGRAVITY_PLUGIN_AGENTS_DIR = "agents";
|
|
7830
8548
|
const ANTIGRAVITY_PLUGIN_MCP_FILE_NAME = "mcp_config.json";
|
|
7831
8549
|
const ANTIGRAVITY_PLUGIN_HOOKS_FILE_NAME = "hooks.json";
|
|
7832
8550
|
//#endregion
|
|
@@ -8051,6 +8769,7 @@ const COPILOT_SKILLS_DIR_PATH = join(GITHUB_DIR, "skills");
|
|
|
8051
8769
|
const COPILOT_AGENTS_DIR_PATH = join(GITHUB_DIR, "agents");
|
|
8052
8770
|
const COPILOT_HOOKS_DIR_PATH = join(GITHUB_DIR, "hooks");
|
|
8053
8771
|
const COPILOT_HOOKS_FILE_NAME = "copilot-hooks.json";
|
|
8772
|
+
const COPILOT_GLOBAL_HOOKS_FILE_NAME = "copilot-ide-hooks.json";
|
|
8054
8773
|
const COPILOT_MCP_DIR = ".vscode";
|
|
8055
8774
|
const COPILOT_MCP_FILE_NAME = "mcp.json";
|
|
8056
8775
|
const COPILOT_VSCODE_SETTINGS_FILE_NAME = "settings.json";
|
|
@@ -8058,6 +8777,7 @@ const COPILOTCLI_MCP_FILE_NAME = "mcp-config.json";
|
|
|
8058
8777
|
const COPILOTCLI_PROJECT_MCP_FILE_NAME = "mcp.json";
|
|
8059
8778
|
const COPILOTCLI_AGENTS_DIR_PATH = join(COPILOT_DIR, "agents");
|
|
8060
8779
|
const COPILOTCLI_HOOKS_DIR_PATH = join(COPILOT_DIR, "hooks");
|
|
8780
|
+
const COPILOT_GLOBAL_HOOKS_DIR_PATH = COPILOTCLI_HOOKS_DIR_PATH;
|
|
8061
8781
|
const COPILOTCLI_HOOKS_FILE_NAME = "copilotcli-hooks.json";
|
|
8062
8782
|
const COPILOT_SKILLS_GLOBAL_DIR_PATH = join(COPILOT_DIR, "skills");
|
|
8063
8783
|
//#endregion
|
|
@@ -8181,17 +8901,6 @@ var CopilotCommand = class CopilotCommand extends ToolCommand {
|
|
|
8181
8901
|
}
|
|
8182
8902
|
};
|
|
8183
8903
|
//#endregion
|
|
8184
|
-
//#region src/constants/cursor-paths.ts
|
|
8185
|
-
const CURSOR_DIR = ".cursor";
|
|
8186
|
-
const CURSOR_COMMANDS_DIR_PATH = join(CURSOR_DIR, "commands");
|
|
8187
|
-
const CURSOR_SKILLS_DIR_PATH = join(CURSOR_DIR, "skills");
|
|
8188
|
-
const CURSOR_AGENTS_DIR_PATH = join(CURSOR_DIR, "agents");
|
|
8189
|
-
const CURSOR_MCP_FILE_NAME = "mcp.json";
|
|
8190
|
-
const CURSOR_HOOKS_FILE_NAME = "hooks.json";
|
|
8191
|
-
const CURSOR_IGNORE_FILE_NAME = ".cursorignore";
|
|
8192
|
-
const CURSOR_PERMISSIONS_FILE_NAME = "cli.json";
|
|
8193
|
-
const CURSOR_PERMISSIONS_GLOBAL_FILE_NAME = "cli-config.json";
|
|
8194
|
-
//#endregion
|
|
8195
8904
|
//#region src/features/commands/cursor-command.ts
|
|
8196
8905
|
const CursorCommandFrontmatterSchema = z.looseObject({
|
|
8197
8906
|
description: z.optional(z.string()),
|
|
@@ -8737,6 +9446,223 @@ var GooseCommand = class GooseCommand extends ToolCommand {
|
|
|
8737
9446
|
}
|
|
8738
9447
|
};
|
|
8739
9448
|
//#endregion
|
|
9449
|
+
//#region src/constants/grokcli-paths.ts
|
|
9450
|
+
/**
|
|
9451
|
+
* Grok Build CLI (xAI) configuration-layout conventions.
|
|
9452
|
+
*
|
|
9453
|
+
* Single source of truth for where Grok Build expects its files. Grok Build
|
|
9454
|
+
* stores MCP servers (and other settings) in a `config.toml` under `.grok/`,
|
|
9455
|
+
* with project/global scopes resolved by the directory the CLI runs in
|
|
9456
|
+
* (`./.grok/config.toml` vs `~/.grok/config.toml`).
|
|
9457
|
+
*
|
|
9458
|
+
* Verified against `grok` 0.2.54 (`grok mcp add --help`, `grok mcp add`):
|
|
9459
|
+
* `-s project` writes `./.grok/config.toml`, `-s user` writes
|
|
9460
|
+
* `~/.grok/config.toml`, both as a TOML `[mcp_servers.<name>]` table.
|
|
9461
|
+
* @see https://docs.x.ai/build/overview
|
|
9462
|
+
*/
|
|
9463
|
+
/** Root directory for Grok Build configuration, relative to the scope root. */
|
|
9464
|
+
const GROKCLI_DIR = ".grok";
|
|
9465
|
+
/** MCP servers and other settings live in `config.toml` under `.grok/`. */
|
|
9466
|
+
const GROKCLI_MCP_FILE_NAME = "config.toml";
|
|
9467
|
+
/**
|
|
9468
|
+
* Shared Grok CLI config file (`config.toml`). MCP servers, the `[ui]`
|
|
9469
|
+
* permission mode, and other settings all live here; permissions reuse the same
|
|
9470
|
+
* file name as MCP since Grok consolidates everything into one config.
|
|
9471
|
+
*/
|
|
9472
|
+
const GROKCLI_CONFIG_FILE_NAME = "config.toml";
|
|
9473
|
+
/** Skills directory under `.grok/` (project: `./.grok/skills`, global: `~/.grok/skills`). */
|
|
9474
|
+
const GROKCLI_SKILLS_DIR_PATH = join(GROKCLI_DIR, "skills");
|
|
9475
|
+
/**
|
|
9476
|
+
* Hooks directory under `.grok/`. Grok Build discovers hook config files from
|
|
9477
|
+
* `.grok/hooks/*.json` (project) and `~/.grok/hooks/*.json` (global), each a
|
|
9478
|
+
* standalone JSON file using the Claude-Code-compatible nested `{ hooks: { … } }`
|
|
9479
|
+
* shape. rulesync writes all its hooks into a single `rulesync.json`.
|
|
9480
|
+
* @see https://docs.x.ai/build/features/hooks
|
|
9481
|
+
*/
|
|
9482
|
+
const GROKCLI_HOOKS_DIR_PATH = join(GROKCLI_DIR, "hooks");
|
|
9483
|
+
/** rulesync-managed Grok hooks file under `.grok/hooks/`. */
|
|
9484
|
+
const GROKCLI_HOOKS_FILE_NAME = "rulesync.json";
|
|
9485
|
+
/**
|
|
9486
|
+
* Subagents (agent profiles) directory under `.grok/`. Grok Build discovers
|
|
9487
|
+
* agent definitions from `.grok/agents/*.md` (project) and `~/.grok/agents/*.md`
|
|
9488
|
+
* (global), each a Markdown file with YAML frontmatter (verified via
|
|
9489
|
+
* `grok inspect`; format matches the bundled `~/.grok/bundled/agents/*.md`).
|
|
9490
|
+
*/
|
|
9491
|
+
const GROKCLI_AGENTS_DIR_PATH = join(GROKCLI_DIR, "agents");
|
|
9492
|
+
/**
|
|
9493
|
+
* Instruction file. Grok reads the AGENTS.md instruction-file family natively,
|
|
9494
|
+
* including the user-level `~/.grok/AGENTS.md` for global rules (verified via
|
|
9495
|
+
* `grok inspect`, consistent with the `.grok/` global discovery used by the
|
|
9496
|
+
* MCP/skills/subagents adapters).
|
|
9497
|
+
*/
|
|
9498
|
+
const GROKCLI_RULE_FILE_NAME = "AGENTS.md";
|
|
9499
|
+
/**
|
|
9500
|
+
* Custom slash commands directory. Grok's `find_command_paths` scans
|
|
9501
|
+
* `commands/*.md` under every discovered config dir — `.grok/commands/`
|
|
9502
|
+
* (project, walked from cwd up to the git root) and `~/.grok/commands/`
|
|
9503
|
+
* (global). The scan is **flat and non-recursive**, so subdirectory
|
|
9504
|
+
* namespacing (`git/commit.md` → `/git:commit`) is not supported the way it is
|
|
9505
|
+
* for Claude Code.
|
|
9506
|
+
*
|
|
9507
|
+
* Skills are collected before commands and win name collisions, so a
|
|
9508
|
+
* `.grok/skills/<name>/` shadows `.grok/commands/<name>.md`.
|
|
9509
|
+
* @see https://docs.x.ai/build/features/skills-plugins-marketplaces
|
|
9510
|
+
*/
|
|
9511
|
+
const GROKCLI_COMMANDS_DIR_PATH = join(GROKCLI_DIR, "commands");
|
|
9512
|
+
/**
|
|
9513
|
+
* Non-root rules directory. Grok scans `*.md` here — flat, sorted by name —
|
|
9514
|
+
* alongside the AGENTS.md family: `.grok/rules/` in each project directory it
|
|
9515
|
+
* walks, and `~/.grok/rules/` in the home scope.
|
|
9516
|
+
* @see https://docs.x.ai/build/overview
|
|
9517
|
+
*/
|
|
9518
|
+
const GROKCLI_RULES_DIR_PATH = join(GROKCLI_DIR, "rules");
|
|
9519
|
+
//#endregion
|
|
9520
|
+
//#region src/features/commands/grokcli-command.ts
|
|
9521
|
+
/**
|
|
9522
|
+
* Grok CLI custom slash commands are Markdown files under `.grok/commands/`
|
|
9523
|
+
* (project) / `~/.grok/commands/` (global), discovered by the same
|
|
9524
|
+
* Claude-Code-compatible frontmatter parser Grok uses for skills.
|
|
9525
|
+
*
|
|
9526
|
+
* Two upstream constraints shape this adapter:
|
|
9527
|
+
*
|
|
9528
|
+
* - The scan is **flat and non-recursive**, so nested namespacing is not
|
|
9529
|
+
* modelled (`supportsSubdirectory: false` flattens nested rulesync commands
|
|
9530
|
+
* onto their basename).
|
|
9531
|
+
* - Skills are collected before commands and win name collisions, so a
|
|
9532
|
+
* `.grok/skills/<name>/` shadows `.grok/commands/<name>.md`.
|
|
9533
|
+
*
|
|
9534
|
+
* `description` and `argument-hint` describe the command, while
|
|
9535
|
+
* `user-invocable` (default true) and `disable-model-invocation` (default
|
|
9536
|
+
* false) control who may invoke it — the same pair `GrokcliSkill` emits.
|
|
9537
|
+
* @see https://docs.x.ai/build/features/skills-plugins-marketplaces
|
|
9538
|
+
*/
|
|
9539
|
+
const GrokcliCommandFrontmatterSchema = z.looseObject({
|
|
9540
|
+
description: z.optional(z.string()),
|
|
9541
|
+
"argument-hint": z.optional(z.string()),
|
|
9542
|
+
"user-invocable": z.optional(z.boolean()),
|
|
9543
|
+
"disable-model-invocation": z.optional(z.boolean())
|
|
9544
|
+
});
|
|
9545
|
+
var GrokcliCommand = class GrokcliCommand extends ToolCommand {
|
|
9546
|
+
frontmatter;
|
|
9547
|
+
body;
|
|
9548
|
+
constructor({ frontmatter, body, ...rest }) {
|
|
9549
|
+
if (rest.validate) {
|
|
9550
|
+
const result = GrokcliCommandFrontmatterSchema.safeParse(frontmatter);
|
|
9551
|
+
if (!result.success) throw new Error(`Invalid frontmatter in ${join(rest.relativeDirPath, rest.relativeFilePath)}: ${formatError(result.error)}`);
|
|
9552
|
+
}
|
|
9553
|
+
super({
|
|
9554
|
+
...rest,
|
|
9555
|
+
fileContent: stringifyFrontmatter(body, frontmatter)
|
|
9556
|
+
});
|
|
9557
|
+
this.frontmatter = frontmatter;
|
|
9558
|
+
this.body = body;
|
|
9559
|
+
}
|
|
9560
|
+
static getSettablePaths(_options = {}) {
|
|
9561
|
+
return { relativeDirPath: GROKCLI_COMMANDS_DIR_PATH };
|
|
9562
|
+
}
|
|
9563
|
+
getBody() {
|
|
9564
|
+
return this.body;
|
|
9565
|
+
}
|
|
9566
|
+
getFrontmatter() {
|
|
9567
|
+
return this.frontmatter;
|
|
9568
|
+
}
|
|
9569
|
+
toRulesyncCommand() {
|
|
9570
|
+
const { description, ...restFields } = this.frontmatter;
|
|
9571
|
+
const rulesyncFrontmatter = {
|
|
9572
|
+
targets: ["*"],
|
|
9573
|
+
description,
|
|
9574
|
+
...Object.keys(restFields).length > 0 && { grokcli: restFields }
|
|
9575
|
+
};
|
|
9576
|
+
return new RulesyncCommand({
|
|
9577
|
+
outputRoot: ".",
|
|
9578
|
+
frontmatter: rulesyncFrontmatter,
|
|
9579
|
+
body: this.body,
|
|
9580
|
+
relativeDirPath: RulesyncCommand.getSettablePaths().relativeDirPath,
|
|
9581
|
+
relativeFilePath: this.relativeFilePath,
|
|
9582
|
+
fileContent: stringifyFrontmatter(this.body, rulesyncFrontmatter),
|
|
9583
|
+
validate: true
|
|
9584
|
+
});
|
|
9585
|
+
}
|
|
9586
|
+
static fromRulesyncCommand({ outputRoot = process.cwd(), rulesyncCommand, validate = true, global = false }) {
|
|
9587
|
+
const rulesyncFrontmatter = rulesyncCommand.getFrontmatter();
|
|
9588
|
+
const grokcliFields = rulesyncFrontmatter.grokcli ?? {};
|
|
9589
|
+
const grokcliFrontmatter = {
|
|
9590
|
+
description: rulesyncFrontmatter.description,
|
|
9591
|
+
...grokcliFields
|
|
9592
|
+
};
|
|
9593
|
+
const paths = this.getSettablePaths({ global });
|
|
9594
|
+
return new GrokcliCommand({
|
|
9595
|
+
outputRoot,
|
|
9596
|
+
frontmatter: grokcliFrontmatter,
|
|
9597
|
+
body: rulesyncCommand.getBody(),
|
|
9598
|
+
relativeDirPath: paths.relativeDirPath,
|
|
9599
|
+
relativeFilePath: rulesyncCommand.getRelativeFilePath(),
|
|
9600
|
+
validate
|
|
9601
|
+
});
|
|
9602
|
+
}
|
|
9603
|
+
validate() {
|
|
9604
|
+
if (!this.frontmatter) return {
|
|
9605
|
+
success: true,
|
|
9606
|
+
error: null
|
|
9607
|
+
};
|
|
9608
|
+
const result = GrokcliCommandFrontmatterSchema.safeParse(this.frontmatter);
|
|
9609
|
+
if (result.success) return {
|
|
9610
|
+
success: true,
|
|
9611
|
+
error: null
|
|
9612
|
+
};
|
|
9613
|
+
return {
|
|
9614
|
+
success: false,
|
|
9615
|
+
error: /* @__PURE__ */ new Error(`Invalid frontmatter in ${join(this.relativeDirPath, this.relativeFilePath)}: ${formatError(result.error)}`)
|
|
9616
|
+
};
|
|
9617
|
+
}
|
|
9618
|
+
static isTargetedByRulesyncCommand(rulesyncCommand) {
|
|
9619
|
+
return this.isTargetedByRulesyncCommandDefault({
|
|
9620
|
+
rulesyncCommand,
|
|
9621
|
+
toolTarget: "grokcli"
|
|
9622
|
+
});
|
|
9623
|
+
}
|
|
9624
|
+
/**
|
|
9625
|
+
* Warn when a rulesync skill would shadow a rulesync command.
|
|
9626
|
+
*
|
|
9627
|
+
* Grok collects skills before commands and lets skills win name collisions,
|
|
9628
|
+
* so `.grok/skills/<name>/` makes `.grok/commands/<name>.md` unreachable.
|
|
9629
|
+
* Both files are still written correctly — nothing is overwritten and no
|
|
9630
|
+
* output is lost — so this warns rather than failing the run the way the
|
|
9631
|
+
* Hermes check does, where the two surfaces really do write the same path.
|
|
9632
|
+
*/
|
|
9633
|
+
static async validateRulesyncCommands({ inputRoot, rulesyncCommands, logger }) {
|
|
9634
|
+
const commandNames = new Set(rulesyncCommands.filter((command) => this.isTargetedByRulesyncCommand(command)).map((command) => basename(command.getRelativeFilePath(), ".md")));
|
|
9635
|
+
if (commandNames.size === 0) return;
|
|
9636
|
+
const shadowed = (await findFilesByGlobs(join(join(inputRoot, RULESYNC_SKILLS_RELATIVE_DIR_PATH), "**", "SKILL.md"))).map((filePath) => basename(dirname(filePath))).filter((skillName) => commandNames.has(skillName));
|
|
9637
|
+
if (shadowed.length > 0) logger.warn(`Grok CLI resolves skills before commands, so these skills shadow the same-named commands, which will never be reachable: ${[...new Set(shadowed)].toSorted().join(", ")}. Rename either side to make both invocable.`);
|
|
9638
|
+
}
|
|
9639
|
+
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
|
|
9640
|
+
const paths = this.getSettablePaths({ global });
|
|
9641
|
+
const filePath = join(outputRoot, paths.relativeDirPath, relativeFilePath);
|
|
9642
|
+
const { frontmatter, body: content } = parseFrontmatter(await readFileContent(filePath), filePath);
|
|
9643
|
+
const result = GrokcliCommandFrontmatterSchema.safeParse(frontmatter);
|
|
9644
|
+
if (!result.success) throw new Error(`Invalid frontmatter in ${filePath}: ${formatError(result.error)}`);
|
|
9645
|
+
return new GrokcliCommand({
|
|
9646
|
+
outputRoot,
|
|
9647
|
+
relativeDirPath: paths.relativeDirPath,
|
|
9648
|
+
relativeFilePath,
|
|
9649
|
+
frontmatter: result.data,
|
|
9650
|
+
body: content.trim(),
|
|
9651
|
+
validate
|
|
9652
|
+
});
|
|
9653
|
+
}
|
|
9654
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
9655
|
+
return new GrokcliCommand({
|
|
9656
|
+
outputRoot,
|
|
9657
|
+
relativeDirPath,
|
|
9658
|
+
relativeFilePath,
|
|
9659
|
+
frontmatter: { description: "" },
|
|
9660
|
+
body: "",
|
|
9661
|
+
validate: false
|
|
9662
|
+
});
|
|
9663
|
+
}
|
|
9664
|
+
};
|
|
9665
|
+
//#endregion
|
|
8740
9666
|
//#region src/features/skills/tool-skill.ts
|
|
8741
9667
|
/** Ordered skill directory roots: primary first. */
|
|
8742
9668
|
function toolSkillSearchRoots(paths) {
|
|
@@ -9371,7 +10297,7 @@ def register(ctx):
|
|
|
9371
10297
|
_register_command(ctx, command)
|
|
9372
10298
|
`;
|
|
9373
10299
|
}
|
|
9374
|
-
function getEnabledPluginConfigContent$1(currentContent) {
|
|
10300
|
+
function getEnabledPluginConfigContent$1({ currentContent, global }) {
|
|
9375
10301
|
const config = parseSharedConfig({
|
|
9376
10302
|
format: "yaml",
|
|
9377
10303
|
fileContent: currentContent
|
|
@@ -9379,7 +10305,7 @@ function getEnabledPluginConfigContent$1(currentContent) {
|
|
|
9379
10305
|
const plugins = config.plugins && typeof config.plugins === "object" ? config.plugins : {};
|
|
9380
10306
|
const enabled = Array.isArray(plugins.enabled) ? plugins.enabled : [];
|
|
9381
10307
|
return applySharedConfigPatch({
|
|
9382
|
-
fileKey:
|
|
10308
|
+
fileKey: getHermesagentConfigSharedFileKey({ global }),
|
|
9383
10309
|
feature: "commands",
|
|
9384
10310
|
existingContent: currentContent,
|
|
9385
10311
|
patch: { plugins: {
|
|
@@ -9388,7 +10314,7 @@ function getEnabledPluginConfigContent$1(currentContent) {
|
|
|
9388
10314
|
} }
|
|
9389
10315
|
});
|
|
9390
10316
|
}
|
|
9391
|
-
function getDisabledHermesCommandsPluginConfigContent(currentContent) {
|
|
10317
|
+
function getDisabledHermesCommandsPluginConfigContent({ currentContent, global }) {
|
|
9392
10318
|
const config = parseSharedConfig({
|
|
9393
10319
|
format: "yaml",
|
|
9394
10320
|
fileContent: currentContent
|
|
@@ -9396,7 +10322,7 @@ function getDisabledHermesCommandsPluginConfigContent(currentContent) {
|
|
|
9396
10322
|
const plugins = config.plugins && typeof config.plugins === "object" ? config.plugins : {};
|
|
9397
10323
|
const enabled = Array.isArray(plugins.enabled) ? plugins.enabled : [];
|
|
9398
10324
|
return applySharedConfigPatch({
|
|
9399
|
-
fileKey:
|
|
10325
|
+
fileKey: getHermesagentConfigSharedFileKey({ global }),
|
|
9400
10326
|
feature: "commands",
|
|
9401
10327
|
existingContent: currentContent,
|
|
9402
10328
|
patch: { plugins: {
|
|
@@ -9412,35 +10338,36 @@ var HermesagentCommandAuxiliaryFile = class extends ToolFile {
|
|
|
9412
10338
|
error: null
|
|
9413
10339
|
};
|
|
9414
10340
|
}
|
|
9415
|
-
|
|
10341
|
+
/**
|
|
10342
|
+
* Whether this auxiliary file is the one at `relativeFilePath`, comparing
|
|
10343
|
+
* against the scope-resolved location of that canonical `.hermes/...` path.
|
|
10344
|
+
*/
|
|
10345
|
+
matchesPath(relativeFilePath) {
|
|
9416
10346
|
return this.getRelativePathFromCwd() === toPosixPath(getHermesagentRelativeFilePath({
|
|
9417
10347
|
global: this.global,
|
|
9418
|
-
relativeFilePath
|
|
10348
|
+
relativeFilePath
|
|
9419
10349
|
}));
|
|
9420
10350
|
}
|
|
10351
|
+
shouldMergeExistingFileContent() {
|
|
10352
|
+
return this.matchesPath(HERMESAGENT_CONFIG_FILE_PATH);
|
|
10353
|
+
}
|
|
9421
10354
|
setFileContent(newFileContent) {
|
|
9422
|
-
if (this.
|
|
9423
|
-
|
|
9424
|
-
|
|
9425
|
-
|
|
9426
|
-
|
|
10355
|
+
if (this.matchesPath(HERMESAGENT_CONFIG_FILE_PATH)) {
|
|
10356
|
+
super.setFileContent(getEnabledPluginConfigContent$1({
|
|
10357
|
+
currentContent: newFileContent,
|
|
10358
|
+
global: this.global
|
|
10359
|
+
}));
|
|
9427
10360
|
return;
|
|
9428
10361
|
}
|
|
9429
10362
|
super.setFileContent(newFileContent);
|
|
9430
10363
|
}
|
|
9431
10364
|
getFileContent() {
|
|
9432
|
-
if (this.
|
|
9433
|
-
|
|
9434
|
-
|
|
9435
|
-
|
|
9436
|
-
|
|
9437
|
-
|
|
9438
|
-
relativeFilePath: HERMESAGENT_RULESYNC_COMMANDS_PLUGIN_INIT_PATH
|
|
9439
|
-
}))) return getPluginInitContent$2();
|
|
9440
|
-
if (this.getRelativePathFromCwd() === toPosixPath(getHermesagentRelativeFilePath({
|
|
9441
|
-
global: this.global,
|
|
9442
|
-
relativeFilePath: HERMESAGENT_CONFIG_FILE_PATH
|
|
9443
|
-
}))) return getEnabledPluginConfigContent$1(super.getFileContent());
|
|
10365
|
+
if (this.matchesPath(HERMESAGENT_RULESYNC_COMMANDS_PLUGIN_MANIFEST_PATH)) return getPluginManifestContent$2();
|
|
10366
|
+
if (this.matchesPath(HERMESAGENT_RULESYNC_COMMANDS_PLUGIN_INIT_PATH)) return getPluginInitContent$2();
|
|
10367
|
+
if (this.matchesPath(HERMESAGENT_CONFIG_FILE_PATH)) return getEnabledPluginConfigContent$1({
|
|
10368
|
+
currentContent: super.getFileContent(),
|
|
10369
|
+
global: this.global
|
|
10370
|
+
});
|
|
9444
10371
|
return super.getFileContent();
|
|
9445
10372
|
}
|
|
9446
10373
|
};
|
|
@@ -9457,14 +10384,12 @@ var HermesagentCommand = class HermesagentCommand extends ToolCommand {
|
|
|
9457
10384
|
relativeDirPath: HERMESAGENT_RULESYNC_COMMANDS_DIR_PATH
|
|
9458
10385
|
}) };
|
|
9459
10386
|
}
|
|
9460
|
-
|
|
9461
|
-
|
|
9462
|
-
|
|
9463
|
-
|
|
9464
|
-
|
|
9465
|
-
|
|
9466
|
-
relativeFilePath: basename(HERMESAGENT_CONFIG_FILE_PATH)
|
|
9467
|
-
}];
|
|
10387
|
+
/**
|
|
10388
|
+
* `config.yaml` under every spelling the global profile root can take.
|
|
10389
|
+
* @see getHermesagentSharedConfigWritePaths
|
|
10390
|
+
*/
|
|
10391
|
+
static getExtraSharedWritePaths() {
|
|
10392
|
+
return getHermesagentSharedConfigWritePaths();
|
|
9468
10393
|
}
|
|
9469
10394
|
static async validateRulesyncCommands({ inputRoot, rulesyncCommands }) {
|
|
9470
10395
|
const commandSlugs = /* @__PURE__ */ new Set();
|
|
@@ -9489,33 +10414,28 @@ var HermesagentCommand = class HermesagentCommand extends ToolCommand {
|
|
|
9489
10414
|
}
|
|
9490
10415
|
static async getAuxiliaryFiles({ toolCommands, outputRoot, global = false, forDeletion = false }) {
|
|
9491
10416
|
if (toolCommands.length === 0 && !forDeletion) return [];
|
|
10417
|
+
const pluginDirPath = getHermesagentRelativeDirPath({
|
|
10418
|
+
global,
|
|
10419
|
+
relativeDirPath: HERMESAGENT_RULESYNC_COMMANDS_PLUGIN_DIR_PATH
|
|
10420
|
+
});
|
|
9492
10421
|
const pluginFiles = [
|
|
9493
10422
|
new HermesagentCommandAuxiliaryFile({
|
|
9494
10423
|
outputRoot,
|
|
9495
|
-
relativeDirPath:
|
|
9496
|
-
global,
|
|
9497
|
-
relativeDirPath: HERMESAGENT_RULESYNC_COMMANDS_PLUGIN_DIR_PATH
|
|
9498
|
-
}),
|
|
10424
|
+
relativeDirPath: pluginDirPath,
|
|
9499
10425
|
relativeFilePath: basename(HERMESAGENT_RULESYNC_COMMANDS_PLUGIN_MANIFEST_PATH),
|
|
9500
10426
|
fileContent: "",
|
|
9501
10427
|
global
|
|
9502
10428
|
}),
|
|
9503
10429
|
new HermesagentCommandAuxiliaryFile({
|
|
9504
10430
|
outputRoot,
|
|
9505
|
-
relativeDirPath:
|
|
9506
|
-
global,
|
|
9507
|
-
relativeDirPath: HERMESAGENT_RULESYNC_COMMANDS_PLUGIN_DIR_PATH
|
|
9508
|
-
}),
|
|
10431
|
+
relativeDirPath: pluginDirPath,
|
|
9509
10432
|
relativeFilePath: basename(HERMESAGENT_RULESYNC_COMMANDS_PLUGIN_OWNERSHIP_PATH),
|
|
9510
10433
|
fileContent: "Generated and owned by RuleSync.\n",
|
|
9511
10434
|
global
|
|
9512
10435
|
}),
|
|
9513
10436
|
new HermesagentCommandAuxiliaryFile({
|
|
9514
10437
|
outputRoot,
|
|
9515
|
-
relativeDirPath:
|
|
9516
|
-
global,
|
|
9517
|
-
relativeDirPath: HERMESAGENT_RULESYNC_COMMANDS_PLUGIN_DIR_PATH
|
|
9518
|
-
}),
|
|
10438
|
+
relativeDirPath: pluginDirPath,
|
|
9519
10439
|
relativeFilePath: basename(HERMESAGENT_RULESYNC_COMMANDS_PLUGIN_INIT_PATH),
|
|
9520
10440
|
fileContent: "",
|
|
9521
10441
|
global
|
|
@@ -10750,19 +11670,6 @@ var RooCommand = class RooCommand extends ToolCommand {
|
|
|
10750
11670
|
}
|
|
10751
11671
|
};
|
|
10752
11672
|
//#endregion
|
|
10753
|
-
//#region src/constants/rovodev-paths.ts
|
|
10754
|
-
const ROVODEV_DIR = ".rovodev";
|
|
10755
|
-
const ROVODEV_SKILLS_DIR_PATH = join(ROVODEV_DIR, "skills");
|
|
10756
|
-
const ROVODEV_SUBAGENTS_DIR_PATH = join(ROVODEV_DIR, "subagents");
|
|
10757
|
-
const ROVODEV_MODULAR_RULES_DIR_PATH = join(ROVODEV_DIR, ".rulesync", "modular-rules");
|
|
10758
|
-
const ROVODEV_RULE_FILE_NAME = "AGENTS.md";
|
|
10759
|
-
const ROVODEV_LEGACY_RULE_FILE_NAME = "AGENTS.local.md";
|
|
10760
|
-
const ROVODEV_MCP_FILE_NAME = "mcp.json";
|
|
10761
|
-
const ROVODEV_CONFIG_FILE_NAME = "config.yml";
|
|
10762
|
-
const ROVODEV_AGENTS_SKILLS_DIR_PATH = join(".agents", "skills");
|
|
10763
|
-
const ROVODEV_PROMPTS_FILE_NAME = "prompts.yml";
|
|
10764
|
-
const ROVODEV_PROMPTS_DIR_PATH = join(ROVODEV_DIR, "prompts");
|
|
10765
|
-
//#endregion
|
|
10766
11673
|
//#region src/features/commands/rovodev-command.ts
|
|
10767
11674
|
/**
|
|
10768
11675
|
* Rovo Dev CLI "saved prompts": a file-based custom-command surface made of a
|
|
@@ -11346,6 +12253,16 @@ const toolCommandFactories = /* @__PURE__ */ new Map([
|
|
|
11346
12253
|
supportsSubdirectory: false
|
|
11347
12254
|
}
|
|
11348
12255
|
}],
|
|
12256
|
+
["grokcli", {
|
|
12257
|
+
class: GrokcliCommand,
|
|
12258
|
+
meta: {
|
|
12259
|
+
extension: "md",
|
|
12260
|
+
supportsProject: true,
|
|
12261
|
+
supportsGlobal: true,
|
|
12262
|
+
isSimulated: false,
|
|
12263
|
+
supportsSubdirectory: false
|
|
12264
|
+
}
|
|
12265
|
+
}],
|
|
11349
12266
|
["hermesagent", {
|
|
11350
12267
|
class: HermesagentCommand,
|
|
11351
12268
|
meta: {
|
|
@@ -11539,7 +12456,8 @@ var CommandsProcessor = class extends FeatureProcessor {
|
|
|
11539
12456
|
const factory = this.getFactory(this.toolTarget);
|
|
11540
12457
|
await factory.class.validateRulesyncCommands?.({
|
|
11541
12458
|
inputRoot: this.inputRoot,
|
|
11542
|
-
rulesyncCommands
|
|
12459
|
+
rulesyncCommands,
|
|
12460
|
+
logger: this.logger
|
|
11543
12461
|
});
|
|
11544
12462
|
const flattenedPathOrigins = /* @__PURE__ */ new Map();
|
|
11545
12463
|
const toolCommands = rulesyncCommands.map((rulesyncCommand) => {
|
|
@@ -11677,7 +12595,10 @@ var CommandsProcessor = class extends FeatureProcessor {
|
|
|
11677
12595
|
}));
|
|
11678
12596
|
const currentContent = await readFileContentOrNull(configPath);
|
|
11679
12597
|
if (currentContent === null) return changedCount;
|
|
11680
|
-
const nextContent = getDisabledHermesCommandsPluginConfigContent(
|
|
12598
|
+
const nextContent = getDisabledHermesCommandsPluginConfigContent({
|
|
12599
|
+
currentContent,
|
|
12600
|
+
global: this.global
|
|
12601
|
+
});
|
|
11681
12602
|
if (nextContent === currentContent) return changedCount;
|
|
11682
12603
|
if (this.dryRun) this.logger.info(`[DRY RUN] Would write: ${configPath}`);
|
|
11683
12604
|
else await writeFileContent(configPath, nextContent);
|
|
@@ -11960,6 +12881,13 @@ function groupDefinitionsByMatcher(definitions) {
|
|
|
11960
12881
|
}
|
|
11961
12882
|
return byMatcher;
|
|
11962
12883
|
}
|
|
12884
|
+
/** `$CLAUDE_PROJECT_DIR` -> `${CLAUDE_PROJECT_DIR}`, the form the tool substitutes. */
|
|
12885
|
+
function bracePlaceholder(projectDirVar) {
|
|
12886
|
+
return `\${${projectDirVar.replace(/^\$/, "")}}`;
|
|
12887
|
+
}
|
|
12888
|
+
function stripSurroundingQuotes(value) {
|
|
12889
|
+
return value.replace(/^(["'])(.*)\1$/, "$2").replace(/^["']/, "");
|
|
12890
|
+
}
|
|
11963
12891
|
/**
|
|
11964
12892
|
* Apply the optional project directory variable prefix to a command string.
|
|
11965
12893
|
*/
|
|
@@ -11969,8 +12897,10 @@ function applyCommandPrefix({ def, converterConfig }) {
|
|
|
11969
12897
|
const unquotedCommand = trimmedCommand?.replace(/^["']/, "");
|
|
11970
12898
|
const isDotRelativeCommand = unquotedCommand?.startsWith(".") ?? false;
|
|
11971
12899
|
const isAbsoluteCommand = typeof unquotedCommand === "string" && (posix.isAbsolute(unquotedCommand) || win32.isAbsolute(unquotedCommand) || unquotedCommand.startsWith("~/"));
|
|
12900
|
+
const isExecForm = (converterConfig.arrayPassthroughFields?.some(({ canonical }) => canonical === "args") ?? false) && Array.isArray(def.args);
|
|
11972
12901
|
if (!(converterConfig.projectDirVar !== "" && typeof trimmedCommand === "string" && !trimmedCommand.startsWith("$") && !isAbsoluteCommand && (!converterConfig.prefixDotRelativeCommandsOnly || isDotRelativeCommand)) || typeof trimmedCommand !== "string") return def.command;
|
|
11973
12902
|
const relativeCommand = trimmedCommand.replace(/^(["'])\.\//, "$1").replace(/^\.\//, "");
|
|
12903
|
+
if (isExecForm) return `${bracePlaceholder(converterConfig.projectDirVar)}/${stripSurroundingQuotes(relativeCommand)}`;
|
|
11974
12904
|
return `"${converterConfig.projectDirVar}"/${relativeCommand}`;
|
|
11975
12905
|
}
|
|
11976
12906
|
/**
|
|
@@ -11978,8 +12908,11 @@ function applyCommandPrefix({ def, converterConfig }) {
|
|
|
11978
12908
|
* canonical field name to its (possibly renamed) tool field name. Only boolean
|
|
11979
12909
|
* values are carried through.
|
|
11980
12910
|
*/
|
|
11981
|
-
function emitBooleanPassthroughFields({ def, converterConfig }) {
|
|
11982
|
-
return Object.fromEntries((converterConfig.booleanPassthroughFields ?? []).filter(({ canonical
|
|
12911
|
+
function emitBooleanPassthroughFields({ def, hookType, converterConfig }) {
|
|
12912
|
+
return Object.fromEntries((converterConfig.booleanPassthroughFields ?? []).filter(({ canonical, commandOnly }) => {
|
|
12913
|
+
if (commandOnly === true && hookType !== "command") return false;
|
|
12914
|
+
return typeof def[canonical] === "boolean";
|
|
12915
|
+
}).map(({ canonical, tool }) => [tool, def[canonical]]));
|
|
11983
12916
|
}
|
|
11984
12917
|
/**
|
|
11985
12918
|
* Import the configured boolean passthrough fields back into canonical fields,
|
|
@@ -11993,8 +12926,11 @@ function importBooleanPassthroughFields({ h, converterConfig }) {
|
|
|
11993
12926
|
* canonical field name to its (possibly renamed) tool field name. Only non-empty
|
|
11994
12927
|
* string values are carried through.
|
|
11995
12928
|
*/
|
|
11996
|
-
function emitStringPassthroughFields({ def, converterConfig }) {
|
|
11997
|
-
return Object.fromEntries((converterConfig.stringPassthroughFields ?? []).filter(({ canonical
|
|
12929
|
+
function emitStringPassthroughFields({ def, hookType, converterConfig }) {
|
|
12930
|
+
return Object.fromEntries((converterConfig.stringPassthroughFields ?? []).filter(({ canonical, commandOnly }) => {
|
|
12931
|
+
if (commandOnly === true && hookType !== "command") return false;
|
|
12932
|
+
return typeof def[canonical] === "string" && def[canonical] !== "";
|
|
12933
|
+
}).map(({ canonical, tool }) => [tool, def[canonical]]));
|
|
11998
12934
|
}
|
|
11999
12935
|
/**
|
|
12000
12936
|
* Import the configured string passthrough fields back into canonical fields,
|
|
@@ -12007,8 +12943,11 @@ function importStringPassthroughFields({ h, converterConfig }) {
|
|
|
12007
12943
|
/**
|
|
12008
12944
|
* Emit the configured string-array passthrough fields on the tool side.
|
|
12009
12945
|
*/
|
|
12010
|
-
function emitArrayPassthroughFields({ def, converterConfig }) {
|
|
12011
|
-
return Object.fromEntries((converterConfig.arrayPassthroughFields ?? []).filter(({ canonical
|
|
12946
|
+
function emitArrayPassthroughFields({ def, hookType, converterConfig }) {
|
|
12947
|
+
return Object.fromEntries((converterConfig.arrayPassthroughFields ?? []).filter(({ canonical, commandOnly }) => {
|
|
12948
|
+
if (commandOnly === true && hookType !== "command") return false;
|
|
12949
|
+
return isStringArray(def[canonical]);
|
|
12950
|
+
}).map(({ canonical, tool }) => [tool, def[canonical]]));
|
|
12012
12951
|
}
|
|
12013
12952
|
/**
|
|
12014
12953
|
* Import the configured string-array passthrough fields, reversing
|
|
@@ -12085,14 +13024,17 @@ function buildToolHooks({ defs, converterConfig }) {
|
|
|
12085
13024
|
hooks.push({
|
|
12086
13025
|
...emitBooleanPassthroughFields({
|
|
12087
13026
|
def,
|
|
13027
|
+
hookType,
|
|
12088
13028
|
converterConfig
|
|
12089
13029
|
}),
|
|
12090
13030
|
...emitStringPassthroughFields({
|
|
12091
13031
|
def,
|
|
13032
|
+
hookType,
|
|
12092
13033
|
converterConfig
|
|
12093
13034
|
}),
|
|
12094
13035
|
...emitArrayPassthroughFields({
|
|
12095
13036
|
def,
|
|
13037
|
+
hookType,
|
|
12096
13038
|
converterConfig
|
|
12097
13039
|
}),
|
|
12098
13040
|
type: hookType,
|
|
@@ -12176,6 +13118,8 @@ function stripCommandPrefix({ command, converterConfig }) {
|
|
|
12176
13118
|
if (converterConfig.projectDirVar === "" || typeof cmd !== "string") return cmd;
|
|
12177
13119
|
const quotedPrefix = `"${converterConfig.projectDirVar}"/`;
|
|
12178
13120
|
if (cmd.startsWith(quotedPrefix)) return `./${cmd.slice(quotedPrefix.length)}`;
|
|
13121
|
+
const bracedPrefix = `${bracePlaceholder(converterConfig.projectDirVar)}/`;
|
|
13122
|
+
if (cmd.startsWith(bracedPrefix)) return `./${cmd.slice(bracedPrefix.length)}`;
|
|
12179
13123
|
if (cmd.includes(`${converterConfig.projectDirVar}/`)) {
|
|
12180
13124
|
const escapedVar = converterConfig.projectDirVar.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
12181
13125
|
return cmd.replace(new RegExp(`^${escapedVar}\\/?`), "./");
|
|
@@ -12723,7 +13667,10 @@ const CLAUDE_CONVERTER_CONFIG = {
|
|
|
12723
13667
|
"taskCreated",
|
|
12724
13668
|
"taskCompleted",
|
|
12725
13669
|
"teammateIdle",
|
|
12726
|
-
"cwdChanged"
|
|
13670
|
+
"cwdChanged",
|
|
13671
|
+
"beforeSubmitPrompt",
|
|
13672
|
+
"stop",
|
|
13673
|
+
"directoryAdded"
|
|
12727
13674
|
]),
|
|
12728
13675
|
supportedHookTypes: /* @__PURE__ */ new Set([
|
|
12729
13676
|
"command",
|
|
@@ -12733,9 +13680,45 @@ const CLAUDE_CONVERTER_CONFIG = {
|
|
|
12733
13680
|
"agent"
|
|
12734
13681
|
]),
|
|
12735
13682
|
emitsPromptModel: true,
|
|
12736
|
-
stringPassthroughFields: [
|
|
12737
|
-
|
|
12738
|
-
|
|
13683
|
+
stringPassthroughFields: [
|
|
13684
|
+
{
|
|
13685
|
+
canonical: "if",
|
|
13686
|
+
tool: "if"
|
|
13687
|
+
},
|
|
13688
|
+
{
|
|
13689
|
+
canonical: "statusMessage",
|
|
13690
|
+
tool: "statusMessage"
|
|
13691
|
+
},
|
|
13692
|
+
{
|
|
13693
|
+
canonical: "shell",
|
|
13694
|
+
tool: "shell",
|
|
13695
|
+
commandOnly: true
|
|
13696
|
+
}
|
|
13697
|
+
],
|
|
13698
|
+
booleanPassthroughFields: [
|
|
13699
|
+
{
|
|
13700
|
+
canonical: "once",
|
|
13701
|
+
tool: "once"
|
|
13702
|
+
},
|
|
13703
|
+
{
|
|
13704
|
+
canonical: "async",
|
|
13705
|
+
tool: "async",
|
|
13706
|
+
commandOnly: true
|
|
13707
|
+
},
|
|
13708
|
+
{
|
|
13709
|
+
canonical: "asyncRewake",
|
|
13710
|
+
tool: "asyncRewake",
|
|
13711
|
+
commandOnly: true
|
|
13712
|
+
},
|
|
13713
|
+
{
|
|
13714
|
+
canonical: "continueOnBlock",
|
|
13715
|
+
tool: "continueOnBlock"
|
|
13716
|
+
}
|
|
13717
|
+
],
|
|
13718
|
+
arrayPassthroughFields: [{
|
|
13719
|
+
canonical: "args",
|
|
13720
|
+
tool: "args",
|
|
13721
|
+
commandOnly: true
|
|
12739
13722
|
}]
|
|
12740
13723
|
};
|
|
12741
13724
|
var ClaudecodeHooks = class extends ToolHooks {
|
|
@@ -12843,7 +13826,14 @@ const CODEXCLI_CONVERTER_CONFIG = {
|
|
|
12843
13826
|
toolToCanonicalEventNames: CODEXCLI_TO_CANONICAL_EVENT_NAMES,
|
|
12844
13827
|
projectDirVar: "",
|
|
12845
13828
|
supportedHookTypes: /* @__PURE__ */ new Set(["command"]),
|
|
12846
|
-
passthroughFields: ["name", "description"]
|
|
13829
|
+
passthroughFields: ["name", "description"],
|
|
13830
|
+
stringPassthroughFields: [{
|
|
13831
|
+
canonical: "commandWindows",
|
|
13832
|
+
tool: "commandWindows"
|
|
13833
|
+
}, {
|
|
13834
|
+
canonical: "statusMessage",
|
|
13835
|
+
tool: "statusMessage"
|
|
13836
|
+
}]
|
|
12847
13837
|
};
|
|
12848
13838
|
/**
|
|
12849
13839
|
* Build the content for `.codex/config.toml`, cleaning up the deprecated `codex_hooks` key.
|
|
@@ -13091,7 +14081,11 @@ var CopilotHooks = class CopilotHooks extends ToolHooks {
|
|
|
13091
14081
|
fileContent: params.fileContent ?? "{}"
|
|
13092
14082
|
});
|
|
13093
14083
|
}
|
|
13094
|
-
static getSettablePaths(
|
|
14084
|
+
static getSettablePaths({ global = false } = {}) {
|
|
14085
|
+
if (global) return {
|
|
14086
|
+
relativeDirPath: COPILOT_GLOBAL_HOOKS_DIR_PATH,
|
|
14087
|
+
relativeFilePath: COPILOT_GLOBAL_HOOKS_FILE_NAME
|
|
14088
|
+
};
|
|
13095
14089
|
return {
|
|
13096
14090
|
relativeDirPath: COPILOT_HOOKS_DIR_PATH,
|
|
13097
14091
|
relativeFilePath: COPILOT_HOOKS_FILE_NAME
|
|
@@ -13105,11 +14099,12 @@ var CopilotHooks = class CopilotHooks extends ToolHooks {
|
|
|
13105
14099
|
relativeDirPath: paths.relativeDirPath,
|
|
13106
14100
|
relativeFilePath: paths.relativeFilePath,
|
|
13107
14101
|
fileContent,
|
|
13108
|
-
validate
|
|
14102
|
+
validate,
|
|
14103
|
+
global
|
|
13109
14104
|
});
|
|
13110
14105
|
}
|
|
13111
|
-
static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true }) {
|
|
13112
|
-
const paths = CopilotHooks.getSettablePaths();
|
|
14106
|
+
static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false }) {
|
|
14107
|
+
const paths = CopilotHooks.getSettablePaths({ global });
|
|
13113
14108
|
const copilotHooks = canonicalToCopilotHooks(rulesyncHooks.getJson());
|
|
13114
14109
|
const fileContent = JSON.stringify({
|
|
13115
14110
|
version: 1,
|
|
@@ -13120,7 +14115,8 @@ var CopilotHooks = class CopilotHooks extends ToolHooks {
|
|
|
13120
14115
|
relativeDirPath: paths.relativeDirPath,
|
|
13121
14116
|
relativeFilePath: paths.relativeFilePath,
|
|
13122
14117
|
fileContent,
|
|
13123
|
-
validate
|
|
14118
|
+
validate,
|
|
14119
|
+
global
|
|
13124
14120
|
});
|
|
13125
14121
|
}
|
|
13126
14122
|
toRulesyncHooks(options) {
|
|
@@ -13164,10 +14160,16 @@ var CopilotHooks = class CopilotHooks extends ToolHooks {
|
|
|
13164
14160
|
* (`sessionStart`, `sessionEnd`, `userPromptSubmitted`, `preToolUse`,
|
|
13165
14161
|
* `postToolUse`, `postToolUseFailure`, `agentStop`, `subagentStart`,
|
|
13166
14162
|
* `subagentStop`, `errorOccurred`, `preCompact`, `permissionRequest`,
|
|
13167
|
-
* `notification`, `preMcpToolCall`). Each entry
|
|
14163
|
+
* `notification`, `userPromptTransformed`, `preMcpToolCall`). Each entry
|
|
14164
|
+
* supports three hook types:
|
|
13168
14165
|
*
|
|
13169
14166
|
* - `command` — the `bash` / `powershell` command-field shape with optional
|
|
13170
|
-
* `timeoutSec`, plus optional `cwd` / `env`.
|
|
14167
|
+
* `timeoutSec`, plus optional `cwd` / `env`. Upstream also accepts the
|
|
14168
|
+
* portable `command` field (copied to both shells when neither is present)
|
|
14169
|
+
* and `timeout` as an alias for `timeoutSec`; rulesync reads both on import.
|
|
14170
|
+
* On generate the canonical `shell` selector picks `bash` or `powershell`,
|
|
14171
|
+
* and without it the portable `command` field is written — so the generated
|
|
14172
|
+
* file does not depend on the machine rulesync ran on.
|
|
13171
14173
|
* - `prompt` — a `prompt` string (Copilot CLI only honors prompt hooks on
|
|
13172
14174
|
* `sessionStart`, so prompt hooks on other events are skipped).
|
|
13173
14175
|
* - `http` — `url` / `headers` / `allowedEnvVars` with optional `timeoutSec`.
|
|
@@ -13193,11 +14195,11 @@ var CopilotHooks = class CopilotHooks extends ToolHooks {
|
|
|
13193
14195
|
* all rulesync-managed Copilot CLI files under the single `~/.copilot/`
|
|
13194
14196
|
* root and will revisit if the spec later mandates an alternate layout.
|
|
13195
14197
|
*
|
|
13196
|
-
* Hook entries on
|
|
13197
|
-
*
|
|
13198
|
-
* emitted on those events and dropped (with a warning) on any other event,
|
|
13199
|
-
* which never honors matchers.
|
|
13200
|
-
*
|
|
14198
|
+
* Hook entries on the six matcher-aware events (see
|
|
14199
|
+
* {@link COPILOTCLI_MATCHER_EVENTS}) may carry an optional `matcher` regex; it
|
|
14200
|
+
* is emitted on those events and dropped (with a warning) on any other event,
|
|
14201
|
+
* which never honors matchers.
|
|
14202
|
+
* Reference: https://docs.github.com/en/copilot/reference/hooks-reference
|
|
13201
14203
|
*
|
|
13202
14204
|
* The output JSON schema and platform-specific `bash` / `powershell` command
|
|
13203
14205
|
* field selection match `copilot-hooks.ts`, but the event surface diverges (the
|
|
@@ -13212,19 +14214,30 @@ var CopilotHooks = class CopilotHooks extends ToolHooks {
|
|
|
13212
14214
|
* `Edit|Write`) are now honored instead of silently dropped").
|
|
13213
14215
|
* @see https://docs.github.com/en/copilot/reference/hooks-reference
|
|
13214
14216
|
*/
|
|
13215
|
-
const COPILOTCLI_MATCHER_EVENTS = /* @__PURE__ */ new Set([
|
|
14217
|
+
const COPILOTCLI_MATCHER_EVENTS = /* @__PURE__ */ new Set([
|
|
14218
|
+
"preToolUse",
|
|
14219
|
+
"postToolUse",
|
|
14220
|
+
"notification",
|
|
14221
|
+
"permissionRequest",
|
|
14222
|
+
"preCompact",
|
|
14223
|
+
"subagentStart"
|
|
14224
|
+
]);
|
|
14225
|
+
/** Human-readable list of the matcher-aware events, for the drop warning. */
|
|
14226
|
+
const COPILOTCLI_MATCHER_EVENTS_LABEL = [...COPILOTCLI_MATCHER_EVENTS].join("/");
|
|
13216
14227
|
const CopilotCliHookEntrySchema = z.looseObject({
|
|
13217
14228
|
type: z._default(z.string(), "command"),
|
|
13218
14229
|
matcher: z.optional(z.string()),
|
|
13219
14230
|
bash: z.optional(z.string()),
|
|
13220
14231
|
powershell: z.optional(z.string()),
|
|
14232
|
+
command: z.optional(z.string()),
|
|
13221
14233
|
prompt: z.optional(z.string()),
|
|
13222
14234
|
url: z.optional(z.string()),
|
|
13223
14235
|
headers: z.optional(z.record(z.string(), z.string())),
|
|
13224
14236
|
allowedEnvVars: z.optional(z.array(z.string())),
|
|
13225
14237
|
cwd: z.optional(z.string()),
|
|
13226
14238
|
env: z.optional(z.record(z.string(), z.string())),
|
|
13227
|
-
timeoutSec: z.optional(z.number())
|
|
14239
|
+
timeoutSec: z.optional(z.number()),
|
|
14240
|
+
timeout: z.optional(z.number())
|
|
13228
14241
|
});
|
|
13229
14242
|
/** Filter the shared config hooks down to events the Copilot CLI supports. */
|
|
13230
14243
|
function filterSupportedCopilotCliHooks(hooks) {
|
|
@@ -13235,21 +14248,21 @@ function filterSupportedCopilotCliHooks(hooks) {
|
|
|
13235
14248
|
}
|
|
13236
14249
|
/**
|
|
13237
14250
|
* Resolve the `matcher` part for an exported entry. Copilot CLI honors `matcher`
|
|
13238
|
-
* only on
|
|
13239
|
-
* silently dropped by the CLI, so we drop it here with
|
|
13240
|
-
* emitting a dead field.
|
|
14251
|
+
* only on the events listed in {@link COPILOTCLI_MATCHER_EVENTS}; on any other
|
|
14252
|
+
* event a matcher would be silently dropped by the CLI, so we drop it here with
|
|
14253
|
+
* a warning rather than emitting a dead field.
|
|
13241
14254
|
*/
|
|
13242
14255
|
function resolveExportMatcherPart({ matcher, matcherSupported, eventName, logger }) {
|
|
13243
14256
|
if (matcher === void 0 || matcher === null || matcher === "") return {};
|
|
13244
14257
|
if (matcherSupported) return { matcher };
|
|
13245
|
-
logger?.warn(`Copilot CLI hook matchers are only honored on
|
|
14258
|
+
logger?.warn(`Copilot CLI hook matchers are only honored on ${COPILOTCLI_MATCHER_EVENTS_LABEL}; dropping matcher "${matcher}" on '${eventName}'.`);
|
|
13246
14259
|
return {};
|
|
13247
14260
|
}
|
|
13248
14261
|
/**
|
|
13249
14262
|
* Build the exported entries for a single canonical event. Returns an empty
|
|
13250
14263
|
* array when no entries are emitted (e.g. all prompt hooks were skipped).
|
|
13251
14264
|
*/
|
|
13252
|
-
function buildCopilotCliEntriesForEvent({ eventName, definitions, canonicalSchemaKeys,
|
|
14265
|
+
function buildCopilotCliEntriesForEvent({ eventName, definitions, canonicalSchemaKeys, logger }) {
|
|
13253
14266
|
const matcherSupported = COPILOTCLI_MATCHER_EVENTS.has(eventName);
|
|
13254
14267
|
const entries = [];
|
|
13255
14268
|
for (const def of definitions) {
|
|
@@ -13285,22 +14298,24 @@ function buildCopilotCliEntriesForEvent({ eventName, definitions, canonicalSchem
|
|
|
13285
14298
|
...timeoutPart,
|
|
13286
14299
|
...rest
|
|
13287
14300
|
});
|
|
13288
|
-
else if (hookType === "command")
|
|
13289
|
-
|
|
13290
|
-
|
|
13291
|
-
|
|
13292
|
-
|
|
13293
|
-
|
|
13294
|
-
|
|
13295
|
-
|
|
13296
|
-
|
|
13297
|
-
|
|
14301
|
+
else if (hookType === "command") {
|
|
14302
|
+
const commandField = def.shell ?? "command";
|
|
14303
|
+
entries.push({
|
|
14304
|
+
type: "command",
|
|
14305
|
+
...matcherPart,
|
|
14306
|
+
...compact({
|
|
14307
|
+
[commandField]: def.command,
|
|
14308
|
+
env: def.env
|
|
14309
|
+
}),
|
|
14310
|
+
...timeoutPart,
|
|
14311
|
+
...rest
|
|
14312
|
+
});
|
|
14313
|
+
}
|
|
13298
14314
|
}
|
|
13299
14315
|
return entries;
|
|
13300
14316
|
}
|
|
13301
14317
|
function canonicalToCopilotCliHooks(config, logger) {
|
|
13302
14318
|
const canonicalSchemaKeys = Object.keys(HookDefinitionSchema.shape);
|
|
13303
|
-
const commandField = process.platform === "win32" ? "powershell" : "bash";
|
|
13304
14319
|
const effectiveHooks = {
|
|
13305
14320
|
...filterSupportedCopilotCliHooks(config.hooks),
|
|
13306
14321
|
...config.copilot?.hooks,
|
|
@@ -13313,7 +14328,6 @@ function canonicalToCopilotCliHooks(config, logger) {
|
|
|
13313
14328
|
eventName,
|
|
13314
14329
|
definitions,
|
|
13315
14330
|
canonicalSchemaKeys,
|
|
13316
|
-
commandField,
|
|
13317
14331
|
logger
|
|
13318
14332
|
});
|
|
13319
14333
|
if (entries.length > 0) out[copilotEventName] = entries;
|
|
@@ -13330,6 +14344,13 @@ function importPassthrough(entry) {
|
|
|
13330
14344
|
if (entry.env !== void 0) passthrough.env = entry.env;
|
|
13331
14345
|
return passthrough;
|
|
13332
14346
|
}
|
|
14347
|
+
/**
|
|
14348
|
+
* Resolve the canonical command and its `shell` selector from an imported entry.
|
|
14349
|
+
*
|
|
14350
|
+
* A shell-specific field carries its `shell` through so re-export writes the
|
|
14351
|
+
* same field back. An entry using only the portable `command` field leaves
|
|
14352
|
+
* `shell` unset, which re-export renders as the portable field again.
|
|
14353
|
+
*/
|
|
13333
14354
|
function resolveImportCommand(entry, logger) {
|
|
13334
14355
|
const hasBash = typeof entry.bash === "string";
|
|
13335
14356
|
const hasPowershell = typeof entry.powershell === "string";
|
|
@@ -13338,9 +14359,22 @@ function resolveImportCommand(entry, logger) {
|
|
|
13338
14359
|
const chosen = isWindows ? "powershell" : "bash";
|
|
13339
14360
|
const ignored = isWindows ? "bash" : "powershell";
|
|
13340
14361
|
logger?.warn(`Copilot CLI hook has both bash and powershell commands; using ${chosen} and ignoring ${ignored} on this platform.`);
|
|
13341
|
-
return isWindows ?
|
|
13342
|
-
|
|
13343
|
-
|
|
14362
|
+
return isWindows ? {
|
|
14363
|
+
command: entry.powershell,
|
|
14364
|
+
shell: "powershell"
|
|
14365
|
+
} : {
|
|
14366
|
+
command: entry.bash,
|
|
14367
|
+
shell: "bash"
|
|
14368
|
+
};
|
|
14369
|
+
} else if (hasBash) return {
|
|
14370
|
+
command: entry.bash,
|
|
14371
|
+
shell: "bash"
|
|
14372
|
+
};
|
|
14373
|
+
else if (hasPowershell) return {
|
|
14374
|
+
command: entry.powershell,
|
|
14375
|
+
shell: "powershell"
|
|
14376
|
+
};
|
|
14377
|
+
return typeof entry.command === "string" ? { command: entry.command } : {};
|
|
13344
14378
|
}
|
|
13345
14379
|
function copilotCliHooksToCanonical(rawHooks, logger) {
|
|
13346
14380
|
if (rawHooks === null || rawHooks === void 0 || typeof rawHooks !== "object") return {};
|
|
@@ -13353,7 +14387,7 @@ function copilotCliHooksToCanonical(rawHooks, logger) {
|
|
|
13353
14387
|
const parseResult = CopilotCliHookEntrySchema.safeParse(rawEntry);
|
|
13354
14388
|
if (!parseResult.success) continue;
|
|
13355
14389
|
const entry = parseResult.data;
|
|
13356
|
-
const timeout = entry.timeoutSec;
|
|
14390
|
+
const timeout = entry.timeoutSec ?? entry.timeout;
|
|
13357
14391
|
const timeoutPart = timeout !== void 0 ? { timeout } : {};
|
|
13358
14392
|
const matcherPart = entry.matcher !== void 0 && entry.matcher !== "" ? { matcher: entry.matcher } : {};
|
|
13359
14393
|
const passthrough = importPassthrough(entry);
|
|
@@ -13372,10 +14406,11 @@ function copilotCliHooksToCanonical(rawHooks, logger) {
|
|
|
13372
14406
|
...passthrough
|
|
13373
14407
|
});
|
|
13374
14408
|
else {
|
|
13375
|
-
const command = resolveImportCommand(entry, logger);
|
|
14409
|
+
const { command, shell } = resolveImportCommand(entry, logger);
|
|
13376
14410
|
defs.push({
|
|
13377
14411
|
type: "command",
|
|
13378
14412
|
...command !== void 0 && { command },
|
|
14413
|
+
...shell !== void 0 && { shell },
|
|
13379
14414
|
...matcherPart,
|
|
13380
14415
|
...timeoutPart,
|
|
13381
14416
|
...passthrough
|
|
@@ -14025,64 +15060,6 @@ var GooseHooks = class GooseHooks extends ToolHooks {
|
|
|
14025
15060
|
}
|
|
14026
15061
|
};
|
|
14027
15062
|
//#endregion
|
|
14028
|
-
//#region src/constants/grokcli-paths.ts
|
|
14029
|
-
/**
|
|
14030
|
-
* Grok Build CLI (xAI) configuration-layout conventions.
|
|
14031
|
-
*
|
|
14032
|
-
* Single source of truth for where Grok Build expects its files. Grok Build
|
|
14033
|
-
* stores MCP servers (and other settings) in a `config.toml` under `.grok/`,
|
|
14034
|
-
* with project/global scopes resolved by the directory the CLI runs in
|
|
14035
|
-
* (`./.grok/config.toml` vs `~/.grok/config.toml`).
|
|
14036
|
-
*
|
|
14037
|
-
* Verified against `grok` 0.2.54 (`grok mcp add --help`, `grok mcp add`):
|
|
14038
|
-
* `-s project` writes `./.grok/config.toml`, `-s user` writes
|
|
14039
|
-
* `~/.grok/config.toml`, both as a TOML `[mcp_servers.<name>]` table.
|
|
14040
|
-
* @see https://docs.x.ai/build/overview
|
|
14041
|
-
*/
|
|
14042
|
-
/** Root directory for Grok Build configuration, relative to the scope root. */
|
|
14043
|
-
const GROKCLI_DIR = ".grok";
|
|
14044
|
-
/** MCP servers and other settings live in `config.toml` under `.grok/`. */
|
|
14045
|
-
const GROKCLI_MCP_FILE_NAME = "config.toml";
|
|
14046
|
-
/**
|
|
14047
|
-
* Shared Grok CLI config file (`config.toml`). MCP servers, the `[ui]`
|
|
14048
|
-
* permission mode, and other settings all live here; permissions reuse the same
|
|
14049
|
-
* file name as MCP since Grok consolidates everything into one config.
|
|
14050
|
-
*/
|
|
14051
|
-
const GROKCLI_CONFIG_FILE_NAME = "config.toml";
|
|
14052
|
-
/** Skills directory under `.grok/` (project: `./.grok/skills`, global: `~/.grok/skills`). */
|
|
14053
|
-
const GROKCLI_SKILLS_DIR_PATH = join(GROKCLI_DIR, "skills");
|
|
14054
|
-
/**
|
|
14055
|
-
* Hooks directory under `.grok/`. Grok Build discovers hook config files from
|
|
14056
|
-
* `.grok/hooks/*.json` (project) and `~/.grok/hooks/*.json` (global), each a
|
|
14057
|
-
* standalone JSON file using the Claude-Code-compatible nested `{ hooks: { … } }`
|
|
14058
|
-
* shape. rulesync writes all its hooks into a single `rulesync.json`.
|
|
14059
|
-
* @see https://docs.x.ai/build/features/hooks
|
|
14060
|
-
*/
|
|
14061
|
-
const GROKCLI_HOOKS_DIR_PATH = join(GROKCLI_DIR, "hooks");
|
|
14062
|
-
/** rulesync-managed Grok hooks file under `.grok/hooks/`. */
|
|
14063
|
-
const GROKCLI_HOOKS_FILE_NAME = "rulesync.json";
|
|
14064
|
-
/**
|
|
14065
|
-
* Subagents (agent profiles) directory under `.grok/`. Grok Build discovers
|
|
14066
|
-
* agent definitions from `.grok/agents/*.md` (project) and `~/.grok/agents/*.md`
|
|
14067
|
-
* (global), each a Markdown file with YAML frontmatter (verified via
|
|
14068
|
-
* `grok inspect`; format matches the bundled `~/.grok/bundled/agents/*.md`).
|
|
14069
|
-
*/
|
|
14070
|
-
const GROKCLI_AGENTS_DIR_PATH = join(GROKCLI_DIR, "agents");
|
|
14071
|
-
/**
|
|
14072
|
-
* Instruction file. Grok reads the AGENTS.md instruction-file family natively,
|
|
14073
|
-
* including the user-level `~/.grok/AGENTS.md` for global rules (verified via
|
|
14074
|
-
* `grok inspect`, consistent with the `.grok/` global discovery used by the
|
|
14075
|
-
* MCP/skills/subagents adapters).
|
|
14076
|
-
*/
|
|
14077
|
-
const GROKCLI_RULE_FILE_NAME = "AGENTS.md";
|
|
14078
|
-
/**
|
|
14079
|
-
* Non-root rules directory. Grok scans `*.md` here — flat, sorted by name —
|
|
14080
|
-
* alongside the AGENTS.md family: `.grok/rules/` in each project directory it
|
|
14081
|
-
* walks, and `~/.grok/rules/` in the home scope.
|
|
14082
|
-
* @see https://docs.x.ai/build/overview
|
|
14083
|
-
*/
|
|
14084
|
-
const GROKCLI_RULES_DIR_PATH = join(GROKCLI_DIR, "rules");
|
|
14085
|
-
//#endregion
|
|
14086
15063
|
//#region src/features/hooks/grokcli-hooks.ts
|
|
14087
15064
|
const GROKCLI_CONVERTER_CONFIG = {
|
|
14088
15065
|
supportedEvents: GROKCLI_HOOK_EVENTS,
|
|
@@ -14328,6 +15305,13 @@ var HermesagentHooks = class HermesagentHooks extends ToolHooks {
|
|
|
14328
15305
|
relativeFilePath: HERMESAGENT_CONFIG_FILE_NAME
|
|
14329
15306
|
};
|
|
14330
15307
|
}
|
|
15308
|
+
/**
|
|
15309
|
+
* `config.yaml` under every spelling the global profile root can take.
|
|
15310
|
+
* @see getHermesagentSharedConfigWritePaths
|
|
15311
|
+
*/
|
|
15312
|
+
static getExtraSharedWritePaths() {
|
|
15313
|
+
return getHermesagentSharedConfigWritePaths();
|
|
15314
|
+
}
|
|
14331
15315
|
constructor(params) {
|
|
14332
15316
|
super({
|
|
14333
15317
|
...params,
|
|
@@ -14365,7 +15349,7 @@ var HermesagentHooks = class HermesagentHooks extends ToolHooks {
|
|
|
14365
15349
|
}
|
|
14366
15350
|
setFileContent(fileContent) {
|
|
14367
15351
|
this.fileContent = applySharedConfigPatch({
|
|
14368
|
-
fileKey:
|
|
15352
|
+
fileKey: getHermesagentConfigSharedFileKey({ global: this.global }),
|
|
14369
15353
|
feature: "hooks",
|
|
14370
15354
|
existingContent: fileContent,
|
|
14371
15355
|
patch: parseSharedConfig({
|
|
@@ -14515,7 +15499,23 @@ var JunieHooks = class JunieHooks extends ToolHooks {
|
|
|
14515
15499
|
};
|
|
14516
15500
|
//#endregion
|
|
14517
15501
|
//#region src/features/hooks/opencode-style-generator.ts
|
|
14518
|
-
|
|
15502
|
+
/**
|
|
15503
|
+
* Tool events emitted as named `(input, ...)` hooks rather than through the
|
|
15504
|
+
* generic `event.type` dispatch, mapped to the expression a hook's `matcher`
|
|
15505
|
+
* regex is tested against — or `null` when the hook has no matchable subject,
|
|
15506
|
+
* in which case a matcher is dropped rather than compiled against a field that
|
|
15507
|
+
* does not exist.
|
|
15508
|
+
*
|
|
15509
|
+
* `experimental.session.compacting` receives `(input, output)` and exposes no
|
|
15510
|
+
* per-invocation identifier worth matching on, so it takes `null`.
|
|
15511
|
+
*
|
|
15512
|
+
* @see https://opencode.ai/docs/plugins/
|
|
15513
|
+
*/
|
|
15514
|
+
const NAMED_HOOK_MATCHER_SUBJECTS = {
|
|
15515
|
+
"tool.execute.before": "input.tool",
|
|
15516
|
+
"tool.execute.after": "input.tool",
|
|
15517
|
+
"experimental.session.compacting": null
|
|
15518
|
+
};
|
|
14519
15519
|
function escapeForTemplateLiteral(command) {
|
|
14520
15520
|
return command.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
|
|
14521
15521
|
}
|
|
@@ -14548,7 +15548,7 @@ function collectOpencodeStyleHandlers({ effectiveHooks, eventMap, namedEventHand
|
|
|
14548
15548
|
});
|
|
14549
15549
|
}
|
|
14550
15550
|
if (handlers.length > 0) {
|
|
14551
|
-
const grouped =
|
|
15551
|
+
const grouped = Object.hasOwn(NAMED_HOOK_MATCHER_SUBJECTS, toolEvent) ? namedEventHandlers : genericEventHandlers;
|
|
14552
15552
|
const existing = grouped[toolEvent];
|
|
14553
15553
|
if (existing) existing.push(...handlers);
|
|
14554
15554
|
else grouped[toolEvent] = handlers;
|
|
@@ -14577,14 +15577,15 @@ function buildGenericEventBodyLines(genericEventHandlers) {
|
|
|
14577
15577
|
function buildNamedEventBodyLines(namedEventHandlers) {
|
|
14578
15578
|
const bodyLines = [];
|
|
14579
15579
|
for (const [eventName, handlers] of Object.entries(namedEventHandlers)) {
|
|
15580
|
+
const matcherSubject = NAMED_HOOK_MATCHER_SUBJECTS[eventName] ?? null;
|
|
14580
15581
|
bodyLines.push(` "${eventName}": async (input) => {`);
|
|
14581
15582
|
for (const handler of handlers) {
|
|
14582
15583
|
const escapedCommand = escapeForTemplateLiteral(handler.command);
|
|
14583
|
-
if (handler.matcher) {
|
|
15584
|
+
if (handler.matcher && matcherSubject !== null) {
|
|
14584
15585
|
const safeMatcher = validateAndSanitizeMatcher(handler.matcher);
|
|
14585
15586
|
bodyLines.push(" {");
|
|
14586
15587
|
bodyLines.push(` const __re = new RegExp("${safeMatcher}");`);
|
|
14587
|
-
bodyLines.push(` if (__re.test(
|
|
15588
|
+
bodyLines.push(` if (__re.test(${matcherSubject})) {`);
|
|
14588
15589
|
bodyLines.push(` await $\`${escapedCommand}\`;`);
|
|
14589
15590
|
bodyLines.push(" }");
|
|
14590
15591
|
bodyLines.push(" }");
|
|
@@ -14724,8 +15725,38 @@ function getKimiCodeHome() {
|
|
|
14724
15725
|
function getKimiCodeRelativeDirPath({ global, relativeDirPath = "." }) {
|
|
14725
15726
|
return global && getKimiCodeHome() ? relativeDirPath : join(KIMI_CODE_DIR, relativeDirPath);
|
|
14726
15727
|
}
|
|
15728
|
+
/**
|
|
15729
|
+
* Both spellings the shared user `config.toml` can take: under `.kimi-code/`,
|
|
15730
|
+
* or at the root of `KIMI_CODE_HOME` when that override names the profile dir.
|
|
15731
|
+
* Declared unconditionally so the derived shared-file keys — and the drift
|
|
15732
|
+
* guards checked against them — do not depend on the ambient environment.
|
|
15733
|
+
*/
|
|
15734
|
+
function getKimiCodeSharedConfigWritePaths() {
|
|
15735
|
+
return [{
|
|
15736
|
+
relativeDirPath: KIMI_CODE_DIR,
|
|
15737
|
+
relativeFilePath: KIMI_CODE_CONFIG_FILE_NAME
|
|
15738
|
+
}, {
|
|
15739
|
+
relativeDirPath: ".",
|
|
15740
|
+
relativeFilePath: KIMI_CODE_CONFIG_FILE_NAME
|
|
15741
|
+
}];
|
|
15742
|
+
}
|
|
15743
|
+
/**
|
|
15744
|
+
* The `SHARED_CONFIG_OWNERSHIP` key of the `config.toml` actually being written.
|
|
15745
|
+
* Both spellings carry the same declaration, but passing the key of the file
|
|
15746
|
+
* being written keeps the write path and the drift guards on the same entry.
|
|
15747
|
+
*/
|
|
15748
|
+
function getKimiCodeConfigSharedFileKey({ global }) {
|
|
15749
|
+
return sharedConfigFileKey({
|
|
15750
|
+
relativeDirPath: getKimiCodeRelativeDirPath({ global }),
|
|
15751
|
+
relativeFilePath: KIMI_CODE_CONFIG_FILE_NAME
|
|
15752
|
+
});
|
|
15753
|
+
}
|
|
14727
15754
|
function getKimiCodeRulesyncOutputRoot({ nativeOutputRoot, global }) {
|
|
14728
|
-
return
|
|
15755
|
+
return getToolRulesyncOutputRoot({
|
|
15756
|
+
nativeOutputRoot,
|
|
15757
|
+
global,
|
|
15758
|
+
toolHome: getKimiCodeHome
|
|
15759
|
+
});
|
|
14729
15760
|
}
|
|
14730
15761
|
//#endregion
|
|
14731
15762
|
//#region src/features/hooks/kimi-code-hooks.ts
|
|
@@ -14823,13 +15854,20 @@ var KimiCodeHooks = class KimiCodeHooks extends ToolHooks {
|
|
|
14823
15854
|
isDeletable() {
|
|
14824
15855
|
return false;
|
|
14825
15856
|
}
|
|
15857
|
+
/**
|
|
15858
|
+
* `config.toml` under both spellings its directory can take.
|
|
15859
|
+
* @see getKimiCodeSharedConfigWritePaths
|
|
15860
|
+
*/
|
|
15861
|
+
static getExtraSharedWritePaths() {
|
|
15862
|
+
return getKimiCodeSharedConfigWritePaths();
|
|
15863
|
+
}
|
|
14826
15864
|
shouldMergeExistingFileContent() {
|
|
14827
15865
|
return true;
|
|
14828
15866
|
}
|
|
14829
15867
|
setFileContent(fileContent) {
|
|
14830
15868
|
const paths = KimiCodeHooks.getSettablePaths({ global: this.global });
|
|
14831
15869
|
this.fileContent = applySharedConfigPatch({
|
|
14832
|
-
fileKey:
|
|
15870
|
+
fileKey: getKimiCodeConfigSharedFileKey({ global: this.global }),
|
|
14833
15871
|
feature: "hooks",
|
|
14834
15872
|
existingContent: fileContent,
|
|
14835
15873
|
patch: parseSharedConfig({
|
|
@@ -16220,7 +17258,7 @@ const toolHooksFactories = /* @__PURE__ */ new Map([
|
|
|
16220
17258
|
class: CopilotHooks,
|
|
16221
17259
|
meta: {
|
|
16222
17260
|
supportsProject: true,
|
|
16223
|
-
supportsGlobal:
|
|
17261
|
+
supportsGlobal: true,
|
|
16224
17262
|
supportsImport: true
|
|
16225
17263
|
},
|
|
16226
17264
|
supportedEvents: COPILOT_HOOK_EVENTS,
|
|
@@ -17520,6 +18558,151 @@ var QwencodeIgnore = class QwencodeIgnore extends ToolIgnore {
|
|
|
17520
18558
|
}
|
|
17521
18559
|
};
|
|
17522
18560
|
//#endregion
|
|
18561
|
+
//#region src/features/shared/reasonix-config-table.ts
|
|
18562
|
+
/**
|
|
18563
|
+
* Shape-narrowing helpers for the Reasonix TOML config (`reasonix.toml` /
|
|
18564
|
+
* `~/.reasonix/config.toml`), shared by the features that read-modify-write it.
|
|
18565
|
+
*
|
|
18566
|
+
* TOML is only structurally validated on parse, so a hand-edited config can
|
|
18567
|
+
* hold any type under `permissions` or inside `allow`/`ask`/`deny`. Both the
|
|
18568
|
+
* `permissions` and `ignore` adapters have to narrow the same two shapes
|
|
18569
|
+
* before merging, so the narrowing lives here once rather than being re-spelled
|
|
18570
|
+
* (and re-diverging) per feature.
|
|
18571
|
+
*/
|
|
18572
|
+
/** Keep only the string entries of a TOML array; anything else becomes `[]`. */
|
|
18573
|
+
function toReasonixStringArray(value) {
|
|
18574
|
+
if (!Array.isArray(value)) return [];
|
|
18575
|
+
return value.filter((entry) => typeof entry === "string");
|
|
18576
|
+
}
|
|
18577
|
+
/** Copy a TOML table; a non-table (scalar, array, missing) becomes `{}`. */
|
|
18578
|
+
function toReasonixTable(value) {
|
|
18579
|
+
if (!isPlainObject$1(value)) return {};
|
|
18580
|
+
return { ...value };
|
|
18581
|
+
}
|
|
18582
|
+
//#endregion
|
|
18583
|
+
//#region src/features/ignore/reasonix-ignore.ts
|
|
18584
|
+
const permissionsTableOf = (document) => toReasonixTable(document.permissions);
|
|
18585
|
+
/**
|
|
18586
|
+
* Reshape the parsed TOML document into the `permissions.allow/ask/deny` shape
|
|
18587
|
+
* {@link applyIgnoreReadDenies} operates on. Reasonix's `[permissions]` table
|
|
18588
|
+
* is Claude-Code-shaped (SPEC.md §3.7), so the entry-level ownership rule the
|
|
18589
|
+
* gateway already implements applies verbatim; only the surrounding file
|
|
18590
|
+
* format differs. Sibling keys such as `mode` pass through untouched.
|
|
18591
|
+
*/
|
|
18592
|
+
const asClaudeStyleSettings = (document) => {
|
|
18593
|
+
const table = permissionsTableOf(document);
|
|
18594
|
+
return {
|
|
18595
|
+
...document,
|
|
18596
|
+
permissions: {
|
|
18597
|
+
...table,
|
|
18598
|
+
allow: toReasonixStringArray(table.allow),
|
|
18599
|
+
ask: toReasonixStringArray(table.ask),
|
|
18600
|
+
deny: toReasonixStringArray(table.deny)
|
|
18601
|
+
}
|
|
18602
|
+
};
|
|
18603
|
+
};
|
|
18604
|
+
/**
|
|
18605
|
+
* Drop a `[permissions]` table that ended up with nothing in it, so an empty
|
|
18606
|
+
* `.rulesyncignore` does not add a bare table header to a file that never had
|
|
18607
|
+
* one.
|
|
18608
|
+
*/
|
|
18609
|
+
const withoutEmptyPermissions = (settings) => {
|
|
18610
|
+
const document = { ...settings };
|
|
18611
|
+
const permissions = document.permissions;
|
|
18612
|
+
if (isPlainObject$1(permissions) && Object.keys(permissions).length === 0) delete document.permissions;
|
|
18613
|
+
return document;
|
|
18614
|
+
};
|
|
18615
|
+
/**
|
|
18616
|
+
* Writes `.rulesyncignore` patterns as `Read(<pattern>)` entries in the
|
|
18617
|
+
* `[permissions] deny` table of `reasonix.toml` (project) /
|
|
18618
|
+
* `~/.reasonix/config.toml` (global).
|
|
18619
|
+
*
|
|
18620
|
+
* `deny` is the right target rather than `[sandbox] forbid_read`: deny rules
|
|
18621
|
+
* take glob specifiers (`Edit(docs/**)`) and are "a hard block in every mode",
|
|
18622
|
+
* while `forbid_read` is documented as absolute paths with no glob support.
|
|
18623
|
+
* @see https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/docs/SPEC.md
|
|
18624
|
+
*/
|
|
18625
|
+
var ReasonixIgnore = class ReasonixIgnore extends ToolIgnore {
|
|
18626
|
+
constructor(params) {
|
|
18627
|
+
super(params);
|
|
18628
|
+
const document = parseSharedConfig({
|
|
18629
|
+
format: "toml",
|
|
18630
|
+
fileContent: this.fileContent
|
|
18631
|
+
});
|
|
18632
|
+
this.patterns = toReasonixStringArray(permissionsTableOf(document).deny);
|
|
18633
|
+
}
|
|
18634
|
+
static getSettablePaths({ global = false } = {}) {
|
|
18635
|
+
return {
|
|
18636
|
+
relativeDirPath: global ? REASONIX_GLOBAL_DIR : ".",
|
|
18637
|
+
relativeFilePath: global ? REASONIX_GLOBAL_PERMISSIONS_FILE_NAME : REASONIX_PROJECT_PERMISSIONS_FILE_NAME
|
|
18638
|
+
};
|
|
18639
|
+
}
|
|
18640
|
+
/**
|
|
18641
|
+
* The config file also carries `[[plugins]]`, `[permissions]` rules from the
|
|
18642
|
+
* permissions feature and user-authored tables, so rulesync must never
|
|
18643
|
+
* delete it.
|
|
18644
|
+
*/
|
|
18645
|
+
isDeletable() {
|
|
18646
|
+
return false;
|
|
18647
|
+
}
|
|
18648
|
+
toRulesyncIgnore() {
|
|
18649
|
+
const rulesyncPatterns = this.patterns.filter((pattern) => isReadDenyEntry(pattern)).map((pattern) => pattern.slice(5, -1)).filter((pattern) => pattern.length > 0);
|
|
18650
|
+
return new RulesyncIgnore({
|
|
18651
|
+
outputRoot: this.outputRoot,
|
|
18652
|
+
relativeDirPath: RulesyncIgnore.getSettablePaths().recommended.relativeDirPath,
|
|
18653
|
+
relativeFilePath: RulesyncIgnore.getSettablePaths().recommended.relativeFilePath,
|
|
18654
|
+
fileContent: rulesyncPatterns.join("\n")
|
|
18655
|
+
});
|
|
18656
|
+
}
|
|
18657
|
+
static async fromRulesyncIgnore({ outputRoot = process.cwd(), rulesyncIgnore, global = false }) {
|
|
18658
|
+
const readDenies = rulesyncIgnore.getFileContent().split(/\r?\n|\r/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#")).map((pattern) => buildReadDenyEntry(pattern));
|
|
18659
|
+
const paths = this.getSettablePaths({ global });
|
|
18660
|
+
const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
18661
|
+
const existingDocument = parseSharedConfig({
|
|
18662
|
+
format: "toml",
|
|
18663
|
+
fileContent: await readFileContentOrNull(filePath) ?? "",
|
|
18664
|
+
filePath
|
|
18665
|
+
});
|
|
18666
|
+
const document = withoutEmptyPermissions(applyIgnoreReadDenies({
|
|
18667
|
+
settings: asClaudeStyleSettings(existingDocument),
|
|
18668
|
+
readDenies
|
|
18669
|
+
}));
|
|
18670
|
+
return new ReasonixIgnore({
|
|
18671
|
+
outputRoot,
|
|
18672
|
+
relativeDirPath: paths.relativeDirPath,
|
|
18673
|
+
relativeFilePath: paths.relativeFilePath,
|
|
18674
|
+
fileContent: stringifySharedConfig({
|
|
18675
|
+
format: "toml",
|
|
18676
|
+
document
|
|
18677
|
+
}),
|
|
18678
|
+
validate: true,
|
|
18679
|
+
global
|
|
18680
|
+
});
|
|
18681
|
+
}
|
|
18682
|
+
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
18683
|
+
const paths = this.getSettablePaths({ global });
|
|
18684
|
+
const fileContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "";
|
|
18685
|
+
return new ReasonixIgnore({
|
|
18686
|
+
outputRoot,
|
|
18687
|
+
relativeDirPath: paths.relativeDirPath,
|
|
18688
|
+
relativeFilePath: paths.relativeFilePath,
|
|
18689
|
+
fileContent,
|
|
18690
|
+
validate,
|
|
18691
|
+
global
|
|
18692
|
+
});
|
|
18693
|
+
}
|
|
18694
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
18695
|
+
return new ReasonixIgnore({
|
|
18696
|
+
outputRoot,
|
|
18697
|
+
relativeDirPath,
|
|
18698
|
+
relativeFilePath,
|
|
18699
|
+
fileContent: "",
|
|
18700
|
+
validate: false,
|
|
18701
|
+
global
|
|
18702
|
+
});
|
|
18703
|
+
}
|
|
18704
|
+
};
|
|
18705
|
+
//#endregion
|
|
17523
18706
|
//#region src/features/ignore/roo-ignore.ts
|
|
17524
18707
|
/**
|
|
17525
18708
|
* RooIgnore represents ignore patterns for the Roo Code AI coding assistant.
|
|
@@ -17677,6 +18860,14 @@ const ZED_GLOBAL_WIN32_DIR = join("AppData", "Roaming", "Zed");
|
|
|
17677
18860
|
function getZedGlobalDir() {
|
|
17678
18861
|
return process.platform === "win32" ? ZED_GLOBAL_WIN32_DIR : ZED_GLOBAL_DIR;
|
|
17679
18862
|
}
|
|
18863
|
+
/**
|
|
18864
|
+
* The global config dir of the OTHER platform. `getZedGlobalDir()` resolves one
|
|
18865
|
+
* spelling per platform, but the shared-write derivation (and the gateway
|
|
18866
|
+
* ownership table it is checked against) must know both on every platform.
|
|
18867
|
+
*/
|
|
18868
|
+
function getZedOtherPlatformGlobalDir() {
|
|
18869
|
+
return process.platform === "win32" ? ZED_GLOBAL_DIR : ZED_GLOBAL_WIN32_DIR;
|
|
18870
|
+
}
|
|
17680
18871
|
const ZED_SETTINGS_FILE_NAME = "settings.json";
|
|
17681
18872
|
const ZED_RULE_FILE_NAME = ".rules";
|
|
17682
18873
|
const ZED_GLOBAL_RULE_FILE_NAME = "AGENTS.md";
|
|
@@ -17689,12 +18880,20 @@ var ZedIgnore = class ZedIgnore extends ToolIgnore {
|
|
|
17689
18880
|
const jsonValue = JSON.parse(this.fileContent);
|
|
17690
18881
|
this.patterns = jsonValue.private_files ?? [];
|
|
17691
18882
|
}
|
|
17692
|
-
static getSettablePaths() {
|
|
18883
|
+
static getSettablePaths({ global = false } = {}) {
|
|
17693
18884
|
return {
|
|
17694
|
-
relativeDirPath: ZED_DIR,
|
|
18885
|
+
relativeDirPath: global ? getZedGlobalDir() : ZED_DIR,
|
|
17695
18886
|
relativeFilePath: ZED_SETTINGS_FILE_NAME
|
|
17696
18887
|
};
|
|
17697
18888
|
}
|
|
18889
|
+
/** @see getZedOtherPlatformGlobalDir */
|
|
18890
|
+
static getExtraSharedWritePaths({ global = false } = {}) {
|
|
18891
|
+
if (!global) return [];
|
|
18892
|
+
return [{
|
|
18893
|
+
relativeDirPath: getZedOtherPlatformGlobalDir(),
|
|
18894
|
+
relativeFilePath: ZED_SETTINGS_FILE_NAME
|
|
18895
|
+
}];
|
|
18896
|
+
}
|
|
17698
18897
|
/**
|
|
17699
18898
|
* ZedIgnore uses settings.json which is a user-managed config file.
|
|
17700
18899
|
* It should not be deleted by rulesync.
|
|
@@ -17705,48 +18904,53 @@ var ZedIgnore = class ZedIgnore extends ToolIgnore {
|
|
|
17705
18904
|
toRulesyncIgnore() {
|
|
17706
18905
|
const fileContent = this.patterns.filter((pattern) => pattern.length > 0).join("\n");
|
|
17707
18906
|
return new RulesyncIgnore({
|
|
17708
|
-
outputRoot:
|
|
18907
|
+
outputRoot: ".",
|
|
17709
18908
|
relativeDirPath: RulesyncIgnore.getSettablePaths().recommended.relativeDirPath,
|
|
17710
18909
|
relativeFilePath: RulesyncIgnore.getSettablePaths().recommended.relativeFilePath,
|
|
17711
18910
|
fileContent
|
|
17712
18911
|
});
|
|
17713
18912
|
}
|
|
17714
|
-
static async fromRulesyncIgnore({ outputRoot = process.cwd(), rulesyncIgnore }) {
|
|
18913
|
+
static async fromRulesyncIgnore({ outputRoot = process.cwd(), rulesyncIgnore, global = false }) {
|
|
17715
18914
|
const patterns = rulesyncIgnore.getFileContent().split(/\r?\n|\r/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
|
|
17716
|
-
const
|
|
18915
|
+
const paths = this.getSettablePaths({ global });
|
|
18916
|
+
const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
17717
18917
|
const existingFileContent = await fileExists(filePath) ? await readFileContent(filePath) : "{}";
|
|
17718
|
-
const
|
|
18918
|
+
const managedPatterns = patterns.length > 0 ? [...new Set(patterns)].toSorted() : void 0;
|
|
17719
18919
|
return new ZedIgnore({
|
|
17720
18920
|
outputRoot,
|
|
17721
|
-
relativeDirPath:
|
|
17722
|
-
relativeFilePath:
|
|
18921
|
+
relativeDirPath: paths.relativeDirPath,
|
|
18922
|
+
relativeFilePath: paths.relativeFilePath,
|
|
17723
18923
|
fileContent: applySharedConfigPatch({
|
|
17724
|
-
fileKey: sharedConfigFileKey(
|
|
18924
|
+
fileKey: sharedConfigFileKey(paths),
|
|
17725
18925
|
feature: "ignore",
|
|
17726
18926
|
existingContent: existingFileContent,
|
|
17727
|
-
patch: { private_files:
|
|
18927
|
+
patch: { private_files: managedPatterns },
|
|
17728
18928
|
filePath
|
|
17729
18929
|
}),
|
|
17730
|
-
validate: true
|
|
18930
|
+
validate: true,
|
|
18931
|
+
global
|
|
17731
18932
|
});
|
|
17732
18933
|
}
|
|
17733
|
-
static async fromFile({ outputRoot = process.cwd(), validate = true }) {
|
|
17734
|
-
const
|
|
18934
|
+
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
18935
|
+
const paths = this.getSettablePaths({ global });
|
|
18936
|
+
const fileContent = await readFileContent(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath));
|
|
17735
18937
|
return new ZedIgnore({
|
|
17736
18938
|
outputRoot,
|
|
17737
|
-
relativeDirPath:
|
|
17738
|
-
relativeFilePath:
|
|
18939
|
+
relativeDirPath: paths.relativeDirPath,
|
|
18940
|
+
relativeFilePath: paths.relativeFilePath,
|
|
17739
18941
|
fileContent,
|
|
17740
|
-
validate
|
|
18942
|
+
validate,
|
|
18943
|
+
global
|
|
17741
18944
|
});
|
|
17742
18945
|
}
|
|
17743
|
-
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
18946
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
17744
18947
|
return new ZedIgnore({
|
|
17745
18948
|
outputRoot,
|
|
17746
18949
|
relativeDirPath,
|
|
17747
18950
|
relativeFilePath,
|
|
17748
18951
|
fileContent: "{}",
|
|
17749
|
-
validate: false
|
|
18952
|
+
validate: false,
|
|
18953
|
+
global
|
|
17750
18954
|
});
|
|
17751
18955
|
}
|
|
17752
18956
|
};
|
|
@@ -17768,6 +18972,7 @@ const toolIgnoreFactories = /* @__PURE__ */ new Map([
|
|
|
17768
18972
|
["kiro-cli", { class: KiroIgnore }],
|
|
17769
18973
|
["kiro-ide", { class: KiroIgnore }],
|
|
17770
18974
|
["qwencode", { class: QwencodeIgnore }],
|
|
18975
|
+
["reasonix", { class: ReasonixIgnore }],
|
|
17771
18976
|
["roo", { class: RooIgnore }],
|
|
17772
18977
|
["devin", { class: DevinIgnore }],
|
|
17773
18978
|
["vibe", { class: VibeIgnore }],
|
|
@@ -17778,7 +18983,9 @@ const ignoreProcessorToolTargets = [...toolIgnoreFactories.keys()];
|
|
|
17778
18983
|
const ignoreProcessorGlobalToolTargets = [
|
|
17779
18984
|
"kiro",
|
|
17780
18985
|
"kiro-cli",
|
|
17781
|
-
"kiro-ide"
|
|
18986
|
+
"kiro-ide",
|
|
18987
|
+
"reasonix",
|
|
18988
|
+
"zed"
|
|
17782
18989
|
];
|
|
17783
18990
|
const defaultGetFactory$4 = (target) => {
|
|
17784
18991
|
const factory = toolIgnoreFactories.get(target);
|
|
@@ -18697,8 +19904,20 @@ const RULESYNC_TO_CODEX_FIELD_MAP = {
|
|
|
18697
19904
|
disabledTools: "disabled_tools",
|
|
18698
19905
|
envVars: "env_vars"
|
|
18699
19906
|
};
|
|
19907
|
+
const RULESYNC_TO_CODEX_SCALAR_FIELD_MAP = { experimentalEnvironment: "experimental_environment" };
|
|
19908
|
+
const CODEX_TO_RULESYNC_SCALAR_FIELD_MAP = Object.fromEntries(Object.entries(RULESYNC_TO_CODEX_SCALAR_FIELD_MAP).map(([canonical, codex]) => [codex, canonical]));
|
|
18700
19909
|
const MAX_REMOVE_EMPTY_ENTRIES_DEPTH$1 = 32;
|
|
18701
19910
|
/**
|
|
19911
|
+
* `env_vars` entries are either a bare variable name or `{ name, source }`,
|
|
19912
|
+
* where `source = "remote"` reads the variable from the remote executor
|
|
19913
|
+
* environment. The other renamed keys (`enabled_tools`, `disabled_tools`) stay
|
|
19914
|
+
* plain string arrays, so the widened check applies to `env_vars` only.
|
|
19915
|
+
* @see https://learn.chatgpt.com/docs/extend/mcp
|
|
19916
|
+
*/
|
|
19917
|
+
function isValidRenamedArray(key, value) {
|
|
19918
|
+
return key === "env_vars" || key === "envVars" ? isEnvVarEntryArray(value) : isStringArray$1(value);
|
|
19919
|
+
}
|
|
19920
|
+
/**
|
|
18702
19921
|
* Translate a server's `oauth` table from the canonical rulesync shape (Claude
|
|
18703
19922
|
* Code style camelCase) into the shape Codex CLI understands. Codex expects the
|
|
18704
19923
|
* OAuth client id under snake_case `client_id`; without it `codex mcp login`
|
|
@@ -18758,8 +19977,12 @@ function convertFromCodexFormat(codexMcp) {
|
|
|
18758
19977
|
} else if (key === "oauth" && isRecord(value)) converted[key] = mapOauthFromCodex(value);
|
|
18759
19978
|
else if (Object.hasOwn(CODEX_TO_RULESYNC_FIELD_MAP, key)) {
|
|
18760
19979
|
const mappedKey = CODEX_TO_RULESYNC_FIELD_MAP[key];
|
|
18761
|
-
if (mappedKey) if (
|
|
19980
|
+
if (mappedKey) if (isValidRenamedArray(key, value)) converted[mappedKey] = value;
|
|
18762
19981
|
else warnWithFallback(void 0, `Ignored malformed array for ${key} in MCP server ${name}`);
|
|
19982
|
+
} else if (Object.hasOwn(CODEX_TO_RULESYNC_SCALAR_FIELD_MAP, key)) {
|
|
19983
|
+
const mappedKey = CODEX_TO_RULESYNC_SCALAR_FIELD_MAP[key];
|
|
19984
|
+
if (mappedKey) if (typeof value === "string") converted[mappedKey] = value;
|
|
19985
|
+
else warnWithFallback(void 0, `Ignored malformed value for ${key} in MCP server ${name}: expected a string`);
|
|
18763
19986
|
} else converted[key] = value;
|
|
18764
19987
|
}
|
|
18765
19988
|
result[name] = converted;
|
|
@@ -18781,8 +20004,12 @@ function convertToCodexFormat(mcpServers) {
|
|
|
18781
20004
|
} else if (key === "oauth" && isRecord(value)) converted[key] = mapOauthToCodex(value);
|
|
18782
20005
|
else if (Object.hasOwn(RULESYNC_TO_CODEX_FIELD_MAP, key)) {
|
|
18783
20006
|
const mappedKey = RULESYNC_TO_CODEX_FIELD_MAP[key];
|
|
18784
|
-
if (mappedKey) if (
|
|
20007
|
+
if (mappedKey) if (isValidRenamedArray(key, value)) converted[mappedKey] = value;
|
|
18785
20008
|
else warnWithFallback(void 0, `[CodexCliMcp] Skipping invalid value type for mapped key '${key}': expected string array, got ${typeof value}`);
|
|
20009
|
+
} else if (Object.hasOwn(RULESYNC_TO_CODEX_SCALAR_FIELD_MAP, key)) {
|
|
20010
|
+
const mappedKey = RULESYNC_TO_CODEX_SCALAR_FIELD_MAP[key];
|
|
20011
|
+
if (mappedKey) if (typeof value === "string") converted[mappedKey] = value;
|
|
20012
|
+
else warnWithFallback(void 0, `[CodexCliMcp] Skipping invalid value type for mapped key '${key}': expected string, got ${typeof value}`);
|
|
18786
20013
|
} else converted[key] = value;
|
|
18787
20014
|
}
|
|
18788
20015
|
const previousName = originalNames.get(codexName);
|
|
@@ -18853,7 +20080,9 @@ var CodexcliMcp = class CodexcliMcp extends ToolMcp {
|
|
|
18853
20080
|
const rawServer = isRecord(rawMcpServers) ? rawMcpServers[serverName] : void 0;
|
|
18854
20081
|
return [serverName, {
|
|
18855
20082
|
...serverConfig,
|
|
18856
|
-
...isRecord(rawServer) &&
|
|
20083
|
+
...isRecord(rawServer) && isEnvVarEntryArray(rawServer.envVars) ? { envVars: rawServer.envVars } : {},
|
|
20084
|
+
...isRecord(rawServer) && typeof rawServer.experimental_environment === "string" ? { experimentalEnvironment: rawServer.experimental_environment } : {},
|
|
20085
|
+
...isRecord(rawServer) && typeof rawServer.experimentalEnvironment === "string" ? { experimentalEnvironment: rawServer.experimentalEnvironment } : {}
|
|
18857
20086
|
}];
|
|
18858
20087
|
})));
|
|
18859
20088
|
const filteredMcpServers = this.removeEmptyEntries(converted);
|
|
@@ -18924,9 +20153,6 @@ var CodexcliMcp = class CodexcliMcp extends ToolMcp {
|
|
|
18924
20153
|
};
|
|
18925
20154
|
//#endregion
|
|
18926
20155
|
//#region src/features/mcp/copilot-mcp.ts
|
|
18927
|
-
function convertToCopilotFormat(mcpServers) {
|
|
18928
|
-
return { servers: mcpServers };
|
|
18929
|
-
}
|
|
18930
20156
|
function convertFromCopilotFormat(copilotConfig) {
|
|
18931
20157
|
return copilotConfig.servers ?? {};
|
|
18932
20158
|
}
|
|
@@ -18955,13 +20181,21 @@ var CopilotMcp = class CopilotMcp extends ToolMcp {
|
|
|
18955
20181
|
validate
|
|
18956
20182
|
});
|
|
18957
20183
|
}
|
|
18958
|
-
static fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true }) {
|
|
18959
|
-
const
|
|
20184
|
+
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true }) {
|
|
20185
|
+
const paths = this.getSettablePaths();
|
|
20186
|
+
const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
20187
|
+
const existingContent = await readFileContentOrNull(filePath) ?? "{}";
|
|
18960
20188
|
return new CopilotMcp({
|
|
18961
20189
|
outputRoot,
|
|
18962
|
-
relativeDirPath:
|
|
18963
|
-
relativeFilePath:
|
|
18964
|
-
fileContent:
|
|
20190
|
+
relativeDirPath: paths.relativeDirPath,
|
|
20191
|
+
relativeFilePath: paths.relativeFilePath,
|
|
20192
|
+
fileContent: applySharedConfigPatch({
|
|
20193
|
+
fileKey: sharedConfigFileKey(paths),
|
|
20194
|
+
feature: "mcp",
|
|
20195
|
+
existingContent,
|
|
20196
|
+
patch: { servers: rulesyncMcp.getMcpServers() },
|
|
20197
|
+
filePath
|
|
20198
|
+
}),
|
|
18965
20199
|
validate
|
|
18966
20200
|
});
|
|
18967
20201
|
}
|
|
@@ -20347,7 +21581,7 @@ var HermesagentMcp = class HermesagentMcp extends ToolMcp {
|
|
|
20347
21581
|
}), isRecord(this.config.mcp_servers) ? this.config.mcp_servers : {});
|
|
20348
21582
|
this.config = merged;
|
|
20349
21583
|
super.setFileContent(applySharedConfigPatch({
|
|
20350
|
-
fileKey:
|
|
21584
|
+
fileKey: getHermesagentConfigSharedFileKey({ global: this.global }),
|
|
20351
21585
|
feature: "mcp",
|
|
20352
21586
|
existingContent: fileContent,
|
|
20353
21587
|
patch: { mcp_servers: merged.mcp_servers }
|
|
@@ -20365,6 +21599,13 @@ var HermesagentMcp = class HermesagentMcp extends ToolMcp {
|
|
|
20365
21599
|
relativeFilePath: HERMESAGENT_CONFIG_FILE_NAME
|
|
20366
21600
|
};
|
|
20367
21601
|
}
|
|
21602
|
+
/**
|
|
21603
|
+
* `config.yaml` under every spelling the global profile root can take.
|
|
21604
|
+
* @see getHermesagentSharedConfigWritePaths
|
|
21605
|
+
*/
|
|
21606
|
+
static getExtraSharedWritePaths() {
|
|
21607
|
+
return getHermesagentSharedConfigWritePaths();
|
|
21608
|
+
}
|
|
20368
21609
|
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
20369
21610
|
if (!global) throw new Error(HERMESAGENT_GLOBAL_ONLY_MESSAGE);
|
|
20370
21611
|
const paths = this.getSettablePaths({ global });
|
|
@@ -20391,7 +21632,7 @@ var HermesagentMcp = class HermesagentMcp extends ToolMcp {
|
|
|
20391
21632
|
relativeDirPath: paths.relativeDirPath,
|
|
20392
21633
|
relativeFilePath: paths.relativeFilePath,
|
|
20393
21634
|
fileContent: applySharedConfigPatch({
|
|
20394
|
-
fileKey:
|
|
21635
|
+
fileKey: getHermesagentConfigSharedFileKey({ global }),
|
|
20395
21636
|
feature: "mcp",
|
|
20396
21637
|
existingContent: fileContent,
|
|
20397
21638
|
patch: { mcp_servers: merged.mcp_servers }
|
|
@@ -20644,7 +21885,7 @@ const KILO_TOGGLE_KEPT_KEYS = /* @__PURE__ */ new Set([
|
|
|
20644
21885
|
* impossible to start — no command, no URL — so it is written as the toggle it
|
|
20645
21886
|
* resembles rather than dropped, but the loss is said out loud.
|
|
20646
21887
|
*/
|
|
20647
|
-
function warnAboutToggleDroppedKeys(serverName, serverConfig, logger) {
|
|
21888
|
+
function warnAboutToggleDroppedKeys$1(serverName, serverConfig, logger) {
|
|
20648
21889
|
const dropped = Object.keys(serverConfig).filter((key) => !KILO_TOGGLE_KEPT_KEYS.has(key));
|
|
20649
21890
|
if (dropped.length === 0) return;
|
|
20650
21891
|
logger?.warn(`Kilo MCP: "${serverName}" declares no transport, so it is written as a toggle entry and ${dropped.toSorted().join(", ")} ${dropped.length === 1 ? "is" : "are"} dropped.`);
|
|
@@ -20657,7 +21898,7 @@ function convertServerToKiloFormat(serverName, serverConfig, existingEntry, logg
|
|
|
20657
21898
|
if (declaresNoTransport(serverConfig)) {
|
|
20658
21899
|
if (serverConfig.disabled === void 0) {
|
|
20659
21900
|
if (existingEntry !== void 0 && !isKiloTransportServer(existingEntry)) {
|
|
20660
|
-
warnAboutToggleDroppedKeys(serverName, serverConfig, logger);
|
|
21901
|
+
warnAboutToggleDroppedKeys$1(serverName, serverConfig, logger);
|
|
20661
21902
|
return existingEntry;
|
|
20662
21903
|
}
|
|
20663
21904
|
return warnAndSkipMcpServer({
|
|
@@ -20667,7 +21908,7 @@ function convertServerToKiloFormat(serverName, serverConfig, existingEntry, logg
|
|
|
20667
21908
|
logger
|
|
20668
21909
|
});
|
|
20669
21910
|
}
|
|
20670
|
-
warnAboutToggleDroppedKeys(serverName, serverConfig, logger);
|
|
21911
|
+
warnAboutToggleDroppedKeys$1(serverName, serverConfig, logger);
|
|
20671
21912
|
return { enabled: !serverConfig.disabled };
|
|
20672
21913
|
}
|
|
20673
21914
|
if (isRemoteMcpServer(serverConfig)) {
|
|
@@ -20852,9 +22093,12 @@ var KiloMcp = class KiloMcp extends ToolMcp {
|
|
|
20852
22093
|
* Merge a list of project rule file globs into the `instructions` array of the
|
|
20853
22094
|
* shared `kilo.jsonc` (or `kilo.json`) config, preserving every existing key
|
|
20854
22095
|
* (notably `mcp`/`tools` written by the MCP feature). In Kilo v7, files under
|
|
20855
|
-
* `.kilo/rules/` are NOT auto-loaded; they are only picked up
|
|
20856
|
-
* the `instructions` key. The
|
|
20857
|
-
*
|
|
22096
|
+
* a *project* `.kilo/rules/` are NOT auto-loaded; they are only picked up
|
|
22097
|
+
* when listed in the `instructions` key. (The home-scope `~/.kilo/rules/` is
|
|
22098
|
+
* different — the rules migrator's `globalRulesDirs()` walks it on every
|
|
22099
|
+
* config load — which is why `KiloRule` registers instructions in project
|
|
22100
|
+
* scope only.) The resulting `instructions` list is deduped and sorted for a
|
|
22101
|
+
* stable output.
|
|
20858
22102
|
*
|
|
20859
22103
|
* @see https://kilo.ai/docs/automate/mcp/using-in-kilo-code
|
|
20860
22104
|
*/
|
|
@@ -21067,7 +22311,7 @@ var KimiCodeMcpConfigToml = class KimiCodeMcpConfigToml extends ToolFile {
|
|
|
21067
22311
|
const existingContent = existing.content;
|
|
21068
22312
|
const existingSection = existing.mcp;
|
|
21069
22313
|
const fileContent = applySharedConfigPatch({
|
|
21070
|
-
fileKey:
|
|
22314
|
+
fileKey: getKimiCodeConfigSharedFileKey({ global: true }),
|
|
21071
22315
|
feature: "mcp",
|
|
21072
22316
|
existingContent,
|
|
21073
22317
|
patch: { mcp: {
|
|
@@ -21154,11 +22398,8 @@ var KimiCodeMcp = class KimiCodeMcp extends ToolMcp {
|
|
|
21154
22398
|
* derivation sees this feature as one of that file's writers — it is not a
|
|
21155
22399
|
* settable path, since the servers themselves live in `mcp.json`.
|
|
21156
22400
|
*/
|
|
21157
|
-
static getExtraSharedWritePaths(
|
|
21158
|
-
return
|
|
21159
|
-
relativeDirPath: getKimiCodeRelativeDirPath({ global: true }),
|
|
21160
|
-
relativeFilePath: KIMI_CODE_CONFIG_FILE_NAME
|
|
21161
|
-
}] : [];
|
|
22401
|
+
static getExtraSharedWritePaths() {
|
|
22402
|
+
return getKimiCodeSharedConfigWritePaths();
|
|
21162
22403
|
}
|
|
21163
22404
|
/**
|
|
21164
22405
|
* The `[mcp]` defaults live in the shared user `config.toml`, not in
|
|
@@ -21293,13 +22534,49 @@ const OpencodeMcpRemoteServerSchema = z.looseObject({
|
|
|
21293
22534
|
headers: z.optional(z.record(z.string(), z.string())),
|
|
21294
22535
|
enabled: z._default(z.boolean(), true)
|
|
21295
22536
|
});
|
|
21296
|
-
const
|
|
22537
|
+
const OPENCODE_PASSTHROUGH_SERVER_FIELDS = ["timeout", "oauth"];
|
|
22538
|
+
const OPENCODE_MCP_TRANSPORT_KEYS = [
|
|
22539
|
+
...Object.keys(OpencodeMcpLocalServerSchema.def.shape),
|
|
22540
|
+
...Object.keys(OpencodeMcpRemoteServerSchema.def.shape),
|
|
22541
|
+
...OPENCODE_PASSTHROUGH_SERVER_FIELDS
|
|
22542
|
+
].filter((key) => key !== "enabled");
|
|
22543
|
+
/**
|
|
22544
|
+
* A bare toggle entry: `{"enabled": <bool>}` with no transport of its own,
|
|
22545
|
+
* disabling a server another config layer defines. It is the third member of
|
|
22546
|
+
* OpenCode's own `mcp` union in the published schema, described in-source as
|
|
22547
|
+
* "the legacy `{ enabled: false }` form used to disable a server". Without this
|
|
22548
|
+
* arm the union rejects the entry and the whole MCP import aborts — taking
|
|
22549
|
+
* every valid server in the same file down with it.
|
|
22550
|
+
*
|
|
22551
|
+
* Loose, unlike the two transport arms, so a key OpenCode adds to a toggle
|
|
22552
|
+
* later does not bring that abort back — but refined to reject anything
|
|
22553
|
+
* carrying a key only a transport entry has. A plain loose arm would sit under
|
|
22554
|
+
* a malformed `local` or `remote` entry that happens to carry `enabled` and
|
|
22555
|
+
* swallow it, dropping its command, URL, or headers without a word instead of
|
|
22556
|
+
* failing the way it does today. Mirrors {@link KiloMcpToggleSchema}.
|
|
22557
|
+
*
|
|
22558
|
+
* @see https://opencode.ai/config.json
|
|
22559
|
+
*/
|
|
22560
|
+
const OpencodeMcpToggleSchema = z.looseObject({ enabled: z.boolean() }).check(refine((entry) => OPENCODE_MCP_TRANSPORT_KEYS.every((key) => !(key in entry)), "not a valid OpenCode MCP server: expected a local server ({type: \"local\", command: [...]}), a remote server ({type: \"remote\", url: \"...\"}), or a bare toggle ({enabled: <bool>}) carrying no field of either"));
|
|
22561
|
+
const OpencodeMcpServerSchema = z.union([
|
|
22562
|
+
OpencodeMcpLocalServerSchema,
|
|
22563
|
+
OpencodeMcpRemoteServerSchema,
|
|
22564
|
+
OpencodeMcpToggleSchema
|
|
22565
|
+
]);
|
|
21297
22566
|
const OpencodeConfigSchema = z.looseObject({
|
|
21298
22567
|
$schema: z.optional(z.string()),
|
|
21299
22568
|
mcp: z.optional(z.record(z.string(), OpencodeMcpServerSchema)),
|
|
21300
22569
|
tools: z.optional(z.record(z.string(), z.boolean()))
|
|
21301
22570
|
});
|
|
21302
22571
|
/**
|
|
22572
|
+
* Tell the two transport arms from a toggle entry. Both carry a `type` literal
|
|
22573
|
+
* and a toggle never does — the schema refuses one that tries — but the toggle
|
|
22574
|
+
* arm is loose, so its index signature hides that from `in` narrowing.
|
|
22575
|
+
*/
|
|
22576
|
+
function isOpencodeTransportServer(server) {
|
|
22577
|
+
return server.type === "local" || server.type === "remote";
|
|
22578
|
+
}
|
|
22579
|
+
/**
|
|
21303
22580
|
* Convert OpenCode native format back to standard MCP format
|
|
21304
22581
|
* - type: "local" -> "stdio", "remote" -> "sse"
|
|
21305
22582
|
* - command (array) -> command (first element) + args (rest)
|
|
@@ -21324,19 +22601,31 @@ function convertFromOpencodeFormat(opencodeMcp, tools) {
|
|
|
21324
22601
|
...convertOpencodeServers(opencodeMcp, tools)
|
|
21325
22602
|
};
|
|
21326
22603
|
}
|
|
22604
|
+
/** Split the shared top-level `tools` map into this server's own two lists. */
|
|
22605
|
+
function splitOpencodeServerTools(serverName, tools) {
|
|
22606
|
+
const enabledTools = [];
|
|
22607
|
+
const disabledTools = [];
|
|
22608
|
+
const prefix = `${serverName}_`;
|
|
22609
|
+
for (const [toolName, enabled] of Object.entries(tools ?? {})) {
|
|
22610
|
+
if (!toolName.startsWith(prefix)) continue;
|
|
22611
|
+
const toolSuffix = toolName.slice(prefix.length);
|
|
22612
|
+
(enabled ? enabledTools : disabledTools).push(toolSuffix);
|
|
22613
|
+
}
|
|
22614
|
+
return {
|
|
22615
|
+
enabledTools,
|
|
22616
|
+
disabledTools
|
|
22617
|
+
};
|
|
22618
|
+
}
|
|
21327
22619
|
function convertOpencodeServers(opencodeMcp, tools) {
|
|
21328
22620
|
return Object.fromEntries(Object.entries(opencodeMcp).map(([serverName, serverConfig]) => {
|
|
21329
22621
|
const extraFields = Object.fromEntries(Object.entries(serverConfig).filter(([key]) => !OPENCODE_KNOWN_SERVER_KEYS.has(key)));
|
|
21330
|
-
const enabledTools =
|
|
21331
|
-
|
|
21332
|
-
|
|
21333
|
-
|
|
21334
|
-
|
|
21335
|
-
|
|
21336
|
-
|
|
21337
|
-
else disabledTools.push(toolSuffix);
|
|
21338
|
-
}
|
|
21339
|
-
}
|
|
22622
|
+
const { enabledTools, disabledTools } = splitOpencodeServerTools(serverName, tools);
|
|
22623
|
+
if (!isOpencodeTransportServer(serverConfig)) return [serverName, {
|
|
22624
|
+
...extraFields,
|
|
22625
|
+
disabled: serverConfig.enabled === false,
|
|
22626
|
+
...enabledTools.length > 0 && { enabledTools },
|
|
22627
|
+
...disabledTools.length > 0 && { disabledTools }
|
|
22628
|
+
}];
|
|
21340
22629
|
if (serverConfig.type === "remote") return [serverName, {
|
|
21341
22630
|
...extraFields,
|
|
21342
22631
|
type: "sse",
|
|
@@ -21366,7 +22655,20 @@ function convertOpencodeServers(opencodeMcp, tools) {
|
|
|
21366
22655
|
}];
|
|
21367
22656
|
}));
|
|
21368
22657
|
}
|
|
21369
|
-
const
|
|
22658
|
+
const OPENCODE_TOGGLE_KEPT_KEYS = /* @__PURE__ */ new Set([
|
|
22659
|
+
"disabled",
|
|
22660
|
+
"enabledTools",
|
|
22661
|
+
"disabledTools"
|
|
22662
|
+
]);
|
|
22663
|
+
/**
|
|
22664
|
+
* Warn about the fields a transport-less server loses by being written as a
|
|
22665
|
+
* bare toggle, so the drop is never silent.
|
|
22666
|
+
*/
|
|
22667
|
+
function warnAboutToggleDroppedKeys(serverName, serverConfig, logger) {
|
|
22668
|
+
const dropped = Object.keys(serverConfig).filter((key) => !OPENCODE_TOGGLE_KEPT_KEYS.has(key));
|
|
22669
|
+
if (dropped.length === 0) return;
|
|
22670
|
+
logger?.warn(`OpenCode MCP: "${serverName}" declares no transport, so it is written as a toggle entry and ${dropped.toSorted().join(", ")} ${dropped.length === 1 ? "is" : "are"} dropped.`);
|
|
22671
|
+
}
|
|
21370
22672
|
/**
|
|
21371
22673
|
* Convert standard MCP format to OpenCode native format
|
|
21372
22674
|
* - type: "stdio" -> "local", "sse"/"http" -> "remote"
|
|
@@ -21376,11 +22678,27 @@ const OPENCODE_PASSTHROUGH_SERVER_FIELDS = ["timeout", "oauth"];
|
|
|
21376
22678
|
* - enabledTools/disabledTools -> top-level tools map (with server name prefix)
|
|
21377
22679
|
* - OpenCode-supported extras (timeout, oauth) -> passed through verbatim
|
|
21378
22680
|
*/
|
|
21379
|
-
function convertServerToOpencodeFormat(serverName, serverConfig, logger) {
|
|
22681
|
+
function convertServerToOpencodeFormat(serverName, serverConfig, existingEntry, logger) {
|
|
21380
22682
|
const serverRecord = serverConfig;
|
|
21381
22683
|
const passthrough = {};
|
|
21382
22684
|
for (const key of OPENCODE_PASSTHROUGH_SERVER_FIELDS) if (serverRecord[key] !== void 0) passthrough[key] = serverRecord[key];
|
|
21383
22685
|
const enabled = serverConfig.disabled !== void 0 ? !serverConfig.disabled : true;
|
|
22686
|
+
if (declaresNoTransport(serverConfig)) {
|
|
22687
|
+
if (serverConfig.disabled === void 0) {
|
|
22688
|
+
if (existingEntry !== void 0 && !isOpencodeTransportServer(existingEntry)) {
|
|
22689
|
+
warnAboutToggleDroppedKeys(serverName, serverConfig, logger);
|
|
22690
|
+
return existingEntry;
|
|
22691
|
+
}
|
|
22692
|
+
return warnAndSkipMcpServer({
|
|
22693
|
+
toolName: "OpenCode",
|
|
22694
|
+
serverName,
|
|
22695
|
+
reason: "no transport and no enabled state, so there is nothing to toggle",
|
|
22696
|
+
logger
|
|
22697
|
+
});
|
|
22698
|
+
}
|
|
22699
|
+
warnAboutToggleDroppedKeys(serverName, serverConfig, logger);
|
|
22700
|
+
return { enabled };
|
|
22701
|
+
}
|
|
21384
22702
|
if (isRemoteMcpServer(serverConfig)) {
|
|
21385
22703
|
const url = resolveRemoteMcpUrl(serverConfig);
|
|
21386
22704
|
if (url === void 0) return warnAndSkipMcpServer({
|
|
@@ -21401,7 +22719,7 @@ function convertServerToOpencodeFormat(serverName, serverConfig, logger) {
|
|
|
21401
22719
|
if (commandArray.length === 0) return warnAndSkipMcpServer({
|
|
21402
22720
|
toolName: "OpenCode",
|
|
21403
22721
|
serverName,
|
|
21404
|
-
reason:
|
|
22722
|
+
reason: "a local transport but no command",
|
|
21405
22723
|
logger
|
|
21406
22724
|
});
|
|
21407
22725
|
return {
|
|
@@ -21413,11 +22731,11 @@ function convertServerToOpencodeFormat(serverName, serverConfig, logger) {
|
|
|
21413
22731
|
...serverConfig.cwd && { cwd: serverConfig.cwd }
|
|
21414
22732
|
};
|
|
21415
22733
|
}
|
|
21416
|
-
function convertToOpencodeFormat(mcpServers, logger) {
|
|
22734
|
+
function convertToOpencodeFormat(mcpServers, existingMcp, logger) {
|
|
21417
22735
|
const tools = {};
|
|
21418
22736
|
return {
|
|
21419
22737
|
mcp: Object.fromEntries(Object.entries(mcpServers).map(([serverName, serverConfig]) => {
|
|
21420
|
-
const converted = convertServerToOpencodeFormat(serverName, serverConfig, logger);
|
|
22738
|
+
const converted = convertServerToOpencodeFormat(serverName, serverConfig, existingMcp[serverName], logger);
|
|
21421
22739
|
if (serverConfig.enabledTools) for (const tool of serverConfig.enabledTools) tools[`${serverName}_${tool}`] = true;
|
|
21422
22740
|
if (serverConfig.disabledTools) for (const tool of serverConfig.disabledTools) tools[`${serverName}_${tool}`] = false;
|
|
21423
22741
|
return converted === null ? null : [serverName, converted];
|
|
@@ -21487,10 +22805,12 @@ var OpencodeMcp = class OpencodeMcp extends ToolMcp {
|
|
|
21487
22805
|
fileContent = await readFileContentOrNull(jsonPath);
|
|
21488
22806
|
if (fileContent) relativeFilePath = OPENCODE_JSON_FILE_NAME;
|
|
21489
22807
|
}
|
|
21490
|
-
const
|
|
22808
|
+
const transformedServers = convertEnvVarRefsToToolFormat({
|
|
21491
22809
|
mcpServers: rulesyncMcp.getMcpServers(),
|
|
21492
22810
|
replacement: "{env:$1}"
|
|
21493
|
-
})
|
|
22811
|
+
});
|
|
22812
|
+
const existingMcp = OpencodeConfigSchema.safeParse(parse(fileContent || "{}"));
|
|
22813
|
+
const { mcp: convertedMcp, tools: mcpTools } = convertToOpencodeFormat(transformedServers, (existingMcp.success ? existingMcp.data.mcp : void 0) ?? {}, logger);
|
|
21494
22814
|
return new OpencodeMcp({
|
|
21495
22815
|
outputRoot,
|
|
21496
22816
|
relativeDirPath: basePaths.relativeDirPath,
|
|
@@ -22715,16 +24035,11 @@ var ZedMcp = class ZedMcp extends ToolMcp {
|
|
|
22715
24035
|
relativeFilePath: ZED_SETTINGS_FILE_NAME
|
|
22716
24036
|
};
|
|
22717
24037
|
}
|
|
22718
|
-
/**
|
|
22719
|
-
* The global settings file of the OTHER platform: `getSettablePaths` resolves
|
|
22720
|
-
* `~/.config/zed` vs `%APPDATA%\Zed` per platform, but the shared-write
|
|
22721
|
-
* derivation (and the gateway ownership table it is checked against) must
|
|
22722
|
-
* know both spellings on every platform.
|
|
22723
|
-
*/
|
|
24038
|
+
/** @see getZedOtherPlatformGlobalDir */
|
|
22724
24039
|
static getExtraSharedWritePaths({ global = false } = {}) {
|
|
22725
24040
|
if (!global) return [];
|
|
22726
24041
|
return [{
|
|
22727
|
-
relativeDirPath:
|
|
24042
|
+
relativeDirPath: getZedOtherPlatformGlobalDir(),
|
|
22728
24043
|
relativeFilePath: ZED_SETTINGS_FILE_NAME
|
|
22729
24044
|
}];
|
|
22730
24045
|
}
|
|
@@ -23800,10 +25115,11 @@ function buildPermissionEntry$1(toolName, pattern) {
|
|
|
23800
25115
|
* file (global scope only). The file holds other CLI settings besides
|
|
23801
25116
|
* permissions, so it is never deleted.
|
|
23802
25117
|
*
|
|
23803
|
-
*
|
|
23804
|
-
* `toolPermission` (the global autonomy preset)
|
|
23805
|
-
*
|
|
23806
|
-
*
|
|
25118
|
+
* Four CLI-only autonomy/sandbox knobs outside the allow/ask/deny arrays —
|
|
25119
|
+
* `toolPermission` (the global autonomy preset), `enableTerminalSandbox`,
|
|
25120
|
+
* `artifactReviewPolicy` and `allowNonWorkspaceAccess` — are authored and
|
|
25121
|
+
* round-tripped through the `antigravity-cli` permissions override (see
|
|
25122
|
+
* `AntigravityCliPermissionsOverrideSchema`).
|
|
23807
25123
|
*/
|
|
23808
25124
|
var AntigravityCliPermissions = class AntigravityCliPermissions extends ToolPermissions {
|
|
23809
25125
|
constructor(params) {
|
|
@@ -23867,6 +25183,8 @@ var AntigravityCliPermissions = class AntigravityCliPermissions extends ToolPerm
|
|
|
23867
25183
|
const override = config["antigravity-cli"];
|
|
23868
25184
|
if (override?.toolPermission !== void 0) merged.toolPermission = override.toolPermission;
|
|
23869
25185
|
if (override?.enableTerminalSandbox !== void 0) merged.enableTerminalSandbox = override.enableTerminalSandbox;
|
|
25186
|
+
if (override?.artifactReviewPolicy !== void 0) merged.artifactReviewPolicy = override.artifactReviewPolicy;
|
|
25187
|
+
if (override?.allowNonWorkspaceAccess !== void 0) merged.allowNonWorkspaceAccess = override.allowNonWorkspaceAccess;
|
|
23870
25188
|
const fileContent = JSON.stringify(merged, null, 2);
|
|
23871
25189
|
return new AntigravityCliPermissions({
|
|
23872
25190
|
outputRoot,
|
|
@@ -23893,6 +25211,8 @@ var AntigravityCliPermissions = class AntigravityCliPermissions extends ToolPerm
|
|
|
23893
25211
|
const override = {};
|
|
23894
25212
|
if (typeof settings.toolPermission === "string") override.toolPermission = settings.toolPermission;
|
|
23895
25213
|
if (typeof settings.enableTerminalSandbox === "boolean") override.enableTerminalSandbox = settings.enableTerminalSandbox;
|
|
25214
|
+
if (typeof settings.artifactReviewPolicy === "string") override.artifactReviewPolicy = settings.artifactReviewPolicy;
|
|
25215
|
+
if (typeof settings.allowNonWorkspaceAccess === "boolean") override.allowNonWorkspaceAccess = settings.allowNonWorkspaceAccess;
|
|
23896
25216
|
const result = { ...config };
|
|
23897
25217
|
if (Object.keys(override).length > 0) result["antigravity-cli"] = override;
|
|
23898
25218
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
|
|
@@ -24696,12 +26016,57 @@ function parseClaudePermissionEntry(entry) {
|
|
|
24696
26016
|
};
|
|
24697
26017
|
}
|
|
24698
26018
|
/**
|
|
26019
|
+
* Claude Code's file permission checks match only `Edit(path)` and `Read(path)`
|
|
26020
|
+
* rules. A `Write(path)`, `NotebookEdit(path)` or `Glob(path)` rule "is accepted
|
|
26021
|
+
* but never matched by those checks, so Claude Code warns at startup for each
|
|
26022
|
+
* allow, deny, or ask rule in one of these unmatched forms" — so a canonical
|
|
26023
|
+
* `write`/`notebookedit`/`glob` rule with a pattern is emitted in the form the
|
|
26024
|
+
* docs prescribe instead. A tool-name rule with no path is unaffected: it
|
|
26025
|
+
* matches the tool everywhere and produces no warning.
|
|
26026
|
+
* @see https://code.claude.com/docs/en/permissions
|
|
26027
|
+
*/
|
|
26028
|
+
function isPlainRecord(value) {
|
|
26029
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
26030
|
+
}
|
|
26031
|
+
/**
|
|
26032
|
+
* Merge `patch` into `base`, recursing into plain objects so a sibling key at
|
|
26033
|
+
* any depth survives. Arrays and scalars are replaced, since a list the author
|
|
26034
|
+
* states is the list they mean.
|
|
26035
|
+
*/
|
|
26036
|
+
function deepMergeRecords(base, patch) {
|
|
26037
|
+
const merged = { ...base };
|
|
26038
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
26039
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
26040
|
+
const existing = merged[key];
|
|
26041
|
+
merged[key] = isPlainRecord(existing) && isPlainRecord(value) ? deepMergeRecords(existing, value) : value;
|
|
26042
|
+
}
|
|
26043
|
+
return merged;
|
|
26044
|
+
}
|
|
26045
|
+
const CLAUDE_PATH_RULE_ALIASES = {
|
|
26046
|
+
Write: "Edit",
|
|
26047
|
+
NotebookEdit: "Edit",
|
|
26048
|
+
Glob: "Read"
|
|
26049
|
+
};
|
|
26050
|
+
/**
|
|
24699
26051
|
* Build a Claude Code permission entry like "Bash(npm run *)".
|
|
24700
26052
|
* If the pattern is "*", returns just the tool name.
|
|
24701
26053
|
*/
|
|
24702
26054
|
function buildClaudePermissionEntry(toolName, pattern) {
|
|
24703
26055
|
if (pattern === "*") return toolName;
|
|
24704
|
-
return `${toolName}(${pattern})`;
|
|
26056
|
+
return `${CLAUDE_PATH_RULE_ALIASES[toolName] ?? toolName}(${pattern})`;
|
|
26057
|
+
}
|
|
26058
|
+
/**
|
|
26059
|
+
* The Claude tool names the canonical config manages. Deliberately the tool
|
|
26060
|
+
* names the categories map to and *not* the aliases a path rule is rewritten
|
|
26061
|
+
* to: claiming `Edit` because a `write` rule exists would sweep away the
|
|
26062
|
+
* `Read`/`Edit` entries the ignore feature and the user wrote in the same file.
|
|
26063
|
+
* The rewritten entries are still rulesync's to place — `applyPermissions`
|
|
26064
|
+
* replaces an entry this run emits wherever it currently sits — and the
|
|
26065
|
+
* original name stays claimed so an entry an older rulesync wrote in the warned
|
|
26066
|
+
* form is cleaned up on the next generate.
|
|
26067
|
+
*/
|
|
26068
|
+
function managedClaudeToolNames(config) {
|
|
26069
|
+
return new Set(Object.keys(config.permission).map((category) => toClaudeToolName(category)));
|
|
24705
26070
|
}
|
|
24706
26071
|
var ClaudecodePermissions = class ClaudecodePermissions extends ToolPermissions {
|
|
24707
26072
|
constructor(params) {
|
|
@@ -24741,7 +26106,10 @@ var ClaudecodePermissions = class ClaudecodePermissions extends ToolPermissions
|
|
|
24741
26106
|
throw new Error(`Failed to parse existing Claude settings at ${filePath}: ${formatError(error)}`, { cause: error });
|
|
24742
26107
|
}
|
|
24743
26108
|
const config = rulesyncPermissions.getJson();
|
|
24744
|
-
const { allow, ask, deny } = convertRulesyncToClaudePermissions(
|
|
26109
|
+
const { allow, ask, deny } = convertRulesyncToClaudePermissions({
|
|
26110
|
+
config,
|
|
26111
|
+
logger
|
|
26112
|
+
});
|
|
24745
26113
|
const overridePermissions = config.claudecode?.permissions;
|
|
24746
26114
|
if (overridePermissions && typeof overridePermissions === "object") {
|
|
24747
26115
|
const { allow: _a, ask: _k, deny: _d, ...nonListFields } = overridePermissions;
|
|
@@ -24750,7 +26118,9 @@ var ClaudecodePermissions = class ClaudecodePermissions extends ToolPermissions
|
|
|
24750
26118
|
...nonListFields
|
|
24751
26119
|
};
|
|
24752
26120
|
}
|
|
24753
|
-
const
|
|
26121
|
+
const overrideSandbox = config.claudecode?.sandbox;
|
|
26122
|
+
if (isPlainRecord(overrideSandbox)) settings.sandbox = deepMergeRecords(isPlainRecord(settings.sandbox) ? settings.sandbox : {}, overrideSandbox);
|
|
26123
|
+
const managedToolNames = managedClaudeToolNames(config);
|
|
24754
26124
|
const merged = applyPermissions({
|
|
24755
26125
|
settings,
|
|
24756
26126
|
managedToolNames,
|
|
@@ -24784,6 +26154,11 @@ var ClaudecodePermissions = class ClaudecodePermissions extends ToolPermissions
|
|
|
24784
26154
|
});
|
|
24785
26155
|
const { allow: _a, ask: _k, deny: _d, ...nonListFields } = permissions;
|
|
24786
26156
|
if (Object.keys(nonListFields).length > 0) config.claudecode = { permissions: nonListFields };
|
|
26157
|
+
const { sandbox } = settings;
|
|
26158
|
+
if (isPlainRecord(sandbox) && Object.keys(sandbox).length > 0) config.claudecode = {
|
|
26159
|
+
...config.claudecode,
|
|
26160
|
+
sandbox
|
|
26161
|
+
};
|
|
24787
26162
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(config, null, 2) });
|
|
24788
26163
|
}
|
|
24789
26164
|
validate() {
|
|
@@ -24805,14 +26180,18 @@ var ClaudecodePermissions = class ClaudecodePermissions extends ToolPermissions
|
|
|
24805
26180
|
/**
|
|
24806
26181
|
* Convert rulesync permissions config to Claude Code allow/ask/deny arrays.
|
|
24807
26182
|
*/
|
|
24808
|
-
function convertRulesyncToClaudePermissions(config) {
|
|
26183
|
+
function convertRulesyncToClaudePermissions({ config, logger }) {
|
|
24809
26184
|
const allow = [];
|
|
24810
26185
|
const ask = [];
|
|
24811
26186
|
const deny = [];
|
|
26187
|
+
const actionByEntry = /* @__PURE__ */ new Map();
|
|
24812
26188
|
for (const [category, rules] of Object.entries(config.permission)) {
|
|
24813
26189
|
const claudeToolName = toClaudeToolName(category);
|
|
24814
26190
|
for (const [pattern, action] of Object.entries(rules)) {
|
|
24815
26191
|
const entry = buildClaudePermissionEntry(claudeToolName, pattern);
|
|
26192
|
+
const previous = actionByEntry.get(entry);
|
|
26193
|
+
if (previous !== void 0 && previous !== action) logger?.warn(`Claude Code permissions: rules from different categories both resolve to "${entry}" with conflicting actions (${previous} and ${action}). Both are written; Claude Code applies deny first, then ask, then allow.`);
|
|
26194
|
+
actionByEntry.set(entry, action);
|
|
24816
26195
|
switch (action) {
|
|
24817
26196
|
case "allow":
|
|
24818
26197
|
allow.push(entry);
|
|
@@ -25625,19 +27004,24 @@ function mapBashActionToDecision(action) {
|
|
|
25625
27004
|
//#endregion
|
|
25626
27005
|
//#region src/features/permissions/copilot-permissions.ts
|
|
25627
27006
|
/**
|
|
25628
|
-
* The flat, dotted VS Code setting
|
|
25629
|
-
* settings with dotted keys flat at the
|
|
25630
|
-
* literal key — not a nested
|
|
27007
|
+
* The flat, dotted VS Code setting keys this adapter manages, one per canonical
|
|
27008
|
+
* permission category. VS Code stores settings with dotted keys flat at the
|
|
27009
|
+
* document top level, so each is a single literal key — not a nested
|
|
27010
|
+
* `chat.tools.terminal` path. All three share the same
|
|
27011
|
+
* pattern-to-boolean shape, so one conversion covers them.
|
|
27012
|
+
*
|
|
27013
|
+
* The canonical `read` and `write` categories stay unmapped: VS Code has no
|
|
27014
|
+
* read-approval surface, and folding `write` into the edits map alongside
|
|
27015
|
+
* `edit` would make the two indistinguishable on import.
|
|
27016
|
+
*
|
|
25631
27017
|
* @see https://code.visualstudio.com/docs/agents/approvals
|
|
27018
|
+
* @see https://code.visualstudio.com/docs/copilot/chat/review-code-edits
|
|
25632
27019
|
*/
|
|
25633
|
-
const
|
|
25634
|
-
|
|
25635
|
-
|
|
25636
|
-
|
|
25637
|
-
|
|
25638
|
-
* …) have no terminal-command equivalent and are intentionally not mapped.
|
|
25639
|
-
*/
|
|
25640
|
-
const TERMINAL_CATEGORY = "bash";
|
|
27020
|
+
const AUTO_APPROVE_KEYS = {
|
|
27021
|
+
bash: "chat.tools.terminal.autoApprove",
|
|
27022
|
+
edit: "chat.tools.edits.autoApprove",
|
|
27023
|
+
webfetch: "chat.tools.urls.autoApprove"
|
|
27024
|
+
};
|
|
25641
27025
|
function asAutoApproveMap(value) {
|
|
25642
27026
|
if (!isPlainObject$1(value)) return {};
|
|
25643
27027
|
const result = {};
|
|
@@ -25645,6 +27029,21 @@ function asAutoApproveMap(value) {
|
|
|
25645
27029
|
return result;
|
|
25646
27030
|
}
|
|
25647
27031
|
/**
|
|
27032
|
+
* Render one canonical category's rules as a VS Code auto-approve map. Returns
|
|
27033
|
+
* `undefined` when the category contributes nothing, so the key is retracted
|
|
27034
|
+
* rather than written as an empty object.
|
|
27035
|
+
*
|
|
27036
|
+
* The resulting map replaces the file's existing value wholesale — rulesync
|
|
27037
|
+
* owns these keys, so a rule dropped from the canonical config disappears from
|
|
27038
|
+
* the settings file too.
|
|
27039
|
+
*/
|
|
27040
|
+
function buildAutoApproveValue(rules) {
|
|
27041
|
+
const autoApprove = {};
|
|
27042
|
+
for (const [pattern, action] of Object.entries(rules)) if (action === "allow") autoApprove[pattern] = true;
|
|
27043
|
+
else if (action === "deny") autoApprove[pattern] = false;
|
|
27044
|
+
return Object.keys(autoApprove).length > 0 ? autoApprove : void 0;
|
|
27045
|
+
}
|
|
27046
|
+
/**
|
|
25648
27047
|
* Permissions generator for GitHub Copilot Chat in VS Code.
|
|
25649
27048
|
*
|
|
25650
27049
|
* VS Code has no standalone, environment-agnostic Copilot policy file (like
|
|
@@ -25654,13 +27053,16 @@ function asAutoApproveMap(value) {
|
|
|
25654
27053
|
* many unrelated keys, so reads and writes merge into the existing JSON
|
|
25655
27054
|
* (touching only the one managed key) and the file is never deleted.
|
|
25656
27055
|
*
|
|
25657
|
-
*
|
|
25658
|
-
*
|
|
25659
|
-
* map as: `allow` → `true` (auto-approve), `deny`
|
|
25660
|
-
*
|
|
25661
|
-
*
|
|
25662
|
-
*
|
|
25663
|
-
*
|
|
27056
|
+
* Three canonical categories have a clean, non-lossy representation and are
|
|
27057
|
+
* mapped (see {@link AUTO_APPROVE_KEYS}): `bash`, `edit` and `webfetch`. In
|
|
27058
|
+
* every one, per-pattern rules map as: `allow` → `true` (auto-approve), `deny`
|
|
27059
|
+
* → `false` (VS Code then always prompts — note this is "never auto-approve",
|
|
27060
|
+
* not a hard block), and `ask` → the entry is OMITTED (VS Code falls through to
|
|
27061
|
+
* the same default prompt). A key whose canonical category is absent entirely
|
|
27062
|
+
* is left untouched, so authoring only `bash` rules never disturbs a
|
|
27063
|
+
* hand-written edits or urls map.
|
|
27064
|
+
* Only project scope is modeled: VS Code's user-scope settings.json lives at a
|
|
27065
|
+
* platform-dependent path outside rulesync's home-relative global model.
|
|
25664
27066
|
*/
|
|
25665
27067
|
var CopilotPermissions = class CopilotPermissions extends ToolPermissions {
|
|
25666
27068
|
constructor(params) {
|
|
@@ -25697,11 +27099,13 @@ var CopilotPermissions = class CopilotPermissions extends ToolPermissions {
|
|
|
25697
27099
|
const paths = CopilotPermissions.getSettablePaths();
|
|
25698
27100
|
const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
25699
27101
|
const existingContent = await readFileContentOrNull(filePath) ?? "{}";
|
|
25700
|
-
const
|
|
25701
|
-
const
|
|
25702
|
-
for (const [
|
|
25703
|
-
|
|
25704
|
-
|
|
27102
|
+
const config = rulesyncPermissions.getJson();
|
|
27103
|
+
const patch = {};
|
|
27104
|
+
for (const [category, settingKey] of Object.entries(AUTO_APPROVE_KEYS)) {
|
|
27105
|
+
const rules = config.permission[category];
|
|
27106
|
+
if (rules === void 0) continue;
|
|
27107
|
+
patch[settingKey] = buildAutoApproveValue(rules);
|
|
27108
|
+
}
|
|
25705
27109
|
return new CopilotPermissions({
|
|
25706
27110
|
outputRoot,
|
|
25707
27111
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -25710,7 +27114,7 @@ var CopilotPermissions = class CopilotPermissions extends ToolPermissions {
|
|
|
25710
27114
|
fileKey: sharedConfigFileKey(paths),
|
|
25711
27115
|
feature: "permissions",
|
|
25712
27116
|
existingContent,
|
|
25713
|
-
patch
|
|
27117
|
+
patch,
|
|
25714
27118
|
filePath
|
|
25715
27119
|
}),
|
|
25716
27120
|
validate: true
|
|
@@ -25729,10 +27133,13 @@ var CopilotPermissions = class CopilotPermissions extends ToolPermissions {
|
|
|
25729
27133
|
} catch (error) {
|
|
25730
27134
|
throw new Error(`Failed to parse Copilot VS Code settings in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
25731
27135
|
}
|
|
25732
|
-
const
|
|
25733
|
-
const
|
|
25734
|
-
|
|
25735
|
-
|
|
27136
|
+
const permission = {};
|
|
27137
|
+
for (const [category, settingKey] of Object.entries(AUTO_APPROVE_KEYS)) {
|
|
27138
|
+
const autoApprove = asAutoApproveMap(settings[settingKey]);
|
|
27139
|
+
const rules = {};
|
|
27140
|
+
for (const [pattern, flag] of Object.entries(autoApprove)) rules[pattern] = flag ? "allow" : "deny";
|
|
27141
|
+
if (Object.keys(rules).length > 0) permission[category] = rules;
|
|
27142
|
+
}
|
|
25736
27143
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify({ permission }, null, 2) });
|
|
25737
27144
|
}
|
|
25738
27145
|
validate() {
|
|
@@ -27062,6 +28469,13 @@ var HermesagentPermissions = class HermesagentPermissions extends ToolPermission
|
|
|
27062
28469
|
relativeFilePath: HERMESAGENT_CONFIG_FILE_NAME
|
|
27063
28470
|
};
|
|
27064
28471
|
}
|
|
28472
|
+
/**
|
|
28473
|
+
* `config.yaml` under every spelling the global profile root can take.
|
|
28474
|
+
* @see getHermesagentSharedConfigWritePaths
|
|
28475
|
+
*/
|
|
28476
|
+
static getExtraSharedWritePaths() {
|
|
28477
|
+
return getHermesagentSharedConfigWritePaths();
|
|
28478
|
+
}
|
|
27065
28479
|
constructor(params) {
|
|
27066
28480
|
super({
|
|
27067
28481
|
...params,
|
|
@@ -27099,7 +28513,7 @@ var HermesagentPermissions = class HermesagentPermissions extends ToolPermission
|
|
|
27099
28513
|
}
|
|
27100
28514
|
setFileContent(fileContent) {
|
|
27101
28515
|
this.fileContent = applySharedConfigPatch({
|
|
27102
|
-
fileKey:
|
|
28516
|
+
fileKey: getHermesagentConfigSharedFileKey({ global: this.global }),
|
|
27103
28517
|
feature: "permissions",
|
|
27104
28518
|
existingContent: fileContent,
|
|
27105
28519
|
patch: parseSharedConfig({
|
|
@@ -27512,7 +28926,10 @@ const KiloPermissionSchema = z.union([z.enum([
|
|
|
27512
28926
|
"ask",
|
|
27513
28927
|
"deny"
|
|
27514
28928
|
]))]);
|
|
27515
|
-
const KiloPermissionsConfigSchema = z.looseObject({
|
|
28929
|
+
const KiloPermissionsConfigSchema = z.looseObject({
|
|
28930
|
+
permission: z.optional(z.record(z.string(), KiloPermissionSchema)),
|
|
28931
|
+
sandbox: z.optional(z.unknown())
|
|
28932
|
+
});
|
|
27516
28933
|
/**
|
|
27517
28934
|
* Kilo permission keys that share a name with a canonical rulesync category and
|
|
27518
28935
|
* therefore stay in the shared `permission` block. Everything else (Kilo-only
|
|
@@ -27569,6 +28986,25 @@ function collectKiloDenyPatterns(value) {
|
|
|
27569
28986
|
}
|
|
27570
28987
|
return [];
|
|
27571
28988
|
}
|
|
28989
|
+
function asKiloRecord(value) {
|
|
28990
|
+
return isPlainObject$1(value) ? { ...value } : {};
|
|
28991
|
+
}
|
|
28992
|
+
/**
|
|
28993
|
+
* The `sandbox` keys a *project* `kilo.jsonc` may state. Kilo honors
|
|
28994
|
+
* `allowed_hosts` and `writable_paths` from the global config only, and lets a
|
|
28995
|
+
* project config merely tighten — so writing the wider keys into a project file
|
|
28996
|
+
* would produce config Kilo ignores.
|
|
28997
|
+
* @see https://kilo.ai/docs/getting-started/settings/sandboxing
|
|
28998
|
+
*/
|
|
28999
|
+
const KILO_PROJECT_SCOPE_SANDBOX_KEYS = /* @__PURE__ */ new Set(["enabled", "network"]);
|
|
29000
|
+
function narrowSandboxToProjectScope({ authored, logger }) {
|
|
29001
|
+
const emitted = {};
|
|
29002
|
+
const dropped = [];
|
|
29003
|
+
for (const [key, value] of Object.entries(authored)) if (KILO_PROJECT_SCOPE_SANDBOX_KEYS.has(key)) emitted[key] = value;
|
|
29004
|
+
else dropped.push(key);
|
|
29005
|
+
if (dropped.length > 0) logger?.warn(`Kilo honors these 'sandbox' keys from the global config only, so they were dropped from the project config: ${dropped.toSorted().join(", ")}. A project config may only tighten the sandbox ('enabled', 'network'); generate with --global to author the rest.`);
|
|
29006
|
+
return emitted;
|
|
29007
|
+
}
|
|
27572
29008
|
var KiloPermissions = class KiloPermissions extends ToolPermissions {
|
|
27573
29009
|
json;
|
|
27574
29010
|
constructor(params) {
|
|
@@ -27648,8 +29084,7 @@ var KiloPermissions = class KiloPermissions extends ToolPermissions {
|
|
|
27648
29084
|
const basePaths = KiloPermissions.getSettablePaths({ global });
|
|
27649
29085
|
const filePath = join(outputRoot, basePaths.relativeDirPath, basePaths.relativeFilePath);
|
|
27650
29086
|
const parsed = parseKiloJsoncStrict(await readFileContentOrNull(filePath) ?? "{}", filePath);
|
|
27651
|
-
const
|
|
27652
|
-
const existingPermission = parsedPermission && typeof parsedPermission === "object" && !Array.isArray(parsedPermission) ? { ...parsedPermission } : {};
|
|
29087
|
+
const existingPermission = asKiloRecord(parsed.permission);
|
|
27653
29088
|
const rulesyncJson = rulesyncPermissions.getJson();
|
|
27654
29089
|
const kiloOverride = rulesyncJson.kilo;
|
|
27655
29090
|
const incomingPermission = {
|
|
@@ -27675,6 +29110,18 @@ var KiloPermissions = class KiloPermissions extends ToolPermissions {
|
|
|
27675
29110
|
...parsed,
|
|
27676
29111
|
permission: mergedPermission
|
|
27677
29112
|
};
|
|
29113
|
+
if (kiloOverride?.sandbox !== void 0) {
|
|
29114
|
+
const authored = asKiloRecord(kiloOverride.sandbox);
|
|
29115
|
+
const emitted = global ? authored : narrowSandboxToProjectScope({
|
|
29116
|
+
authored,
|
|
29117
|
+
logger
|
|
29118
|
+
});
|
|
29119
|
+
const merged = {
|
|
29120
|
+
...asKiloRecord(parsed.sandbox),
|
|
29121
|
+
...emitted
|
|
29122
|
+
};
|
|
29123
|
+
if (Object.keys(merged).length > 0) nextJson.sandbox = merged;
|
|
29124
|
+
}
|
|
27678
29125
|
return new KiloPermissions({
|
|
27679
29126
|
outputRoot,
|
|
27680
29127
|
relativeDirPath: basePaths.relativeDirPath,
|
|
@@ -27689,9 +29136,14 @@ var KiloPermissions = class KiloPermissions extends ToolPermissions {
|
|
|
27689
29136
|
const overrideOnly = {};
|
|
27690
29137
|
for (const [key, value] of Object.entries(rawPermission)) if (isSharedKiloCategory(key)) shared[key] = typeof value === "string" ? { "*": value } : value;
|
|
27691
29138
|
else overrideOnly[key] = value;
|
|
27692
|
-
const
|
|
29139
|
+
const sandbox = this.json.sandbox;
|
|
29140
|
+
const override = {
|
|
29141
|
+
...Object.keys(overrideOnly).length > 0 && { permission: overrideOnly },
|
|
29142
|
+
...isPlainObject$1(sandbox) && { sandbox }
|
|
29143
|
+
};
|
|
29144
|
+
const json = Object.keys(override).length > 0 ? {
|
|
27693
29145
|
permission: shared,
|
|
27694
|
-
kilo:
|
|
29146
|
+
kilo: override
|
|
27695
29147
|
} : { permission: shared };
|
|
27696
29148
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(json, null, 2) });
|
|
27697
29149
|
}
|
|
@@ -27932,6 +29384,13 @@ var KimiCodePermissions = class KimiCodePermissions extends ToolPermissions {
|
|
|
27932
29384
|
isDeletable() {
|
|
27933
29385
|
return false;
|
|
27934
29386
|
}
|
|
29387
|
+
/**
|
|
29388
|
+
* `config.toml` under both spellings its directory can take.
|
|
29389
|
+
* @see getKimiCodeSharedConfigWritePaths
|
|
29390
|
+
*/
|
|
29391
|
+
static getExtraSharedWritePaths() {
|
|
29392
|
+
return getKimiCodeSharedConfigWritePaths();
|
|
29393
|
+
}
|
|
27935
29394
|
shouldMergeExistingFileContent() {
|
|
27936
29395
|
return true;
|
|
27937
29396
|
}
|
|
@@ -27946,7 +29405,7 @@ var KimiCodePermissions = class KimiCodePermissions extends ToolPermissions {
|
|
|
27946
29405
|
patch
|
|
27947
29406
|
});
|
|
27948
29407
|
this.fileContent = applySharedConfigPatch({
|
|
27949
|
-
fileKey:
|
|
29408
|
+
fileKey: getKimiCodeConfigSharedFileKey({ global: this.global }),
|
|
27950
29409
|
feature: "permissions",
|
|
27951
29410
|
existingContent: fileContent,
|
|
27952
29411
|
patch: {
|
|
@@ -28870,14 +30329,6 @@ function parseReasonixConfig(fileContent) {
|
|
|
28870
30329
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
28871
30330
|
return { ...parsed };
|
|
28872
30331
|
}
|
|
28873
|
-
function toStringArray$1(value) {
|
|
28874
|
-
if (!Array.isArray(value)) return [];
|
|
28875
|
-
return value.filter((entry) => typeof entry === "string");
|
|
28876
|
-
}
|
|
28877
|
-
function toPermissionsTable(value) {
|
|
28878
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
28879
|
-
return { ...value };
|
|
28880
|
-
}
|
|
28881
30332
|
const REASONIX_OVERRIDE_AGENT_KEYS = ["plan_mode_read_only_commands"];
|
|
28882
30333
|
/**
|
|
28883
30334
|
* `[agent]` keys an older `reasonix.toml` may carry that left the documented
|
|
@@ -28934,12 +30385,12 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
28934
30385
|
const config = rulesyncPermissions.getJson();
|
|
28935
30386
|
const { allow, ask, deny } = convertRulesyncToReasonixPermissions(config);
|
|
28936
30387
|
const managedToolNames = new Set(Object.keys(config.permission).map((category) => toReasonixToolName(category)));
|
|
28937
|
-
const existingPermissions =
|
|
28938
|
-
const preservedAllow =
|
|
28939
|
-
const preservedAsk =
|
|
28940
|
-
const preservedDeny =
|
|
30388
|
+
const existingPermissions = toReasonixTable(parsed.permissions);
|
|
30389
|
+
const preservedAllow = toReasonixStringArray(existingPermissions.allow).filter((entry) => !managedToolNames.has(parseReasonixPermissionEntry(entry).toolName));
|
|
30390
|
+
const preservedAsk = toReasonixStringArray(existingPermissions.ask).filter((entry) => !managedToolNames.has(parseReasonixPermissionEntry(entry).toolName));
|
|
30391
|
+
const preservedDeny = toReasonixStringArray(existingPermissions.deny).filter((entry) => !managedToolNames.has(parseReasonixPermissionEntry(entry).toolName));
|
|
28941
30392
|
if (logger && managedToolNames.has("Read")) {
|
|
28942
|
-
const droppedReadDenyEntries =
|
|
30393
|
+
const droppedReadDenyEntries = toReasonixStringArray(existingPermissions.deny).filter((entry) => {
|
|
28943
30394
|
const { toolName } = parseReasonixPermissionEntry(entry);
|
|
28944
30395
|
return toolName === "Read";
|
|
28945
30396
|
});
|
|
@@ -28986,11 +30437,11 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
28986
30437
|
});
|
|
28987
30438
|
}
|
|
28988
30439
|
toRulesyncPermissions() {
|
|
28989
|
-
const permissions =
|
|
30440
|
+
const permissions = toReasonixTable(this.toml.permissions);
|
|
28990
30441
|
const config = convertReasonixToRulesyncPermissions({
|
|
28991
|
-
allow:
|
|
28992
|
-
ask:
|
|
28993
|
-
deny:
|
|
30442
|
+
allow: toReasonixStringArray(permissions.allow),
|
|
30443
|
+
ask: toReasonixStringArray(permissions.ask),
|
|
30444
|
+
deny: toReasonixStringArray(permissions.deny)
|
|
28994
30445
|
});
|
|
28995
30446
|
const sandbox = asReasonixRecord(this.toml.sandbox);
|
|
28996
30447
|
const agentPlanMode = pickReasonixKeys(this.toml.agent, [...REASONIX_OVERRIDE_AGENT_KEYS, ...REASONIX_RETIRED_AGENT_KEYS]);
|
|
@@ -29082,19 +30533,31 @@ const CATEGORY_TO_TOOL_KEYS = {
|
|
|
29082
30533
|
"open_files",
|
|
29083
30534
|
"expand_code_chunks",
|
|
29084
30535
|
"expand_folder",
|
|
29085
|
-
"grep"
|
|
30536
|
+
"grep",
|
|
30537
|
+
"getJiraIssue",
|
|
30538
|
+
"getConfluencePage"
|
|
29086
30539
|
],
|
|
29087
30540
|
edit: [
|
|
29088
30541
|
"find_and_replace_code",
|
|
29089
30542
|
"create_file",
|
|
29090
30543
|
"delete_file",
|
|
29091
|
-
"move_file"
|
|
30544
|
+
"move_file",
|
|
30545
|
+
"createTechnicalPlan",
|
|
30546
|
+
"createJiraIssue",
|
|
30547
|
+
"updateJiraIssue",
|
|
30548
|
+
"createConfluencePage",
|
|
30549
|
+
"updateConfluencePage"
|
|
29092
30550
|
],
|
|
29093
30551
|
write: [
|
|
29094
30552
|
"create_file",
|
|
29095
30553
|
"delete_file",
|
|
29096
30554
|
"move_file",
|
|
29097
|
-
"find_and_replace_code"
|
|
30555
|
+
"find_and_replace_code",
|
|
30556
|
+
"createTechnicalPlan",
|
|
30557
|
+
"createJiraIssue",
|
|
30558
|
+
"updateJiraIssue",
|
|
30559
|
+
"createConfluencePage",
|
|
30560
|
+
"updateConfluencePage"
|
|
29098
30561
|
]
|
|
29099
30562
|
};
|
|
29100
30563
|
const TOOL_KEY_TO_CATEGORY = {
|
|
@@ -29102,13 +30565,24 @@ const TOOL_KEY_TO_CATEGORY = {
|
|
|
29102
30565
|
expand_code_chunks: "read",
|
|
29103
30566
|
expand_folder: "read",
|
|
29104
30567
|
grep: "read",
|
|
30568
|
+
getJiraIssue: "read",
|
|
30569
|
+
getConfluencePage: "read",
|
|
29105
30570
|
find_and_replace_code: "edit",
|
|
29106
30571
|
create_file: "edit",
|
|
29107
30572
|
delete_file: "edit",
|
|
29108
|
-
move_file: "edit"
|
|
30573
|
+
move_file: "edit",
|
|
30574
|
+
createTechnicalPlan: "edit",
|
|
30575
|
+
createJiraIssue: "edit",
|
|
30576
|
+
updateJiraIssue: "edit",
|
|
30577
|
+
createConfluencePage: "edit",
|
|
30578
|
+
updateConfluencePage: "edit"
|
|
29109
30579
|
};
|
|
29110
30580
|
const MANAGED_TOOL_KEYS = [.../* @__PURE__ */ new Set([...Object.values(CATEGORY_TO_TOOL_KEYS).flat(), ...Object.keys(TOOL_KEY_TO_CATEGORY)])];
|
|
29111
|
-
const OWNED_TOOL_PERMISSION_KEYS = [
|
|
30581
|
+
const OWNED_TOOL_PERMISSION_KEYS = [
|
|
30582
|
+
"bash",
|
|
30583
|
+
"allowedExternalPaths",
|
|
30584
|
+
"default"
|
|
30585
|
+
];
|
|
29112
30586
|
/**
|
|
29113
30587
|
* Permissions adapter for Rovo Dev CLI.
|
|
29114
30588
|
*
|
|
@@ -29122,9 +30596,16 @@ const OWNED_TOOL_PERMISSION_KEYS = ["bash", "allowedExternalPaths"];
|
|
|
29122
30596
|
* Mapping decisions (rulesync canonical -> Rovo Dev):
|
|
29123
30597
|
* - `bash`: the catch-all `*` pattern -> `bash.default`; every other pattern ->
|
|
29124
30598
|
* a `bash.commands[]` entry `{ command: <pattern as regex>, permission }`.
|
|
30599
|
+
* - the all-tools category `*`: its catch-all -> `toolPermissions.default`,
|
|
30600
|
+
* the level Rovo Dev falls back to for any tool with no more specific
|
|
30601
|
+
* setting (Rovo Dev's own default is `ask`).
|
|
29125
30602
|
* - `read` -> the inspection tools (`open_files`, `expand_code_chunks`,
|
|
29126
|
-
* `expand_folder`, `grep`
|
|
29127
|
-
*
|
|
30603
|
+
* `expand_folder`, `grep`, `getJiraIssue`, `getConfluencePage`);
|
|
30604
|
+
* `edit`/`write` -> the mutation tools (`find_and_replace_code`,
|
|
30605
|
+
* `create_file`, `delete_file`, `move_file`, `createTechnicalPlan`,
|
|
30606
|
+
* `createJiraIssue`, `updateJiraIssue`, `createConfluencePage`,
|
|
30607
|
+
* `updateConfluencePage`) — so these two categories reach Jira and
|
|
30608
|
+
* Confluence, not just the working tree.
|
|
29128
30609
|
* These Rovo Dev keys hold a single level (no per-pattern rules), so only the
|
|
29129
30610
|
* catch-all `*` of each category sets the level. Non-catch-all `allow` rules
|
|
29130
30611
|
* in those categories are surfaced as `allowedExternalPaths` so explicit path
|
|
@@ -29304,6 +30785,10 @@ function stripPermissiveOwnedValues(toolPermissions) {
|
|
|
29304
30785
|
delete toolPermissions.allowedExternalPaths;
|
|
29305
30786
|
strippedKeys.push("allowedExternalPaths");
|
|
29306
30787
|
}
|
|
30788
|
+
if (toolPermissions.default === "allow") {
|
|
30789
|
+
delete toolPermissions.default;
|
|
30790
|
+
strippedKeys.push("default");
|
|
30791
|
+
}
|
|
29307
30792
|
const bash = toolPermissions.bash;
|
|
29308
30793
|
if (isRecord(bash)) {
|
|
29309
30794
|
const stripped = { ...bash };
|
|
@@ -29328,10 +30813,19 @@ function stripPermissiveOwnedValues(toolPermissions) {
|
|
|
29328
30813
|
function convertRulesyncToRovodevToolPermissions({ config, logger }) {
|
|
29329
30814
|
const toolPermissions = {};
|
|
29330
30815
|
const allowedExternalPaths = [];
|
|
29331
|
-
|
|
29332
|
-
|
|
29333
|
-
|
|
30816
|
+
warnOnEditWriteConflict({
|
|
30817
|
+
config,
|
|
30818
|
+
logger
|
|
30819
|
+
});
|
|
29334
30820
|
for (const [category, rules] of Object.entries(config.permission)) {
|
|
30821
|
+
if (category === CATCH_ALL_PATTERN$1) {
|
|
30822
|
+
const toolWideDefault = convertAllToolsRules({
|
|
30823
|
+
rules,
|
|
30824
|
+
logger
|
|
30825
|
+
});
|
|
30826
|
+
if (toolWideDefault) toolPermissions.default = toolWideDefault;
|
|
30827
|
+
continue;
|
|
30828
|
+
}
|
|
29335
30829
|
if (category === "bash") {
|
|
29336
30830
|
const bash = convertBashRules(rules);
|
|
29337
30831
|
if (bash) toolPermissions.bash = bash;
|
|
@@ -29358,6 +30852,36 @@ function convertRulesyncToRovodevToolPermissions({ config, logger }) {
|
|
|
29358
30852
|
if (allowedExternalPaths.length > 0) toolPermissions.allowedExternalPaths = [...new Set(allowedExternalPaths)].toSorted();
|
|
29359
30853
|
return toolPermissions;
|
|
29360
30854
|
}
|
|
30855
|
+
/**
|
|
30856
|
+
* `edit` and `write` collapse onto the same Rovo Dev file-mutation tools, so a
|
|
30857
|
+
* conflicting catch-all between them cannot be represented. Warn that the loss
|
|
30858
|
+
* is happening; the conversion keeps the stricter of the two — the same
|
|
30859
|
+
* fail-closed rule the import direction uses when those tools disagree, so the
|
|
30860
|
+
* resolution never grants more than the author asked for.
|
|
30861
|
+
*/
|
|
30862
|
+
function warnOnEditWriteConflict({ config, logger }) {
|
|
30863
|
+
const editCatchAll = config.permission.edit?.[CATCH_ALL_PATTERN$1];
|
|
30864
|
+
const writeCatchAll = config.permission.write?.[CATCH_ALL_PATTERN$1];
|
|
30865
|
+
if (editCatchAll && writeCatchAll && editCatchAll !== writeCatchAll) logger?.warn(`Rovo Dev maps both "edit" and "write" onto the same file-mutation tools, but they have conflicting catch-all permissions ("edit": "${editCatchAll}", "write": "${writeCatchAll}"). The stricter of the two ("${strictestAction(editCatchAll, writeCatchAll)}") is used.`);
|
|
30866
|
+
}
|
|
30867
|
+
/**
|
|
30868
|
+
* The canonical all-tools category. Its catch-all sets the tool-wide
|
|
30869
|
+
* `toolPermissions.default`, the same way `bash`'s catch-all sets
|
|
30870
|
+
* `bash.default` — both are the level Rovo Dev falls back to. Pattern rules
|
|
30871
|
+
* under `*` have no counterpart (the default is a single level), so they are
|
|
30872
|
+
* reported and skipped like any other rule Rovo Dev cannot express.
|
|
30873
|
+
*/
|
|
30874
|
+
function convertAllToolsRules({ rules, logger }) {
|
|
30875
|
+
let toolWideDefault;
|
|
30876
|
+
for (const [pattern, action] of Object.entries(rules)) {
|
|
30877
|
+
if (pattern === CATCH_ALL_PATTERN$1) {
|
|
30878
|
+
toolWideDefault = action;
|
|
30879
|
+
continue;
|
|
30880
|
+
}
|
|
30881
|
+
logger?.warn(`Rovo Dev's tool-wide default is a single level, so it cannot express the pattern "${pattern}" in the "*" category. Skipping it.`);
|
|
30882
|
+
}
|
|
30883
|
+
return toolWideDefault;
|
|
30884
|
+
}
|
|
29361
30885
|
function convertBashRules(rules) {
|
|
29362
30886
|
const bash = {};
|
|
29363
30887
|
const commands = [];
|
|
@@ -29379,6 +30903,7 @@ function convertBashRules(rules) {
|
|
|
29379
30903
|
*/
|
|
29380
30904
|
function convertRovodevToolPermissionsToRulesync(toolPermissions) {
|
|
29381
30905
|
const permission = {};
|
|
30906
|
+
if (isPermissionAction(toolPermissions.default)) permission[CATCH_ALL_PATTERN$1] = { [CATCH_ALL_PATTERN$1]: toolPermissions.default };
|
|
29382
30907
|
const bash = toolPermissions.bash;
|
|
29383
30908
|
if (isRecord(bash)) {
|
|
29384
30909
|
const bashRules = {};
|
|
@@ -29389,12 +30914,15 @@ function convertRovodevToolPermissionsToRulesync(toolPermissions) {
|
|
|
29389
30914
|
if (Object.keys(bashRules).length > 0) permission.bash = bashRules;
|
|
29390
30915
|
}
|
|
29391
30916
|
const nestedTools = isRecord(toolPermissions.tools) ? toolPermissions.tools : {};
|
|
29392
|
-
|
|
29393
|
-
|
|
29394
|
-
|
|
30917
|
+
const implicitLevel = isPermissionAction(toolPermissions.default) ? toolPermissions.default : "ask";
|
|
30918
|
+
for (const category of new Set(Object.values(TOOL_KEY_TO_CATEGORY))) {
|
|
30919
|
+
const levels = Object.entries(TOOL_KEY_TO_CATEGORY).filter(([, mapped]) => mapped === category).map(([toolKey]) => {
|
|
30920
|
+
const value = Object.hasOwn(nestedTools, toolKey) ? nestedTools[toolKey] : toolPermissions[toolKey];
|
|
30921
|
+
return isPermissionAction(value) ? value : void 0;
|
|
30922
|
+
});
|
|
30923
|
+
if (levels.every((level) => level === void 0)) continue;
|
|
29395
30924
|
permission[category] ??= {};
|
|
29396
|
-
|
|
29397
|
-
permission[category][CATCH_ALL_PATTERN$1] = strictestAction(current, value);
|
|
30925
|
+
permission[category][CATCH_ALL_PATTERN$1] = levels.reduce((strictest, level) => strictestAction(strictest, level ?? implicitLevel), permission[category][CATCH_ALL_PATTERN$1]);
|
|
29398
30926
|
}
|
|
29399
30927
|
if (isStringArray$1(toolPermissions.allowedExternalPaths)) for (const path of toolPermissions.allowedExternalPaths) {
|
|
29400
30928
|
permission.read ??= {};
|
|
@@ -30226,16 +31754,11 @@ var ZedPermissions = class ZedPermissions extends ToolPermissions {
|
|
|
30226
31754
|
relativeFilePath: ZED_SETTINGS_FILE_NAME
|
|
30227
31755
|
};
|
|
30228
31756
|
}
|
|
30229
|
-
/**
|
|
30230
|
-
* The global settings file of the OTHER platform: `getSettablePaths` resolves
|
|
30231
|
-
* `~/.config/zed` vs `%APPDATA%\Zed` per platform, but the shared-write
|
|
30232
|
-
* derivation (and the gateway ownership table it is checked against) must
|
|
30233
|
-
* know both spellings on every platform.
|
|
30234
|
-
*/
|
|
31757
|
+
/** @see getZedOtherPlatformGlobalDir */
|
|
30235
31758
|
static getExtraSharedWritePaths({ global = false } = {}) {
|
|
30236
31759
|
if (!global) return [];
|
|
30237
31760
|
return [{
|
|
30238
|
-
relativeDirPath:
|
|
31761
|
+
relativeDirPath: getZedOtherPlatformGlobalDir(),
|
|
30239
31762
|
relativeFilePath: ZED_SETTINGS_FILE_NAME
|
|
30240
31763
|
}];
|
|
30241
31764
|
}
|
|
@@ -31752,6 +33275,7 @@ const ClaudecodeSkillFrontmatterSchema = z.looseObject({
|
|
|
31752
33275
|
arguments: z.optional(z.union([z.string(), z.array(z.string())])),
|
|
31753
33276
|
context: z.optional(z.string()),
|
|
31754
33277
|
agent: z.optional(z.string()),
|
|
33278
|
+
background: z.optional(z.boolean()),
|
|
31755
33279
|
hooks: z.optional(z.looseObject({})),
|
|
31756
33280
|
shell: z.optional(z.string()),
|
|
31757
33281
|
"disable-model-invocation": z.optional(z.boolean()),
|
|
@@ -31778,6 +33302,7 @@ function buildClaudecodeSkillFrontmatter({ rulesyncFrontmatter, resolvedDisableM
|
|
|
31778
33302
|
shell: section.shell
|
|
31779
33303
|
};
|
|
31780
33304
|
const definedFields = {
|
|
33305
|
+
background: section.background,
|
|
31781
33306
|
arguments: section.arguments,
|
|
31782
33307
|
hooks: section.hooks,
|
|
31783
33308
|
"disable-model-invocation": resolvedDisableModelInvocation,
|
|
@@ -31855,6 +33380,7 @@ var ClaudecodeSkill = class extends ToolSkill {
|
|
|
31855
33380
|
...frontmatter.arguments !== void 0 && { arguments: frontmatter.arguments },
|
|
31856
33381
|
...frontmatter.context && { context: frontmatter.context },
|
|
31857
33382
|
...frontmatter.agent && { agent: frontmatter.agent },
|
|
33383
|
+
...frontmatter.background !== void 0 && { background: frontmatter.background },
|
|
31858
33384
|
...frontmatter.hooks !== void 0 && { hooks: frontmatter.hooks },
|
|
31859
33385
|
...frontmatter.shell && { shell: frontmatter.shell },
|
|
31860
33386
|
...frontmatter["disable-model-invocation"] !== void 0 && { "disable-model-invocation": frontmatter["disable-model-invocation"] },
|
|
@@ -32464,7 +33990,9 @@ const CopilotcliSkillFrontmatterSchema = z.looseObject({
|
|
|
32464
33990
|
description: z.string(),
|
|
32465
33991
|
license: z.optional(z.string()),
|
|
32466
33992
|
"allowed-tools": z.optional(z.union([z.string(), z.array(z.string())])),
|
|
32467
|
-
"argument-hint": z.optional(z.string())
|
|
33993
|
+
"argument-hint": z.optional(z.string()),
|
|
33994
|
+
"user-invocable": z.optional(z.boolean()),
|
|
33995
|
+
"disable-model-invocation": z.optional(z.boolean())
|
|
32468
33996
|
});
|
|
32469
33997
|
/**
|
|
32470
33998
|
* Represents a GitHub Copilot CLI skill directory.
|
|
@@ -32523,7 +34051,9 @@ var CopilotcliSkill = class CopilotcliSkill extends ToolSkill {
|
|
|
32523
34051
|
const copilotcliSection = {
|
|
32524
34052
|
...frontmatter.license !== void 0 && { license: frontmatter.license },
|
|
32525
34053
|
...frontmatter["allowed-tools"] !== void 0 && { "allowed-tools": frontmatter["allowed-tools"] },
|
|
32526
|
-
...frontmatter["argument-hint"] !== void 0 && { "argument-hint": frontmatter["argument-hint"] }
|
|
34054
|
+
...frontmatter["argument-hint"] !== void 0 && { "argument-hint": frontmatter["argument-hint"] },
|
|
34055
|
+
...frontmatter["user-invocable"] !== void 0 && { "user-invocable": frontmatter["user-invocable"] },
|
|
34056
|
+
...frontmatter["disable-model-invocation"] !== void 0 && { "disable-model-invocation": frontmatter["disable-model-invocation"] }
|
|
32527
34057
|
};
|
|
32528
34058
|
const rulesyncFrontmatter = {
|
|
32529
34059
|
name: frontmatter.name,
|
|
@@ -32550,7 +34080,9 @@ var CopilotcliSkill = class CopilotcliSkill extends ToolSkill {
|
|
|
32550
34080
|
description: rulesyncFrontmatter.description,
|
|
32551
34081
|
...rulesyncFrontmatter.copilotcli?.license !== void 0 && { license: rulesyncFrontmatter.copilotcli.license },
|
|
32552
34082
|
...rulesyncFrontmatter.copilotcli?.["allowed-tools"] !== void 0 && { "allowed-tools": rulesyncFrontmatter.copilotcli["allowed-tools"] },
|
|
32553
|
-
...rulesyncFrontmatter.copilotcli?.["argument-hint"] !== void 0 && { "argument-hint": rulesyncFrontmatter.copilotcli["argument-hint"] }
|
|
34083
|
+
...rulesyncFrontmatter.copilotcli?.["argument-hint"] !== void 0 && { "argument-hint": rulesyncFrontmatter.copilotcli["argument-hint"] },
|
|
34084
|
+
...rulesyncFrontmatter.copilotcli?.["user-invocable"] !== void 0 && { "user-invocable": rulesyncFrontmatter.copilotcli["user-invocable"] },
|
|
34085
|
+
...rulesyncFrontmatter.copilotcli?.["disable-model-invocation"] !== void 0 && { "disable-model-invocation": rulesyncFrontmatter.copilotcli["disable-model-invocation"] }
|
|
32554
34086
|
};
|
|
32555
34087
|
return new CopilotcliSkill({
|
|
32556
34088
|
outputRoot,
|
|
@@ -34220,6 +35752,37 @@ var OpenCodeSkill = class OpenCodeSkill extends ToolSkill {
|
|
|
34220
35752
|
alternativeSkillRoots: [global ? OPENCODE_GLOBAL_SKILL_DIR_PATH : OPENCODE_SKILL_DIR_PATH]
|
|
34221
35753
|
};
|
|
34222
35754
|
}
|
|
35755
|
+
/**
|
|
35756
|
+
* Extra skill roots the project configured in `opencode.json` /
|
|
35757
|
+
* `opencode.jsonc` via `skills.paths` ("Additional paths to skill folders").
|
|
35758
|
+
* Without these, skills a project keeps outside `.opencode/skills/` are
|
|
35759
|
+
* invisible to `rulesync import` even though OpenCode loads them.
|
|
35760
|
+
*
|
|
35761
|
+
* Import-only: rulesync keeps writing to its own managed root, so a
|
|
35762
|
+
* configured path is read but never generated into. `skills.urls` is a
|
|
35763
|
+
* remote-fetch surface and is out of scope for a file-based generator.
|
|
35764
|
+
*
|
|
35765
|
+
* Absolute paths and paths escaping the output root are dropped — an import
|
|
35766
|
+
* root is joined onto `outputRoot`, and reaching outside it is not something
|
|
35767
|
+
* a project config should be able to ask for.
|
|
35768
|
+
*
|
|
35769
|
+
* @see https://opencode.ai/config.json
|
|
35770
|
+
*/
|
|
35771
|
+
static async getConfiguredImportRoots({ outputRoot, global = false }) {
|
|
35772
|
+
const skills = asOpencodeEntries((await readOpencodeConfig({
|
|
35773
|
+
outputRoot,
|
|
35774
|
+
global
|
|
35775
|
+
})).skills);
|
|
35776
|
+
if (skills === null || !Array.isArray(skills.paths)) return [];
|
|
35777
|
+
const configDir = getOpencodeConfigDir({
|
|
35778
|
+
outputRoot,
|
|
35779
|
+
global
|
|
35780
|
+
});
|
|
35781
|
+
return skills.paths.filter((candidate) => typeof candidate === "string" && candidate !== "" && !isAbsolute(candidate) && !normalize(candidate).startsWith("..")).map((relativeDirPath) => ({
|
|
35782
|
+
outputRoot: configDir,
|
|
35783
|
+
relativeDirPath
|
|
35784
|
+
}));
|
|
35785
|
+
}
|
|
34223
35786
|
getFrontmatter() {
|
|
34224
35787
|
return OpenCodeSkillFrontmatterSchema.parse(this.requireMainFileFrontmatter());
|
|
34225
35788
|
}
|
|
@@ -36153,12 +37716,19 @@ var SkillsProcessor = class extends DirFeatureProcessor {
|
|
|
36153
37716
|
*/
|
|
36154
37717
|
async loadToolDirs() {
|
|
36155
37718
|
const factory = this.getFactory(this.toolTarget);
|
|
36156
|
-
const
|
|
37719
|
+
const paths = factory.class.getSettablePaths({ global: this.global });
|
|
37720
|
+
const configuredRoots = factory.class.getConfiguredImportRoots ? await factory.class.getConfiguredImportRoots({
|
|
37721
|
+
outputRoot: this.outputRoot,
|
|
37722
|
+
global: this.global
|
|
37723
|
+
}) : [];
|
|
37724
|
+
const configuredRootPaths = new Set(configuredRoots.map((root) => root.relativeDirPath));
|
|
37725
|
+
const roots = [...toolSkillImportRoots(paths), ...configuredRoots];
|
|
36157
37726
|
const seenSkillNames = /* @__PURE__ */ new Set();
|
|
36158
37727
|
const toolSkills = [];
|
|
36159
37728
|
for (const root of roots) {
|
|
36160
37729
|
const rootOutputRoot = typeof root === "string" ? this.outputRoot : root.outputRoot;
|
|
36161
37730
|
const relativeDirPath = typeof root === "string" ? root : root.relativeDirPath;
|
|
37731
|
+
const isConfiguredRoot = configuredRootPaths.has(relativeDirPath);
|
|
36162
37732
|
const skillsDirPath = join(rootOutputRoot, relativeDirPath);
|
|
36163
37733
|
if (!await directoryExists(skillsDirPath)) continue;
|
|
36164
37734
|
const dirPaths = await findFilesByGlobs(join(skillsDirPath, "*"), { type: "dir" });
|
|
@@ -36173,12 +37743,20 @@ var SkillsProcessor = class extends DirFeatureProcessor {
|
|
|
36173
37743
|
})) continue;
|
|
36174
37744
|
ownedDirNames.push(dirName);
|
|
36175
37745
|
}
|
|
36176
|
-
const directorySkills = await Promise.all(ownedDirNames.map((dirName) =>
|
|
36177
|
-
|
|
36178
|
-
|
|
36179
|
-
|
|
36180
|
-
|
|
36181
|
-
|
|
37746
|
+
const directorySkills = (await Promise.all(ownedDirNames.map(async (dirName) => {
|
|
37747
|
+
try {
|
|
37748
|
+
return await factory.class.fromDir({
|
|
37749
|
+
outputRoot: rootOutputRoot,
|
|
37750
|
+
relativeDirPath,
|
|
37751
|
+
dirName,
|
|
37752
|
+
global: this.global
|
|
37753
|
+
});
|
|
37754
|
+
} catch (error) {
|
|
37755
|
+
if (!isConfiguredRoot) throw error;
|
|
37756
|
+
this.logger.warn(`Skipping ${join(relativeDirPath, dirName)}: ${formatError(error)}`);
|
|
37757
|
+
return null;
|
|
37758
|
+
}
|
|
37759
|
+
}))).filter((skill) => skill !== null);
|
|
36182
37760
|
for (const skill of directorySkills) {
|
|
36183
37761
|
const skillName = skill.getImportIdentity();
|
|
36184
37762
|
if (seenSkillNames.has(skillName)) continue;
|
|
@@ -36703,6 +38281,242 @@ var RovodevSubagent = class RovodevSubagent extends ToolSubagent {
|
|
|
36703
38281
|
}
|
|
36704
38282
|
};
|
|
36705
38283
|
//#endregion
|
|
38284
|
+
//#region src/features/subagents/antigravity-shared-subagent.ts
|
|
38285
|
+
/**
|
|
38286
|
+
* Frontmatter of an Antigravity custom agent (Markdown format, CLI v1.1.6+).
|
|
38287
|
+
*
|
|
38288
|
+
* `name` and `description` are required upstream; the rest are optional and
|
|
38289
|
+
* documented with defaults (`tools: []`, `mainAgent: true`, `subagent: true`,
|
|
38290
|
+
* `model: inherit`, `commandExecutionPolicy: sandbox`, `mcpServers: []`,
|
|
38291
|
+
* `skills`/`plugins`: `[]`). `hidden` and `inheritMcp` appear in the v1.1.6
|
|
38292
|
+
* release notes but not in the documented frontmatter table, so they are
|
|
38293
|
+
* accepted as verbatim passthrough without any behavior modeled around them.
|
|
38294
|
+
* `looseObject` keeps unknown future fields round-tripping.
|
|
38295
|
+
*
|
|
38296
|
+
* @see https://antigravity.google/docs/subagents
|
|
38297
|
+
*/
|
|
38298
|
+
const AntigravitySubagentFrontmatterSchema = z.looseObject({
|
|
38299
|
+
name: z.string(),
|
|
38300
|
+
description: z.string().check(z.minLength(1)),
|
|
38301
|
+
tools: z.optional(z.array(z.string())),
|
|
38302
|
+
mainAgent: z.optional(z.boolean()),
|
|
38303
|
+
subagent: z.optional(z.boolean()),
|
|
38304
|
+
model: z.optional(z.string()),
|
|
38305
|
+
commandExecutionPolicy: z.optional(z.string()),
|
|
38306
|
+
mcpServers: z.optional(z.array(z.unknown())),
|
|
38307
|
+
skills: z.optional(z.array(z.string())),
|
|
38308
|
+
plugins: z.optional(z.array(z.string())),
|
|
38309
|
+
hidden: z.optional(z.boolean()),
|
|
38310
|
+
inheritMcp: z.optional(z.boolean())
|
|
38311
|
+
});
|
|
38312
|
+
/**
|
|
38313
|
+
* Shared custom-agent (subagent) implementation for Google Antigravity 2.0,
|
|
38314
|
+
* used by the IDE, the CLI and plugin bundles.
|
|
38315
|
+
*
|
|
38316
|
+
* Antigravity discovers agents at `.agents/agents/<name>.md` (project) and
|
|
38317
|
+
* `~/.gemini/config/agents/<name>.md` (global, shared by the IDE and the CLI
|
|
38318
|
+
* exactly like `~/.gemini/config/hooks.json`). The directory form
|
|
38319
|
+
* (`<name>/agent.md`) is an equivalent alternative upstream; rulesync emits and
|
|
38320
|
+
* imports the flat file form. The body after the frontmatter is the agent's
|
|
38321
|
+
* system prompt.
|
|
38322
|
+
*
|
|
38323
|
+
* Concrete subclasses only supply the rulesync target name they answer to via
|
|
38324
|
+
* {@link AntigravitySharedSubagent.getToolTarget} and, where the shared file is
|
|
38325
|
+
* not involved, the sections they read via
|
|
38326
|
+
* {@link AntigravitySharedSubagent.getReadSectionKeys}.
|
|
38327
|
+
*
|
|
38328
|
+
* @see https://antigravity.google/docs/subagents
|
|
38329
|
+
*/
|
|
38330
|
+
var AntigravitySharedSubagent = class extends ToolSubagent {
|
|
38331
|
+
frontmatter;
|
|
38332
|
+
body;
|
|
38333
|
+
constructor({ frontmatter, body, fileContent, ...rest }) {
|
|
38334
|
+
if (rest.validate !== false) {
|
|
38335
|
+
const result = AntigravitySubagentFrontmatterSchema.safeParse(frontmatter);
|
|
38336
|
+
if (!result.success) throw new Error(`Invalid frontmatter in ${join(rest.relativeDirPath, rest.relativeFilePath)}: ${formatError(result.error)}`);
|
|
38337
|
+
}
|
|
38338
|
+
super({
|
|
38339
|
+
...rest,
|
|
38340
|
+
fileContent: fileContent ?? stringifyFrontmatter(body, frontmatter, { avoidBlockScalars: true })
|
|
38341
|
+
});
|
|
38342
|
+
this.frontmatter = frontmatter;
|
|
38343
|
+
this.body = body;
|
|
38344
|
+
}
|
|
38345
|
+
/** The rulesync target name this subagent answers to. */
|
|
38346
|
+
static getToolTarget() {
|
|
38347
|
+
throw new Error("Please implement this method in the subclass.");
|
|
38348
|
+
}
|
|
38349
|
+
/**
|
|
38350
|
+
* Tool-specific sections this target reads, in increasing precedence order.
|
|
38351
|
+
*
|
|
38352
|
+
* `antigravity-ide` and `antigravity-cli` write the very same file, so a
|
|
38353
|
+
* target that read only its own section would silently drop the other's keys
|
|
38354
|
+
* — and which one survived would depend on `--targets` order. Every target
|
|
38355
|
+
* therefore merges the shared `antigravity-ide` → `antigravity-cli` sections
|
|
38356
|
+
* (the CLI block wins, matching the fixed order the MCP feature already uses
|
|
38357
|
+
* for the same shared-output reason), and the plugin target layers its own
|
|
38358
|
+
* section on top of that. Only `getToolTarget()` decides which section an
|
|
38359
|
+
* import writes back into.
|
|
38360
|
+
*/
|
|
38361
|
+
static getReadSectionKeys() {
|
|
38362
|
+
return ["antigravity-ide", "antigravity-cli"];
|
|
38363
|
+
}
|
|
38364
|
+
static getSettablePaths({ global = false } = {}) {
|
|
38365
|
+
return { relativeDirPath: global ? ANTIGRAVITY_GLOBAL_AGENTS_DIR_PATH : ANTIGRAVITY_AGENTS_DIR_PATH };
|
|
38366
|
+
}
|
|
38367
|
+
getFrontmatter() {
|
|
38368
|
+
return this.frontmatter;
|
|
38369
|
+
}
|
|
38370
|
+
getBody() {
|
|
38371
|
+
return this.body;
|
|
38372
|
+
}
|
|
38373
|
+
toRulesyncSubagent() {
|
|
38374
|
+
const { name, description, ...restFields } = this.frontmatter;
|
|
38375
|
+
return new RulesyncSubagent({
|
|
38376
|
+
outputRoot: ".",
|
|
38377
|
+
frontmatter: {
|
|
38378
|
+
targets: ["*"],
|
|
38379
|
+
name,
|
|
38380
|
+
description,
|
|
38381
|
+
...Object.keys(restFields).length > 0 && { [this.constructor.getToolTarget()]: restFields }
|
|
38382
|
+
},
|
|
38383
|
+
body: this.body,
|
|
38384
|
+
relativeDirPath: RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH,
|
|
38385
|
+
relativeFilePath: this.getRelativeFilePath(),
|
|
38386
|
+
validate: true
|
|
38387
|
+
});
|
|
38388
|
+
}
|
|
38389
|
+
static fromRulesyncSubagent({ outputRoot = process.cwd(), rulesyncSubagent, validate = true, global = false }) {
|
|
38390
|
+
const rulesyncFrontmatter = rulesyncSubagent.getFrontmatter();
|
|
38391
|
+
const mergedSection = Object.assign({}, ...this.getReadSectionKeys().map((key) => rulesyncFrontmatter[key] ?? {}));
|
|
38392
|
+
const toolSection = this.filterToolSpecificSection(mergedSection, ["name", "description"]);
|
|
38393
|
+
const rawFrontmatter = {
|
|
38394
|
+
name: rulesyncFrontmatter.name,
|
|
38395
|
+
description: rulesyncFrontmatter.description || `${rulesyncFrontmatter.name} subagent`,
|
|
38396
|
+
...toolSection
|
|
38397
|
+
};
|
|
38398
|
+
const result = AntigravitySubagentFrontmatterSchema.safeParse(rawFrontmatter);
|
|
38399
|
+
if (!result.success) throw new Error(`Invalid ${this.getToolTarget()} subagent frontmatter in ${rulesyncSubagent.getRelativeFilePath()}: ${formatError(result.error)}`);
|
|
38400
|
+
const frontmatter = result.data;
|
|
38401
|
+
const body = rulesyncSubagent.getBody();
|
|
38402
|
+
const paths = this.getSettablePaths({ global });
|
|
38403
|
+
return new this({
|
|
38404
|
+
outputRoot,
|
|
38405
|
+
frontmatter,
|
|
38406
|
+
body,
|
|
38407
|
+
relativeDirPath: paths.relativeDirPath,
|
|
38408
|
+
relativeFilePath: rulesyncSubagent.getRelativeFilePath(),
|
|
38409
|
+
fileContent: stringifyFrontmatter(body, frontmatter, { avoidBlockScalars: true }),
|
|
38410
|
+
validate,
|
|
38411
|
+
global
|
|
38412
|
+
});
|
|
38413
|
+
}
|
|
38414
|
+
validate() {
|
|
38415
|
+
if (!this.frontmatter) return {
|
|
38416
|
+
success: true,
|
|
38417
|
+
error: null
|
|
38418
|
+
};
|
|
38419
|
+
const result = AntigravitySubagentFrontmatterSchema.safeParse(this.frontmatter);
|
|
38420
|
+
if (result.success) return {
|
|
38421
|
+
success: true,
|
|
38422
|
+
error: null
|
|
38423
|
+
};
|
|
38424
|
+
return {
|
|
38425
|
+
success: false,
|
|
38426
|
+
error: /* @__PURE__ */ new Error(`Invalid frontmatter in ${join(this.relativeDirPath, this.relativeFilePath)}: ${formatError(result.error)}`)
|
|
38427
|
+
};
|
|
38428
|
+
}
|
|
38429
|
+
static isTargetedByRulesyncSubagent(rulesyncSubagent) {
|
|
38430
|
+
return this.isTargetedByRulesyncSubagentDefault({
|
|
38431
|
+
rulesyncSubagent,
|
|
38432
|
+
toolTarget: this.getToolTarget()
|
|
38433
|
+
});
|
|
38434
|
+
}
|
|
38435
|
+
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
|
|
38436
|
+
const paths = this.getSettablePaths({ global });
|
|
38437
|
+
const filePath = join(outputRoot, paths.relativeDirPath, relativeFilePath);
|
|
38438
|
+
const fileContent = await readFileContent(filePath);
|
|
38439
|
+
const { frontmatter, body: content } = parseFrontmatter(fileContent, filePath);
|
|
38440
|
+
const result = AntigravitySubagentFrontmatterSchema.safeParse(frontmatter);
|
|
38441
|
+
if (!result.success) throw new Error(`Invalid frontmatter in ${filePath}: ${formatError(result.error)}`);
|
|
38442
|
+
return new this({
|
|
38443
|
+
outputRoot,
|
|
38444
|
+
relativeDirPath: paths.relativeDirPath,
|
|
38445
|
+
relativeFilePath,
|
|
38446
|
+
frontmatter: result.data,
|
|
38447
|
+
body: content.trim(),
|
|
38448
|
+
fileContent,
|
|
38449
|
+
validate,
|
|
38450
|
+
global
|
|
38451
|
+
});
|
|
38452
|
+
}
|
|
38453
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
38454
|
+
return new this({
|
|
38455
|
+
outputRoot,
|
|
38456
|
+
relativeDirPath,
|
|
38457
|
+
relativeFilePath,
|
|
38458
|
+
frontmatter: {
|
|
38459
|
+
name: "",
|
|
38460
|
+
description: ""
|
|
38461
|
+
},
|
|
38462
|
+
body: "",
|
|
38463
|
+
fileContent: "",
|
|
38464
|
+
validate: false,
|
|
38465
|
+
global
|
|
38466
|
+
});
|
|
38467
|
+
}
|
|
38468
|
+
};
|
|
38469
|
+
//#endregion
|
|
38470
|
+
//#region src/features/subagents/antigravity-cli-subagent.ts
|
|
38471
|
+
/**
|
|
38472
|
+
* Google Antigravity CLI custom agent (subagent), shipped in CLI v1.1.6.
|
|
38473
|
+
*
|
|
38474
|
+
* Shares all behavior with {@link AntigravitySharedSubagent}; the CLI reads the
|
|
38475
|
+
* same `.agents/agents/` and `~/.gemini/config/agents/` roots as the IDE and
|
|
38476
|
+
* exposes them through the `agy agents` subcommand and the `--agent` flag. It
|
|
38477
|
+
* answers to the `antigravity-cli` target.
|
|
38478
|
+
*/
|
|
38479
|
+
var AntigravityCliSubagent = class extends AntigravitySharedSubagent {
|
|
38480
|
+
static getToolTarget() {
|
|
38481
|
+
return "antigravity-cli";
|
|
38482
|
+
}
|
|
38483
|
+
};
|
|
38484
|
+
//#endregion
|
|
38485
|
+
//#region src/features/subagents/antigravity-ide-subagent.ts
|
|
38486
|
+
/**
|
|
38487
|
+
* Google Antigravity IDE custom agent (subagent).
|
|
38488
|
+
*
|
|
38489
|
+
* Shares all behavior with {@link AntigravitySharedSubagent} — the subagents
|
|
38490
|
+
* documentation is product-wide and lists the same `.agents/agents/` and
|
|
38491
|
+
* `~/.gemini/config/agents/` roots for the IDE and the CLI. It answers to the
|
|
38492
|
+
* `antigravity-ide` target.
|
|
38493
|
+
*/
|
|
38494
|
+
var AntigravityIdeSubagent = class extends AntigravitySharedSubagent {
|
|
38495
|
+
static getToolTarget() {
|
|
38496
|
+
return "antigravity-ide";
|
|
38497
|
+
}
|
|
38498
|
+
};
|
|
38499
|
+
//#endregion
|
|
38500
|
+
//#region src/features/subagents/antigravity-plugin-subagent.ts
|
|
38501
|
+
/**
|
|
38502
|
+
* Custom agent inside an Antigravity plugin bundle
|
|
38503
|
+
* (`<plugin_name>/agents/<name>.md`). Plugin bundles are project-scope output
|
|
38504
|
+
* only; they are staged into `~/.gemini/antigravity-cli/plugins/` by the user.
|
|
38505
|
+
*
|
|
38506
|
+
* @see https://antigravity.google/docs/cli/plugins
|
|
38507
|
+
*/
|
|
38508
|
+
var AntigravityPluginSubagent = class extends AntigravitySharedSubagent {
|
|
38509
|
+
static getToolTarget() {
|
|
38510
|
+
return "antigravity-plugin";
|
|
38511
|
+
}
|
|
38512
|
+
static getReadSectionKeys() {
|
|
38513
|
+
return [...super.getReadSectionKeys(), "antigravity-plugin"];
|
|
38514
|
+
}
|
|
38515
|
+
static getSettablePaths() {
|
|
38516
|
+
return { relativeDirPath: ANTIGRAVITY_PLUGIN_AGENTS_DIR };
|
|
38517
|
+
}
|
|
38518
|
+
};
|
|
38519
|
+
//#endregion
|
|
36706
38520
|
//#region src/features/subagents/augmentcode-subagent.ts
|
|
36707
38521
|
const AugmentcodeSubagentFrontmatterSchema = z.looseObject({
|
|
36708
38522
|
name: z.string(),
|
|
@@ -38516,7 +40330,7 @@ def register(ctx):
|
|
|
38516
40330
|
_register_subagent(ctx, subagent)
|
|
38517
40331
|
`;
|
|
38518
40332
|
}
|
|
38519
|
-
function getEnabledPluginConfigContent(currentContent) {
|
|
40333
|
+
function getEnabledPluginConfigContent({ currentContent, global }) {
|
|
38520
40334
|
const config = parseSharedConfig({
|
|
38521
40335
|
format: "yaml",
|
|
38522
40336
|
fileContent: currentContent
|
|
@@ -38524,7 +40338,7 @@ function getEnabledPluginConfigContent(currentContent) {
|
|
|
38524
40338
|
const plugins = config.plugins && typeof config.plugins === "object" ? config.plugins : {};
|
|
38525
40339
|
const enabled = Array.isArray(plugins.enabled) ? plugins.enabled : [];
|
|
38526
40340
|
return applySharedConfigPatch({
|
|
38527
|
-
fileKey:
|
|
40341
|
+
fileKey: getHermesagentConfigSharedFileKey({ global }),
|
|
38528
40342
|
feature: "subagents",
|
|
38529
40343
|
existingContent: currentContent,
|
|
38530
40344
|
patch: { plugins: {
|
|
@@ -38575,6 +40389,10 @@ var HermesagentSubagent = class HermesagentSubagent extends ToolSubagent {
|
|
|
38575
40389
|
return !targets || targets.includes("*") || targets.includes("hermesagent");
|
|
38576
40390
|
}
|
|
38577
40391
|
static fromRulesyncSubagents({ rulesyncSubagents, outputRoot, global = false }) {
|
|
40392
|
+
const pluginDirPath = getHermesagentRelativeDirPath({
|
|
40393
|
+
global,
|
|
40394
|
+
relativeDirPath: HERMESAGENT_RULESYNC_SUBAGENTS_PLUGIN_DIR_PATH
|
|
40395
|
+
});
|
|
38578
40396
|
return [
|
|
38579
40397
|
...rulesyncSubagents.map((rulesyncSubagent) => HermesagentSubagent.fromRulesyncSubagent({
|
|
38580
40398
|
relativeDirPath: this.getSettablePaths({ global }).relativeDirPath,
|
|
@@ -38583,20 +40401,14 @@ var HermesagentSubagent = class HermesagentSubagent extends ToolSubagent {
|
|
|
38583
40401
|
global
|
|
38584
40402
|
})),
|
|
38585
40403
|
new HermesagentSubagent({
|
|
38586
|
-
relativeDirPath:
|
|
38587
|
-
global,
|
|
38588
|
-
relativeDirPath: HERMESAGENT_RULESYNC_SUBAGENTS_PLUGIN_DIR_PATH
|
|
38589
|
-
}),
|
|
40404
|
+
relativeDirPath: pluginDirPath,
|
|
38590
40405
|
relativeFilePath: basename(HERMESAGENT_RULESYNC_SUBAGENTS_PLUGIN_MANIFEST_PATH),
|
|
38591
40406
|
fileContent: "",
|
|
38592
40407
|
outputRoot,
|
|
38593
40408
|
global
|
|
38594
40409
|
}),
|
|
38595
40410
|
new HermesagentSubagent({
|
|
38596
|
-
relativeDirPath:
|
|
38597
|
-
global,
|
|
38598
|
-
relativeDirPath: HERMESAGENT_RULESYNC_SUBAGENTS_PLUGIN_DIR_PATH
|
|
38599
|
-
}),
|
|
40411
|
+
relativeDirPath: pluginDirPath,
|
|
38600
40412
|
relativeFilePath: basename(HERMESAGENT_RULESYNC_SUBAGENTS_PLUGIN_INIT_PATH),
|
|
38601
40413
|
fileContent: "",
|
|
38602
40414
|
outputRoot,
|
|
@@ -38636,14 +40448,12 @@ var HermesagentSubagent = class HermesagentSubagent extends ToolSubagent {
|
|
|
38636
40448
|
* shared `~/.hermes/config.yaml` (enabling the `rulesync-subagents` plugin),
|
|
38637
40449
|
* so the write must be declared for the shared-file order derivation.
|
|
38638
40450
|
*/
|
|
38639
|
-
|
|
38640
|
-
|
|
38641
|
-
|
|
38642
|
-
|
|
38643
|
-
|
|
38644
|
-
|
|
38645
|
-
relativeFilePath: basename(HERMESAGENT_CONFIG_FILE_PATH)
|
|
38646
|
-
}] : [];
|
|
40451
|
+
/**
|
|
40452
|
+
* `config.yaml` under every spelling the global profile root can take.
|
|
40453
|
+
* @see getHermesagentSharedConfigWritePaths
|
|
40454
|
+
*/
|
|
40455
|
+
static getExtraSharedWritePaths() {
|
|
40456
|
+
return getHermesagentSharedConfigWritePaths();
|
|
38647
40457
|
}
|
|
38648
40458
|
static getSettablePathsForRulesyncSubagent(rulesyncSubagent) {
|
|
38649
40459
|
return [join(HERMESAGENT_RULESYNC_SUBAGENTS_DIR_PATH, `${subagentSlug(rulesyncSubagent.getRelativePathFromCwd())}.json`)];
|
|
@@ -38676,7 +40486,10 @@ var HermesagentSubagent = class HermesagentSubagent extends ToolSubagent {
|
|
|
38676
40486
|
}
|
|
38677
40487
|
setFileContent(newFileContent) {
|
|
38678
40488
|
if (this.getRelativeFilePath() === basename(HERMESAGENT_CONFIG_FILE_PATH)) {
|
|
38679
|
-
super.setFileContent(getEnabledPluginConfigContent(
|
|
40489
|
+
super.setFileContent(getEnabledPluginConfigContent({
|
|
40490
|
+
currentContent: newFileContent,
|
|
40491
|
+
global: this.global
|
|
40492
|
+
}));
|
|
38680
40493
|
return;
|
|
38681
40494
|
}
|
|
38682
40495
|
super.setFileContent(newFileContent);
|
|
@@ -38684,7 +40497,10 @@ var HermesagentSubagent = class HermesagentSubagent extends ToolSubagent {
|
|
|
38684
40497
|
getFileContent() {
|
|
38685
40498
|
if (this.getRelativeFilePath() === basename(HERMESAGENT_RULESYNC_SUBAGENTS_PLUGIN_MANIFEST_PATH)) return getPluginManifestContent();
|
|
38686
40499
|
if (this.getRelativeFilePath() === basename(HERMESAGENT_RULESYNC_SUBAGENTS_PLUGIN_INIT_PATH)) return getPluginInitContent();
|
|
38687
|
-
if (this.getRelativeFilePath() === basename(HERMESAGENT_CONFIG_FILE_PATH)) return getEnabledPluginConfigContent(
|
|
40500
|
+
if (this.getRelativeFilePath() === basename(HERMESAGENT_CONFIG_FILE_PATH)) return getEnabledPluginConfigContent({
|
|
40501
|
+
currentContent: super.getFileContent(),
|
|
40502
|
+
global: this.global
|
|
40503
|
+
});
|
|
38688
40504
|
return super.getFileContent();
|
|
38689
40505
|
}
|
|
38690
40506
|
};
|
|
@@ -40280,6 +42096,30 @@ const toolSubagentFactories = /* @__PURE__ */ new Map([
|
|
|
40280
42096
|
filePattern: "*.md"
|
|
40281
42097
|
}
|
|
40282
42098
|
}],
|
|
42099
|
+
["antigravity-cli", {
|
|
42100
|
+
class: AntigravityCliSubagent,
|
|
42101
|
+
meta: {
|
|
42102
|
+
supportsSimulated: false,
|
|
42103
|
+
supportsGlobal: true,
|
|
42104
|
+
filePattern: "*.md"
|
|
42105
|
+
}
|
|
42106
|
+
}],
|
|
42107
|
+
["antigravity-ide", {
|
|
42108
|
+
class: AntigravityIdeSubagent,
|
|
42109
|
+
meta: {
|
|
42110
|
+
supportsSimulated: false,
|
|
42111
|
+
supportsGlobal: true,
|
|
42112
|
+
filePattern: "*.md"
|
|
42113
|
+
}
|
|
42114
|
+
}],
|
|
42115
|
+
["antigravity-plugin", {
|
|
42116
|
+
class: AntigravityPluginSubagent,
|
|
42117
|
+
meta: {
|
|
42118
|
+
supportsSimulated: false,
|
|
42119
|
+
supportsGlobal: false,
|
|
42120
|
+
filePattern: "*.md"
|
|
42121
|
+
}
|
|
42122
|
+
}],
|
|
40283
42123
|
["augmentcode", {
|
|
40284
42124
|
class: AugmentcodeSubagent,
|
|
40285
42125
|
meta: {
|
|
@@ -42493,6 +44333,7 @@ var CodexcliRule = class CodexcliRule extends ToolRule {
|
|
|
42493
44333
|
const CopilotRuleFrontmatterSchema = z.object({
|
|
42494
44334
|
description: z.optional(z.string()),
|
|
42495
44335
|
applyTo: z.optional(z.string()),
|
|
44336
|
+
name: z.optional(z.string()),
|
|
42496
44337
|
excludeAgent: z.optional(z.union([
|
|
42497
44338
|
z.literal("code-review"),
|
|
42498
44339
|
z.literal("cloud-agent"),
|
|
@@ -42550,7 +44391,10 @@ var CopilotRule = class CopilotRule extends ToolRule {
|
|
|
42550
44391
|
root: this.isRoot(),
|
|
42551
44392
|
description: this.frontmatter.description,
|
|
42552
44393
|
globs,
|
|
42553
|
-
...this.frontmatter.excludeAgent && { copilot: {
|
|
44394
|
+
...(this.frontmatter.excludeAgent || this.frontmatter.name) && { copilot: {
|
|
44395
|
+
...this.frontmatter.excludeAgent && { excludeAgent: this.frontmatter.excludeAgent },
|
|
44396
|
+
...this.frontmatter.name && { name: this.frontmatter.name }
|
|
44397
|
+
} }
|
|
42554
44398
|
};
|
|
42555
44399
|
const relativeFilePath = this.getRelativeFilePath().replace(/\.instructions\.md$/, ".md");
|
|
42556
44400
|
return new RulesyncRule({
|
|
@@ -42569,7 +44413,8 @@ var CopilotRule = class CopilotRule extends ToolRule {
|
|
|
42569
44413
|
const copilotFrontmatter = {
|
|
42570
44414
|
description: rulesyncFrontmatter.description,
|
|
42571
44415
|
applyTo: rulesyncFrontmatter.globs?.length ? rulesyncFrontmatter.globs.join(",") : void 0,
|
|
42572
|
-
excludeAgent: rulesyncFrontmatter.copilot?.excludeAgent
|
|
44416
|
+
excludeAgent: rulesyncFrontmatter.copilot?.excludeAgent,
|
|
44417
|
+
name: rulesyncFrontmatter.copilot?.name
|
|
42573
44418
|
};
|
|
42574
44419
|
const body = rulesyncRule.getBody();
|
|
42575
44420
|
if (root) return new CopilotRule({
|
|
@@ -43734,10 +45579,13 @@ var JunieRule = class JunieRule extends ToolRule {
|
|
|
43734
45579
|
//#region src/features/rules/kilo-rule.ts
|
|
43735
45580
|
var KiloRule = class KiloRule extends ToolRule {
|
|
43736
45581
|
static getSettablePaths({ global, excludeToolDir } = {}) {
|
|
43737
|
-
if (global) return {
|
|
43738
|
-
|
|
43739
|
-
|
|
43740
|
-
|
|
45582
|
+
if (global) return {
|
|
45583
|
+
root: {
|
|
45584
|
+
relativeDirPath: buildToolPath(KILO_GLOBAL_DIR, ".", excludeToolDir),
|
|
45585
|
+
relativeFilePath: KILO_RULE_FILE_NAME
|
|
45586
|
+
},
|
|
45587
|
+
nonRoot: { relativeDirPath: buildToolPath(KILO_DIR, KILO_RULES_DIR_NAME, excludeToolDir) }
|
|
45588
|
+
};
|
|
43741
45589
|
return {
|
|
43742
45590
|
root: {
|
|
43743
45591
|
relativeDirPath: ".",
|
|
@@ -45322,6 +47170,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
45322
47170
|
extension: "md",
|
|
45323
47171
|
supportsGlobal: false,
|
|
45324
47172
|
ruleDiscoveryMode: "toon",
|
|
47173
|
+
collisionPolicy: "compose",
|
|
45325
47174
|
additionalConventions: {
|
|
45326
47175
|
commands: { commandClass: AgentsmdCommand },
|
|
45327
47176
|
subagents: { subagentClass: AgentsmdSubagent },
|
|
@@ -45342,7 +47191,8 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
45342
47191
|
meta: {
|
|
45343
47192
|
extension: "md",
|
|
45344
47193
|
supportsGlobal: true,
|
|
45345
|
-
ruleDiscoveryMode: "toon"
|
|
47194
|
+
ruleDiscoveryMode: "toon",
|
|
47195
|
+
collisionPolicy: "compose"
|
|
45346
47196
|
}
|
|
45347
47197
|
}],
|
|
45348
47198
|
["antigravity-cli", {
|
|
@@ -45419,7 +47269,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
45419
47269
|
extension: "md",
|
|
45420
47270
|
supportsGlobal: true,
|
|
45421
47271
|
ruleDiscoveryMode: "auto",
|
|
45422
|
-
|
|
47272
|
+
collisionPolicy: "fold"
|
|
45423
47273
|
}
|
|
45424
47274
|
}],
|
|
45425
47275
|
["copilot", {
|
|
@@ -45452,7 +47302,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
45452
47302
|
extension: "md",
|
|
45453
47303
|
supportsGlobal: true,
|
|
45454
47304
|
ruleDiscoveryMode: "auto",
|
|
45455
|
-
|
|
47305
|
+
collisionPolicy: "fold"
|
|
45456
47306
|
}
|
|
45457
47307
|
}],
|
|
45458
47308
|
["factorydroid", {
|
|
@@ -45460,7 +47310,8 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
45460
47310
|
meta: {
|
|
45461
47311
|
extension: "md",
|
|
45462
47312
|
supportsGlobal: true,
|
|
45463
|
-
ruleDiscoveryMode: "toon"
|
|
47313
|
+
ruleDiscoveryMode: "toon",
|
|
47314
|
+
collisionPolicy: "compose"
|
|
45464
47315
|
}
|
|
45465
47316
|
}],
|
|
45466
47317
|
["goose", {
|
|
@@ -45469,7 +47320,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
45469
47320
|
extension: "md",
|
|
45470
47321
|
supportsGlobal: true,
|
|
45471
47322
|
ruleDiscoveryMode: "auto",
|
|
45472
|
-
|
|
47323
|
+
collisionPolicy: "fold"
|
|
45473
47324
|
}
|
|
45474
47325
|
}],
|
|
45475
47326
|
["hermesagent", {
|
|
@@ -45478,7 +47329,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
45478
47329
|
extension: "md",
|
|
45479
47330
|
supportsGlobal: false,
|
|
45480
47331
|
ruleDiscoveryMode: "auto",
|
|
45481
|
-
|
|
47332
|
+
collisionPolicy: "fold"
|
|
45482
47333
|
}
|
|
45483
47334
|
}],
|
|
45484
47335
|
["grokcli", {
|
|
@@ -45495,7 +47346,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
45495
47346
|
extension: "md",
|
|
45496
47347
|
supportsGlobal: true,
|
|
45497
47348
|
ruleDiscoveryMode: "auto",
|
|
45498
|
-
|
|
47349
|
+
collisionPolicy: "fold"
|
|
45499
47350
|
}
|
|
45500
47351
|
}],
|
|
45501
47352
|
["kilo", {
|
|
@@ -45504,7 +47355,8 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
45504
47355
|
extension: "md",
|
|
45505
47356
|
supportsGlobal: true,
|
|
45506
47357
|
ruleDiscoveryMode: "auto",
|
|
45507
|
-
mcpInstructionsRegistrar: KiloMcp
|
|
47358
|
+
mcpInstructionsRegistrar: KiloMcp,
|
|
47359
|
+
collisionPolicy: "compose"
|
|
45508
47360
|
}
|
|
45509
47361
|
}],
|
|
45510
47362
|
["kimi-code", {
|
|
@@ -45513,7 +47365,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
45513
47365
|
extension: "md",
|
|
45514
47366
|
supportsGlobal: true,
|
|
45515
47367
|
ruleDiscoveryMode: "auto",
|
|
45516
|
-
|
|
47368
|
+
collisionPolicy: "fold"
|
|
45517
47369
|
}
|
|
45518
47370
|
}],
|
|
45519
47371
|
["kiro", {
|
|
@@ -45546,7 +47398,8 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
45546
47398
|
extension: "md",
|
|
45547
47399
|
supportsGlobal: true,
|
|
45548
47400
|
ruleDiscoveryMode: "toon",
|
|
45549
|
-
mcpInstructionsRegistrar: OpencodeMcp
|
|
47401
|
+
mcpInstructionsRegistrar: OpencodeMcp,
|
|
47402
|
+
collisionPolicy: "compose"
|
|
45550
47403
|
}
|
|
45551
47404
|
}],
|
|
45552
47405
|
["pi", {
|
|
@@ -45555,7 +47408,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
45555
47408
|
extension: "md",
|
|
45556
47409
|
supportsGlobal: true,
|
|
45557
47410
|
ruleDiscoveryMode: "auto",
|
|
45558
|
-
|
|
47411
|
+
collisionPolicy: "fold"
|
|
45559
47412
|
}
|
|
45560
47413
|
}],
|
|
45561
47414
|
["qwencode", {
|
|
@@ -45573,7 +47426,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
45573
47426
|
extension: "md",
|
|
45574
47427
|
supportsGlobal: true,
|
|
45575
47428
|
ruleDiscoveryMode: "auto",
|
|
45576
|
-
|
|
47429
|
+
collisionPolicy: "fold"
|
|
45577
47430
|
}
|
|
45578
47431
|
}],
|
|
45579
47432
|
["replit", {
|
|
@@ -45630,7 +47483,7 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
45630
47483
|
extension: "md",
|
|
45631
47484
|
supportsGlobal: false,
|
|
45632
47485
|
ruleDiscoveryMode: "toon",
|
|
45633
|
-
|
|
47486
|
+
collisionPolicy: "fold"
|
|
45634
47487
|
}
|
|
45635
47488
|
}],
|
|
45636
47489
|
["devin", {
|
|
@@ -45699,16 +47552,23 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
45699
47552
|
const nonLocalRootRules = rulesyncRules.filter((rule) => !rule.getFrontmatter().localRoot);
|
|
45700
47553
|
const factory = this.getFactory(this.toolTarget);
|
|
45701
47554
|
const { meta } = factory;
|
|
45702
|
-
const
|
|
47555
|
+
const convertedRules = nonLocalRootRules.map((rulesyncRule) => {
|
|
45703
47556
|
if (!factory.class.isTargetedByRulesyncRule(rulesyncRule)) return null;
|
|
45704
|
-
return
|
|
45705
|
-
|
|
45706
|
-
|
|
45707
|
-
|
|
45708
|
-
|
|
45709
|
-
|
|
47557
|
+
return {
|
|
47558
|
+
toolRule: factory.class.fromRulesyncRule({
|
|
47559
|
+
outputRoot: this.outputRoot,
|
|
47560
|
+
rulesyncRule,
|
|
47561
|
+
validate: true,
|
|
47562
|
+
global: this.global
|
|
47563
|
+
}),
|
|
47564
|
+
rulesyncRule
|
|
47565
|
+
};
|
|
45710
47566
|
}).filter((rule) => rule !== null);
|
|
45711
|
-
|
|
47567
|
+
this.mergeRulesByOutputPath({
|
|
47568
|
+
convertedRules,
|
|
47569
|
+
collisionPolicy: meta.collisionPolicy ?? "preserve"
|
|
47570
|
+
});
|
|
47571
|
+
const toolRules = convertedRules.map(({ toolRule }) => toolRule);
|
|
45712
47572
|
this.applyLocalRootRules({
|
|
45713
47573
|
toolRules,
|
|
45714
47574
|
localRootRules,
|
|
@@ -45726,7 +47586,12 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
45726
47586
|
toolRules,
|
|
45727
47587
|
factory
|
|
45728
47588
|
});
|
|
45729
|
-
|
|
47589
|
+
const outputFiles = [...toolRules, ...extraFiles];
|
|
47590
|
+
this.warnForOutputPathCollisions({
|
|
47591
|
+
outputFiles,
|
|
47592
|
+
convertedRules
|
|
47593
|
+
});
|
|
47594
|
+
return outputFiles;
|
|
45730
47595
|
}
|
|
45731
47596
|
/**
|
|
45732
47597
|
* Handle localRoot rules (only in non-global mode and when enabled). Mutates
|
|
@@ -45813,39 +47678,80 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
45813
47678
|
});
|
|
45814
47679
|
}
|
|
45815
47680
|
/**
|
|
45816
|
-
*
|
|
47681
|
+
* Reconcile rules that resolve to the same output path.
|
|
45817
47682
|
*
|
|
45818
|
-
*
|
|
45819
|
-
*
|
|
45820
|
-
*
|
|
45821
|
-
* `.
|
|
45822
|
-
*
|
|
45823
|
-
*
|
|
47683
|
+
* Multiple root fragments are composed for tools that emit a fixed root file.
|
|
47684
|
+
* The `fold` policy is for tools whose rules engine reads only one root file and
|
|
47685
|
+
* neither scans a modular rules directory nor follows references. For example,
|
|
47686
|
+
* dcode reads `.deepagents/AGENTS.md`, while Warp reads root or subdirectory
|
|
47687
|
+
* `AGENTS.md` files but never `.warp/memories/`. Those adapters must fold every
|
|
47688
|
+
* body into one instance because last-writer-wins would silently drop content.
|
|
47689
|
+
* Plain-Markdown adapters can opt into `compose` for colliding modular outputs.
|
|
45824
47690
|
*
|
|
45825
|
-
*
|
|
45826
|
-
*
|
|
45827
|
-
*
|
|
47691
|
+
* A generated root rule becomes the merge target when present. A `fold` group
|
|
47692
|
+
* without one uses its first rule. A group only composes when every rendered
|
|
47693
|
+
* fragment is plain Markdown — a fragment carrying its own frontmatter block
|
|
47694
|
+
* (e.g. Amp's `globs:` gate) would end up mid-body where the tool ignores it.
|
|
47695
|
+
* Root-involved collisions that cannot be composed safely fail; other
|
|
47696
|
+
* collisions remain separate and are reported by the final output-path check.
|
|
47697
|
+
* Mutates `convertedRules` in place.
|
|
45828
47698
|
*/
|
|
45829
|
-
|
|
45830
|
-
if (
|
|
47699
|
+
mergeRulesByOutputPath({ convertedRules, collisionPolicy }) {
|
|
47700
|
+
if (convertedRules.length <= 1) return;
|
|
45831
47701
|
const groups = /* @__PURE__ */ new Map();
|
|
45832
|
-
for (const
|
|
45833
|
-
const path = join(
|
|
47702
|
+
for (const conversion of convertedRules) {
|
|
47703
|
+
const path = join(conversion.toolRule.getRelativeDirPath(), conversion.toolRule.getRelativeFilePath());
|
|
45834
47704
|
const group = groups.get(path);
|
|
45835
|
-
if (group) group.push(
|
|
45836
|
-
else groups.set(path, [
|
|
47705
|
+
if (group) group.push(conversion);
|
|
47706
|
+
else groups.set(path, [conversion]);
|
|
45837
47707
|
}
|
|
45838
47708
|
const survivors = /* @__PURE__ */ new Set();
|
|
45839
|
-
for (const group of groups
|
|
45840
|
-
|
|
47709
|
+
for (const [path, group] of groups) {
|
|
47710
|
+
if (group.length === 1) {
|
|
47711
|
+
const conversion = group[0];
|
|
47712
|
+
if (conversion) {
|
|
47713
|
+
if (collisionPolicy === "fold") conversion.toolRule.setFileContent(conversion.toolRule.getFileContent().trim());
|
|
47714
|
+
survivors.add(conversion);
|
|
47715
|
+
}
|
|
47716
|
+
continue;
|
|
47717
|
+
}
|
|
47718
|
+
const rootConversion = group.find(({ toolRule }) => toolRule.isRoot());
|
|
47719
|
+
const allGeneratedRulesAreRoots = group.every(({ toolRule }) => toolRule.isRoot());
|
|
47720
|
+
const hasSourceRoot = group.some(({ rulesyncRule }) => rulesyncRule.getFrontmatter().root === true);
|
|
47721
|
+
const allFragmentsArePlain = group.every(({ toolRule }) => !/^---\r?\n/.test(toolRule.getFileContent()));
|
|
47722
|
+
const shouldCompose = (collisionPolicy === "fold" || collisionPolicy === "compose" || allGeneratedRulesAreRoots) && allFragmentsArePlain;
|
|
47723
|
+
if (!shouldCompose && hasSourceRoot) throw new Error(`Multiple generated rules resolve to output path '${path}' for target '${this.toolTarget}', but this target cannot safely compose a collision involving a root rule. Source rules: ${formatRulePaths(group.map(({ rulesyncRule }) => rulesyncRule))}`);
|
|
47724
|
+
if (!shouldCompose) {
|
|
47725
|
+
for (const conversion of group) survivors.add(conversion);
|
|
47726
|
+
continue;
|
|
47727
|
+
}
|
|
47728
|
+
const target = rootConversion ?? group[0];
|
|
45841
47729
|
if (!target) continue;
|
|
45842
|
-
const mergedContent = [target, ...group.filter((rule) => rule !== target)].map((
|
|
45843
|
-
target.setFileContent(mergedContent);
|
|
47730
|
+
const mergedContent = [target, ...group.filter((rule) => rule !== target)].map(({ toolRule }) => toolRule.getFileContent().trim()).filter((content) => content.length > 0).join("\n\n");
|
|
47731
|
+
target.toolRule.setFileContent(mergedContent);
|
|
45844
47732
|
survivors.add(target);
|
|
45845
47733
|
}
|
|
45846
|
-
for (let i =
|
|
45847
|
-
const
|
|
45848
|
-
if (
|
|
47734
|
+
for (let i = convertedRules.length - 1; i >= 0; i--) {
|
|
47735
|
+
const conversion = convertedRules[i];
|
|
47736
|
+
if (conversion && !survivors.has(conversion)) convertedRules.splice(i, 1);
|
|
47737
|
+
}
|
|
47738
|
+
}
|
|
47739
|
+
warnForOutputPathCollisions({ outputFiles, convertedRules }) {
|
|
47740
|
+
const seen = /* @__PURE__ */ new Map();
|
|
47741
|
+
const describeSource = (file) => {
|
|
47742
|
+
const source = convertedRules.find(({ toolRule }) => toolRule === file)?.rulesyncRule;
|
|
47743
|
+
return source ? formatRulePaths([source]) : join(file.getRelativeDirPath(), file.getRelativeFilePath());
|
|
47744
|
+
};
|
|
47745
|
+
for (const file of outputFiles) {
|
|
47746
|
+
const path = join(file.getRelativeDirPath(), file.getRelativeFilePath());
|
|
47747
|
+
const key = path.toLowerCase();
|
|
47748
|
+
const previous = seen.get(key);
|
|
47749
|
+
if (previous) {
|
|
47750
|
+
const previousPath = join(previous.getRelativeDirPath(), previous.getRelativeFilePath());
|
|
47751
|
+
const pathDescription = previousPath === path ? `'${path}'` : `'${previousPath}' and '${path}' (compared case-insensitively, as on macOS and Windows)`;
|
|
47752
|
+
this.logger.warn(`Both ${describeSource(previous)} and ${describeSource(file)} generate to ${pathDescription}; the last one wins wherever they collide.`);
|
|
47753
|
+
}
|
|
47754
|
+
seen.set(key, file);
|
|
45849
47755
|
}
|
|
45850
47756
|
}
|
|
45851
47757
|
/**
|
|
@@ -46005,14 +47911,13 @@ As this project's AI coding tool, you must follow the additional conventions bel
|
|
|
46005
47911
|
}));
|
|
46006
47912
|
const factory = this.getFactory(this.toolTarget);
|
|
46007
47913
|
const targetedRootRules = rulesyncRules.filter((rule) => rule.getFrontmatter().root).filter((rule) => factory.class.isTargetedByRulesyncRule(rule));
|
|
46008
|
-
if (targetedRootRules.length > 1) throw new Error(`Multiple root rulesync rules found for target '${this.toolTarget}': ${formatRulePaths(targetedRootRules)}`);
|
|
46009
47914
|
if (targetedRootRules.length === 0 && rulesyncRules.length > 0) this.logger.warn(`No root rulesync rule file found for target '${this.toolTarget}'. Consider adding 'root: true' to one of your rule files in ${RULESYNC_RULES_RELATIVE_DIR_PATH}.`);
|
|
46010
47915
|
const targetedLocalRootRules = rulesyncRules.filter((rule) => rule.getFrontmatter().localRoot).filter((rule) => factory.class.isTargetedByRulesyncRule(rule));
|
|
46011
47916
|
if (targetedLocalRootRules.length > 1) throw new Error(`Multiple localRoot rules found for target '${this.toolTarget}': ${formatRulePaths(targetedLocalRootRules)}. Only one rule can have localRoot: true`);
|
|
46012
47917
|
if (targetedLocalRootRules.length > 0 && targetedRootRules.length === 0) throw new Error(`localRoot: true requires a root: true rule to exist for target '${this.toolTarget}' (found in ${formatRulePaths(targetedLocalRootRules)})`);
|
|
46013
47918
|
if (this.global) {
|
|
46014
47919
|
const globalPaths = factory.class.getSettablePaths({ global: true });
|
|
46015
|
-
const supportsGlobalNonRoot = "nonRoot" in globalPaths && globalPaths.nonRoot !== null || factory.meta.supportsGlobal && factory.meta.
|
|
47920
|
+
const supportsGlobalNonRoot = "nonRoot" in globalPaths && globalPaths.nonRoot !== null || factory.meta.supportsGlobal && factory.meta.collisionPolicy === "fold";
|
|
46016
47921
|
const nonRootRules = rulesyncRules.filter((rule) => !rule.getFrontmatter().root && !rule.getFrontmatter().localRoot && factory.class.isTargetedByRulesyncRule(rule));
|
|
46017
47922
|
if (nonRootRules.length > 0 && !supportsGlobalNonRoot) this.logger.warn(`${nonRootRules.length} non-root rulesync rules found, but it's in global mode, so ignoring them: ${formatRulePaths(nonRootRules)}`);
|
|
46018
47923
|
if (targetedLocalRootRules.length > 0) this.logger.warn(`${targetedLocalRootRules.length} localRoot rules found, but localRoot is not supported in global mode, ignoring them: ${formatRulePaths(targetedLocalRootRules)}`);
|
|
@@ -46255,14 +48160,36 @@ async function assertPluginRootSafe(params) {
|
|
|
46255
48160
|
}
|
|
46256
48161
|
//#endregion
|
|
46257
48162
|
//#region src/utils/tool-output-root.ts
|
|
48163
|
+
/** The environment variable each tool reads for its profile root. */
|
|
48164
|
+
const TOOL_HOME_ENV_VARS = {
|
|
48165
|
+
hermesagent: "HERMES_HOME",
|
|
48166
|
+
"kimi-code": "KIMI_CODE_HOME"
|
|
48167
|
+
};
|
|
48168
|
+
/**
|
|
48169
|
+
* Substitute a tool's home override (`HERMES_HOME`, `KIMI_CODE_HOME`) for the
|
|
48170
|
+
* output root in global scope.
|
|
48171
|
+
*
|
|
48172
|
+
* The override wins over `--output-roots`: it names where the tool itself reads
|
|
48173
|
+
* its profile, so writing anywhere else would produce files the tool ignores.
|
|
48174
|
+
*
|
|
48175
|
+
* A substituted value goes through the same `validateOutputRoot` the CLI and
|
|
48176
|
+
* config paths use, so an override of `/` or an unnormalized path is rejected
|
|
48177
|
+
* instead of silently becoming the output root. The rejection is re-thrown
|
|
48178
|
+
* naming the variable, since the user never passed an `--output-roots` flag.
|
|
48179
|
+
*/
|
|
46258
48180
|
function resolveToolOutputRoot({ outputRoot, toolTarget, global }) {
|
|
46259
48181
|
if (!global) return outputRoot;
|
|
46260
|
-
|
|
48182
|
+
const resolved = toolTarget === "hermesagent" ? resolveHermesagentOutputRoot({
|
|
46261
48183
|
outputRoot,
|
|
46262
48184
|
global
|
|
46263
|
-
});
|
|
46264
|
-
if (
|
|
46265
|
-
|
|
48185
|
+
}) : toolTarget === "kimi-code" ? getKimiCodeHome() ?? outputRoot : outputRoot;
|
|
48186
|
+
if (resolved === outputRoot) return resolved;
|
|
48187
|
+
try {
|
|
48188
|
+
validateOutputRoot(resolved);
|
|
48189
|
+
} catch (error) {
|
|
48190
|
+
throw new Error(`${TOOL_HOME_ENV_VARS[toolTarget] ?? "The tool home override"} is not a usable output root: ${formatError(error)}`, { cause: error });
|
|
48191
|
+
}
|
|
48192
|
+
return resolved;
|
|
46266
48193
|
}
|
|
46267
48194
|
//#endregion
|
|
46268
48195
|
//#region src/lib/convert.ts
|
|
@@ -47663,7 +49590,11 @@ async function generateChecksCore(params) {
|
|
|
47663
49590
|
for (const toolTarget of toolTargets) for (const outputRoot of config.getOutputRoots(toolTarget)) {
|
|
47664
49591
|
if (!config.getFeatures(toolTarget).includes("checks")) continue;
|
|
47665
49592
|
const processor = new ChecksProcessor({
|
|
47666
|
-
outputRoot
|
|
49593
|
+
outputRoot: resolveToolOutputRoot({
|
|
49594
|
+
outputRoot,
|
|
49595
|
+
toolTarget,
|
|
49596
|
+
global: config.getGlobal()
|
|
49597
|
+
}),
|
|
47667
49598
|
inputRoot: config.getInputRoot(),
|
|
47668
49599
|
toolTarget,
|
|
47669
49600
|
global: config.getGlobal(),
|
|
@@ -48038,4 +49969,4 @@ async function importChecksCore(params) {
|
|
|
48038
49969
|
//#endregion
|
|
48039
49970
|
export { ErrorCodes as $, RULESYNC_SKILLS_RELATIVE_DIR_PATH as $t, RulesyncMcp as A, RULESYNC_AIIGNORE_RELATIVE_FILE_PATH as At, stringifyFrontmatter as B, RULESYNC_IGNORE_RELATIVE_FILE_PATH as Bt, RulesyncSubagent as C, writeFileContent as Ct, RulesyncRule as D, ToolTargetSchema as Dt, RulesyncSkillFrontmatterSchema as E, PACKAGING_TOOL_TARGETS as Et, parseJsonc as F, RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH as Ft, ConfigFileSchema as G, RULESYNC_MCP_SCHEMA_URL as Gt, SHARED_USER_MANAGED_CONFIG_PATHS as H, RULESYNC_MCP_FILE_NAME as Ht, RulesyncCommand as I, RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH as It, ConsoleLogger as J, RULESYNC_PERMISSIONS_LEGACY_FILE_NAME as Jt, SourceEntrySchema as K, RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH as Kt, RulesyncCommandFrontmatterSchema as L, RULESYNC_HOOKS_FILE_NAME as Lt, RulesyncHooks as M, RULESYNC_COMMANDS_RELATIVE_DIR_PATH as Mt, getRulesyncSourceCandidates as N, RULESYNC_CONFIG_RELATIVE_FILE_PATH as Nt, RulesyncRuleFrontmatterSchema as O, MAX_FILE_SIZE as Ot, resolveRulesyncSourceWritePath as P, RULESYNC_CONFIG_SCHEMA_URL as Pt, CLIError as Q, RULESYNC_RULES_RELATIVE_DIR_PATH as Qt, RulesyncCheck as R, RULESYNC_HOOKS_LEGACY_FILE_NAME as Rt, getLocalSkillDirNames as S, toPosixPath as St, RulesyncSkill as T, ALL_TOOL_TARGETS_WITH_WILDCARD as Tt, SKILL_FILE_NAME as U, RULESYNC_MCP_LEGACY_FILE_NAME as Ut, loadYaml as V, RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH as Vt, ConfigResolver as W, RULESYNC_MCP_RELATIVE_FILE_PATH as Wt, fallbackLogger as X, RULESYNC_PERMISSIONS_SCHEMA_URL as Xt, JsonLogger as Y, RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH as Yt, warnOnConflictingFlags as Z, RULESYNC_RELATIVE_DIR_PATH as Zt, CLAUDECODE_SETTINGS_LOCAL_FILE_NAME as _, removeFile as _t, convertFromTool as a, directoryExists as at, CODEXCLI_BASH_RULES_FILE_NAME as b, resolvePath as bt, SubagentsProcessor as c, findFilesByGlobs as ct, IgnoreProcessor as d, isSymlink as dt, RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH as en, assertDirectoryIfExists as et, HooksProcessor as f, listDirectoryFiles as ft, CLAUDECODE_MEMORIES_DIR_NAME as g, removeDirectoryStrict as gt, CLAUDECODE_LOCAL_RULE_FILE_NAME as h, removeDirectory as ht, getProcessorRegistryEntry as i, formatError as in, createTempDirectory as it, RulesyncIgnore as j, RULESYNC_CHECKS_RELATIVE_DIR_PATH as jt, RulesyncPermissions as k, RULESYNC_AIIGNORE_FILE_NAME as kt, SkillsProcessor as l, getFileSize as lt, CLAUDECODE_DIR as m, readFileContentOrNull as mt, checkRulesyncDirExists as n, ALL_FEATURES as nn, assertWritablePathInsideRoot as nt, isPackagingToolTarget as o, ensureDir as ot, CommandsProcessor as p, readFileContent as pt, findControlCharacter as q, RULESYNC_PERMISSIONS_FILE_NAME as qt, generate as r, ALL_FEATURES_WITH_WILDCARD as rn, checkPathTraversal as rt, RulesProcessor as s, fileExists as st, importFromTool as t, RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH as tn, assertTreeContainsNoSymlinks as tt, McpProcessor as u, getHomeDirectory as ut, CLAUDECODE_SKILLS_DIR_PATH as v, removeFileStrict as vt, RulesyncSubagentFrontmatterSchema as w, ALL_TOOL_TARGETS as wt, CODEXCLI_DIR as x, runWithDirectoryRollback as xt, ChecksProcessor as y, removeTempDirectory as yt, RulesyncCheckFrontmatterSchema as z, RULESYNC_HOOKS_RELATIVE_FILE_PATH as zt };
|
|
48040
49971
|
|
|
48041
|
-
//# sourceMappingURL=import-
|
|
49972
|
+
//# sourceMappingURL=import-CArKOPG_.js.map
|